1. 程式人生 > >java 解決Hash(雜湊)衝突的四種方法--開放定址法(線性探測,二次探測,偽隨機探測)、鏈地址法、再雜湊、建立公共溢位區

java 解決Hash(雜湊)衝突的四種方法--開放定址法(線性探測,二次探測,偽隨機探測)、鏈地址法、再雜湊、建立公共溢位區

一)雜湊表簡介

非雜湊表的特點:關鍵字在表中的位置和它之間不存在一個確定的關係,查詢的過程為給定值一次和各個關鍵字進行比較,查詢的效率取決於和給定值進行比較的次數。

    雜湊表的特點:關鍵字在表中位置和它之間存在一種確定的關係。

雜湊函式:一般情況下,需要在關鍵字與它在表中的儲存位置之間建立一個函式關係,以f(key)作為關鍵字為key的記錄在表中的位置,通常稱這個函式f(key)為雜湊函式。

hash : 翻譯為“雜湊”,就是把任意長度的輸入,通過雜湊演算法,變成固定長度的輸出,該輸出就是雜湊值。

           這種轉換是一種壓縮對映雜湊值的空間通常遠小於輸入的空間

,不同的輸入可能會雜湊成相同的輸出,所以不可能從雜湊值來唯一的確定輸入值。

           簡單的說就是一種將任意長度的訊息壓縮到莫伊固定長度的訊息摘要的函式。

hash衝突:(大師兄自己寫的哦)就是根據key即經過一個函式f(key)得到的結果的作為地址去存放當前的key value鍵值對(這個是hashmap的存值方式),但是卻發現算出來的地址上已經有人先來了。就是說這個地方要擠一擠啦。這就是所謂的hash衝突啦

二)雜湊函式處理衝突的方法

1)開放定址法:

其中 m 為表的長度

對增量di有三種取法:

線性探測再雜湊   di = 1 , 2 , 3 , ... , m-1

平方探測再雜湊   di = 1 2 , -12 , 22 , -22 , 32 , -32 , ... , k2 ,  -k2

(大師兄備註:嗎單,上面的平方探測再雜湊是加1的平方;減1的平方,加2的平方,減2的平方,加3的平方,減3的平方。。。加k的平方,減k的平方。臥擦,老師你能再坑點麼?法科。要是你直接看這個平方探測再雜湊的di是怎麼來的,不一定能看懂老師ppt的這個寫法,是平方的意思。上面的紅色字呢,相當於是老師的ppt,是對應上面的圖片一起看的。)

隨機探測再雜湊   di 是一組偽隨機數列

例子:


我在上面的這個配圖底部寫的那個紅色的12,我當時測試的時候,不知道這個12,也就是上面增量 di 的由來。不知道,限制知道了,那是1的2次方。。。。老師懶得或者說不會給數字打角標。


2)鏈地址法

上面這個只是老師的ppt,下面放上自己親自整的測試。


先按照ppt上的hash演算法:h(key) = key % 7,算出來對應的hash值,這個hash值暫時就決定,當前的這個值,存放在陣列的位置。
都算完之後,就可以,按照這個hash值,依次的,把這些數,都放在下面的陣列上。然後就有我自己的這個截圖。
和上面的ppt推算的是一致的。

這個做法就是Java的HashMap就是這麼實現的,簡單的解釋下,這個HashMap原始碼的這個連結串列產生機制。
在put()方法裡面,最後部分有個如下的呼叫。
addEntry(hash, key, value, i);
解釋下幾個引數的意思:
1,hash:就是根據key算出來的一個值,原始碼是這麼滴--int hash = hash(key);,
這個算出來的這個就相當於是身份證號碼,可以唯一確定一個人一樣,唯一確定這個map
2,key:key就是我們在往hashmap裡面put鍵值對的時候的key,使用map的時候,不是可以根據key拿到value嗎。
3,value:這個同上啦,就是存的鍵值對的值。
4,i:原始碼裡面是這麼滴--int i = indexFor(hash, table.length);實際意思就是這個鍵值對存放在底層陣列的索引下標。
然後這個i,可以對應到ppt上的那個取模之後的值,也就是確定在陣列上的下標。

雖然在put的時候,可能會出現擴容的問題,但是在這咱就不考慮這個,只考慮如何生成連結串列,以及連結串列上的鍵值對的順序。
createEntry(hash, key, value, bucketIndex);
這個方法就是真正的在建立一個節點到陣列上。
這幾個引數是一樣的,和上面解釋的一樣的意思。

	//先從陣列上取下原來的值,給塞到新的節點去,然後把新的節點再放到陣列上。
	//也就是後來居上的道理。ppt上畫的也就有點毛病了。
	//老師們嘛,就是 混口飯吃,一般都不斤斤計較這東西的。
    void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }
    static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;
        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
		//******
	}

上面就是hashmap底層陣列上存的元素的model。也是能形成連結串列的關鍵,有興趣的可以看看1.7的hashmap的原始碼。

3、4)再雜湊、建立公共溢位區

3.再hash法,就是算hashcode的方法不止一個,一個要是算出來重複啦,再用另一個演算法去算。反正很多,直到不重複為止咯。大師兄猜的

4.建立一個公共溢位區域,就是把衝突的都放在另一個地方,不在表裡面。具體實現就 不知道啦,也是大師兄猜的。

總結一下的就是下面的四行字: 1.開放定址法(線性探測再雜湊,二次探測再雜湊,偽隨機探測再雜湊)
2.再雜湊法
3.鏈地址法(Java hashmap就是這麼做的)
4.建立一個公共溢位區


看到這個,自個兒還是得靜下心來看看hashmap的原始碼,1.7的簡單易懂,我還做了註解,可以看看,連結如下

Java