漫畫 | 什么是散列表(哈希表)?


創建與輸入數組相等長度的新數組,作為直接尋址表。兩數之和的期望是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紙一直默讀,我想那個面試官待會要面這個人吧。小伙子,你運氣真好,希望你面試成功。

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

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

喜歡本文的朋友,歡迎關注公眾號 @ 算法無遺策,和我們一起學數據結構、刷算法題。


喜歡本文的朋友,歡迎關注公眾號「算法無遺策」,收看更多精彩內容


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM