Hashing

理想中的hash table仅仅是包含元素的固定大小的数组,有用来查询的key和数据字段value,我们假设表的大小为TableSize。
每一个key被映射到0到TableSize-1区间,并替换数组适当的单元格。这种映射方法叫hash函数,我们希望这个映射方法应该简单,并且能够让不同的key获得不同的hash值,然而由于我们有有限的单元格,但却需要支持无尽的key,显然是不可能的,因此我们需要找到一种hash函数,能够将key映射均匀的在这些单元格中。最后,当不同的key映射到相同的值时(通常称之为冲突),我们如何处理?

Hash Function

假如key是整型数值时,没有特殊情况的话,直接返回key mod TableSize即可。比如如果大小为10,但key又都是以0结尾的,那么这种方式显然不合理了。为了防止这种情况发生,我们通常设置TableSize为一个素数。
当key为字符串时,就需要多多考虑这个函数应该如何设计了。一种方式是将每一个字符的ASCII码相加:

public class Hashing {
    public static int hash(String key, int tableSize) {
        int hashVal = 0;
        
        for(int i = 0; i<key.length(); i++) {
            hashVal += key.charAt(i);
        }
        
        return hashVal % tableSize;
    }
}

这种方式简单快捷,但是当TableSize很大的时候,这个方法无法均匀的分布key。例如当大小为10007(素数)。假设key的长度为8或者更小,由于ASCII最大只有127因此key最多分布在1到1016之间。
另一种方式如下:

    public static int hash(String key, int tableSize) {
        return (key.charAt(0) + key.charAt(1) * 27 +key.charAt(2) * 729) % tableSize;
    }

假设key至少有三个字符,27代表英文的字母和一个空格,729是27的平方。这个方法仅仅考虑key的前3个字符,如上所述,虽然263=17576,但英文单词并不是完全随机的,通过字典可以查到常用的单词也就2851个,即使这些完全没有碰撞,但只有28%的区间被映射到,因此这个方法也是不太适合的。
那么我们可以将所有的字符都考虑进来,并生成一个和质数相关的多项式,同时也要考虑可能存在溢出的情况:

    public static int hash(String key, int tableSize) {
        int hashVal = 0;
        for( int i = 0; i<key.length(); i++) {
            hashVal = 37 * hashVal + key.charAt(i);
        }
        
        hashVal = hashVal % tableSize;
        if( hashVal < 0) {
            hashVal += tableSize;
        }
        
        return hashVal;
    }

当然这种方式也不一定是最优的,并且如果key特别长的话将会耗费大量的时间,我们可以看到JDK中字符串的hashcode的实现方式是:

    public static int hashCode(byte[] value) {
        int h = 0;
        int length = value.length >> 1;

        for(int i = 0; i < length; ++i) {
            h = 31 * h + getChar(value, i);
        }

        return h;
    }

剩下的就是需要解决冲突问题了,当两个key有相同的hash结果时,将会发生冲突。有很多种方法来解决这个问题,最主要的两种是独立链表和开放式寻址。

独立链表

第一种方式是独立链表(separate chaining),将所有相同hash值存放在一个链表中。通过使用hash函数来决定在哪个链表中进行遍历,来实现查找。当插入值时,我们hash函数计算后,如果已经有值,就在该值得头部插入,因为通常最近插入的使用会更频繁。

import java.util.LinkedList;
import java.util.List;

/**
 * @author dalu
 * @create 2020-04-12 22:01
 */
public class SeparateChainingHashTable <AnyType> {
    public SeparateChainingHashTable() {
        this(DEFAULT_TABLE_SIZE);
    }

    public SeparateChainingHashTable(int size) {
        theLists = new LinkedList[nextPrime(size)];
        for(int i = 0; i < theLists.length; i++) {
            theLists[i] = new LinkedList<>();
        }
    }

    public void insert(AnyType x) {
        List<AnyType> whichList = theLists[myhash(x)];
        if( !whichList.contains(x)) {
            whichList.add(x);

            if(++currentSize > theLists.length) {
                rehash();
            }
        }

    }

    public void remove(AnyType x) {
        List<AnyType> whichList = theLists[myhash(x)];
        if(whichList.contains(x)) {
            whichList.remove(x);
            currentSize--;
        }

    }

    public boolean contains(AnyType x) {
        List<AnyType> whichList = theLists[myhash(x)];
        return whichList.contains(x);
    }

    public void makeEmpty() {
        for(int i = 0; i < theLists.length; i++) {
            theLists[i].clear();
        }
        currentSize = 0;
    }

    private static final int DEFAULT_TABLE_SIZE = 101;

    private List<AnyType> [] theLists;

    private int currentSize;

    private void rehash() {
        List<AnyType> [] oldLists = theLists;
        
        theLists = new List[nextPrime(2 * theLists.length)];
        for(int j = 0; j < theLists.length; j++) {
            theLists[j] = new LinkedList<>();
        }
        currentSize = 0;
        for(int i = 0; i < oldLists.length; i++) {
            for(AnyType item : oldLists[i]) {
                insert(item);
            }
        }

    }

    private int myhash(AnyType x) {
        int hashVal = x.hashCode();

        hashVal %= theLists.length;
        if(hashVal < 0) {
            hashVal += theLists.length;
        }
        return hashVal;

    }

    private static int nextPrime(int n) {
        if(n % 2 == 0) {
            n++;
        }
        for(; !isPrime(n); n += 2) {

        }
        return n;

    }

    private static boolean isPrime(int n) {
        if(n == 2 || n == 3) {
            return true;
        }

        if(n == 1 || n % 2 == 0) {
            return false;
        }

        for(int i = 3; i * i <= n; i += 2) {
            if(n % i == 0) {
                return  false;
            }
        }
        return  true;

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

推荐阅读更多精彩内容