查找(散列表)

定义

散列表通过算术操作将键转化为数组的索引来访问数组中的键值对。散列表的查找算法分两步:

  1. 用散列函数将被查找的键转化为一个数组的索引
  2. 处理碰撞冲突(拉链法和线性探测法)

优点:常数级别的查找和插入操作
缺点:无法进行有序性相关的操作

散列函数

正整数:将整数散列最常用的方法是除留余数法,选择大小为素数M的数组,对于任意正整数k,计算k除以M的余数

hashCode返回值:将默认的hashCode方法和除留余数法结合起来产生一个0到M-1的整数。通过符号位屏蔽,将一个32位的整数变成一个31位的非负整数。如果直接取余,结果可能是一个负数;如果取绝对值,最大整数可能会返回一个负数。

private int hash(Key x) {
  return (x.hashCode() & 0x7fffffff) % M;
}

基于拉链法的散列表

将数组中的每个元素指向一个链表,链表中的每个结点都存储了散列值为该元素索引的键值对。发生冲突时,将冲突的元素存到链表当中。

基于拉链法的散列表示意图

代码实现

package edu.princeton.cs.algs4.chapter3;

/**
 * 基于拉链法的散列表
 * Created by tianxianhu on 2017/3/14.
 */
public class SeparateChainingHashST<Key, Value> {

    private int N; //键值对的总数
    private int M; //散列表的大小
    private SequentialSearchST<Key, Value>[] st; // 存放链表对象的数组

    public SeparateChainingHashST() { this(997); }

    public SeparateChainingHashST(int M) {
        // 创建M条链表
        this.M = M;
        st = (SequentialSearchST<Key, Value>[])new SequentialSearchST[M];
        // 对每一个链表初始化
        for (int i = 0; i < M; i++) {
            st[i] = new SequentialSearchST();
        }
    }

    private int hash(Key key) {
        return (key.hashCode() & 0x7fffffff) % M;
    }

    public Value get(Key key) {
        return (Value) st[hash(key)].get(key);
    }

    public void put(Key key, Value value) {
        st[hash(key)].put(key,value);
    }

    public void delete(Key key) {
        st[hash(key)].delete(key);
    }
}

基于线性探测法的散列表

线性探测法是使用大小为M的数组保存N个键值对,其中M > N。当发生线性碰撞的时候,直接检查散列表中的下一个位置(将索引值加1)

代码实现

package edu.princeton.cs.algs4.chapter3;

/**
 * 基于线性探测的符号表
 * Created by tianxianhu on 2017/3/14.
 */
public class LinearProbingHashST<Key, Value> {
    private static final int INIT_CAPACITY = 4;

    private int n;          // 符号表中键值对的总数
    private int m;     // 线性探测表的大小
    private Key[] keys;     // 键
    private Value[] vals;   // 值

    public LinearProbingHashST() {
        this(INIT_CAPACITY);
    }

    public LinearProbingHashST(int capacity) {
        m = capacity;
        n = 0;
        keys = (Key[])   new Object[m];
        vals = (Value[]) new Object[m];
    }

    private int hash(Key key) {
        return (key.hashCode() & 0x7fffffff) % m;
    }

    private void resize(int cap) {
        LinearProbingHashST<Key, Value> temp = new LinearProbingHashST<>(cap);
        for (int i = 0; i < m; i++) {
            if (keys[i] != null) {
                temp.put(keys[i], vals[i]);
            }
        }
        keys = temp.keys;
        vals = temp.vals;
        m = temp.m;
    }

    public void put (Key key, Value val) {
        // 超过一半,将数组长度翻倍
        if (n >= m/2)
            resize(2* m);

        int i;
        // 探测
        for (i = hash(key); keys[i] != null; i = (i + 1) % m) {
            if (keys[i].equals(key)) {
                vals[i] = val;
                return;
            }
        }
        // 为空,则直接插入
        keys[i] = key;
        vals[i] = val;
        n++;
    }

    public Value get (Key key) {
        int i;
        // 探测
        for (i = hash(key); keys[i] != null; i = (i + 1) % m) {
            if (keys[i].equals(key)) {
                return vals[i];
            }
        }
        // 没有改键,返回空
        return null;
    }

    public boolean contains(Key key) {
        if (key == null)
            throw new IllegalArgumentException("argument to contains() is null");

        return get(key) != null;
    }

    // 删除指定键,并将簇中被删除键的右侧的所有键重新插入散列表中
    public void delete(Key key) {
        if (!contains(key))
            return;
        int i = hash(key);
        // 找到键所在的时机位置,然后删除
        while (!keys[i].equals(key)) {
            i = (i + 1) % m;
        }
        keys[i] = null;
        vals[i] = null;
        // 将该元素后面的键值对全部重新插入
        i = (i + 1) % m;
        while (keys[i] != null) {
            Key keyToRedo = keys[i];
            Value valueToRedo = vals[i];
            keys[i] = null;
            vals[i] = null;
            n--;
            put(keyToRedo, valueToRedo);
            i = (i + 1) % m;
        }
        n--;
        if (n > 0 && n == m/8)
            resize(m / 2);
    }
}

所有符号表实现的效率总结

符号表实现的时间复杂度
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容

  • 概念 散列技术: 在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f...
    liangxifeng833阅读 2,570评论 1 3
  • 本文主要介绍散列表(Hash Table)这一常见数据结构的原理与实现。由于个人水平有限,文章中难免存在不准确或是...
    absfree阅读 16,298评论 2 35
  • 数据结构与算法--散列表 之前学习了基于链表的顺序查找、基于有序数组的二分查找、二叉查找树、红黑树,这些算法在查找...
    sunhaiyu阅读 649评论 3 5
  • 如果所有的键都是小整数,我们可以用一个数组来实现无序的符号表,将键作为数组的索引而数组中键i处存储对应的值。这样我...
    sleepyjoker阅读 320评论 0 0
  • 打坐的人 双腿并齐 双手交叉 坐得笔直 面无表情 眼神有点深邃 无视行尸走肉 打坐的人时不时半睁眼 看看这个世俗的...
    夜昕子阅读 194评论 0 0