1. 程式人生 > >漫畫 | 什麼是散列表(雜湊表)?

漫畫 | 什麼是散列表(雜湊表)?

建立與輸入陣列相等長度的新陣列,作為直接定址表。兩數之和的期望是Target,將Target依次減輸入陣列的元素,得到的值和直接定址表比較,如果定址表存在這個值則返回;如果不存在這個值則將輸入陣列中的元素插入定址表,再進行輸入陣列中的下一個元素。

再進一步優化可以將輸入陣列直接作為直接定址表,控制對應的下標就好,程式碼如下:

Code:直接定址表
class Solution {
        public int[] twoSum(int[] nums, int target) {
        for (int i = 1; i < nums.length; i++) {
            int temp = target - nums[i];
            for (int j = 0; j < i; j++) {
                if (temp == nums[j]) return new int[]{j, i};
            }
        }
        return null;
    }
}
動畫:直接定址表

數組裡面每一個槽位放的是8個位元組,用於一個指向外部類的引用。這個外部類可以是連結串列物件,也可以是紅黑樹物件,都可以存一個或者一個以上的元素,也可以是空連結串列或空樹。散列表在某種意義上需要的陣列空間可以比直接定址表要少的很多。

雜湊函式是將所有元素的鍵轉換為自然數,自然數的數集是{0,1,2,……}。

如果所有元素的鍵是正整數,最常用的方法是求模(除留餘數法)。我們選擇長度為素數M的陣列,對於任意正整數k,計算k mod M求得餘數;

如果所有元素的鍵是浮點數,我們將它表示為二進位制數,忽略小數點再轉化為十進位制,然後求模;

如果所有元素的鍵是字串,可以將它字串裡面的每一個字元通過ASCII碼轉換,並相加得到這個字串的hash,然後求模;

如果所有元素的鍵是物件或者組合鍵(物件裡面的是屬性型別不定),也可以通過上面的方法混合起來。

除了線性探測法,還有二次探測還有雙重探測。

線性探測法是,通過雜湊函式得到雜湊值,檢查這個雜湊值是否被佔用,如果被佔用,將索引增大,到達陣列結尾時折回陣列的開頭,直到找到沒有被佔用的雜湊值。

線性探測採用的雜湊函式為:

其中h`(k)是第一次通過雜湊函式得到的雜湊值。

二次探測採用的雜湊函式為:

雙重探測採用的雜湊函式為:

其中

鍵簇,是指元素在插入陣列後聚整合的一組連續的條目,決定線性探測的平均成本。

如下圖所示,插入之前已經看到了兩個比較長的鍵簇,如果待插入元素通過雜湊函式得到的雜湊值正好是這兩個鍵簇中的第一個位置,就需要探測很多次才能找到空的位置;如果落在了兩個鍵簇間的只有一個空位置,那就產生了更長的鍵簇,對線性探測的平均成本大大增加。

顯然,短小的鍵簇才能保證較高的效率,不管是插入、查詢還是刪除演算法。隨著插入的鍵越來越多,較長的鍵簇越來越多,有可能插入一個元素就將兩個很長的鍵簇合並。所以才有了兩次探測和雙重探測,可以降低這種情況出現。

動態空間處理其實就是改變陣列的長度,可以設定一個建構函式,這個建構函式可以接受一個固定的容量作為引數。

M是目前散列表陣列的長度,N是目前在散列表已插入元素的個數。如何擴容和縮容可以設定一個條件,如果N/M >= 上邊界,即平均每個槽承載元素超過一定程度,就進行擴容;如果N/M <= 下邊界,即平均每個槽承載元素降到一定程度,就進行縮容。

擴容和縮容都會建立一個新的長度M的散列表,雜湊函式也會因為M而改變,原來的所有元素通過新的雜湊函式重新雜湊並插入新的散列表中。

動畫:動態空間處理

Java 8之前,每一個槽對應一個連結串列;

Java 8開始之後,當雜湊衝突達到一定程度時,每一個位置槽從連結串列轉成紅黑樹。

面試官很客氣,一直送我到門口,我依依不捨地離開這個地方。嗯,面試官真是個好人。

我出去大門,看見一個面試者在拿著A4紙一直默讀,我想那個面試官待會要面這個人吧。小夥子,你運氣真好,希望你面試成功。

場景虛構,如有雷同,實屬巧合

-----完結-----

喜歡本文的朋友,歡迎關注公眾號「演算法無遺策」,和我們一起學資料結構、刷演算法題