【Java基礎知識】哈希表知識總結

哈希表

1.哈希表的概念

順序結構以及平衡樹中,元素關鍵碼與其存儲位置之間沒有對應的關係,因此在查找一個元素時,必須要經過關鍵
碼的多次比較。順序查找時間複雜度爲O(N),平衡樹中爲樹的高度,即O(log2Nlog_2 N),搜索的效率取決於搜索過程中元素的比較次數。
理想的搜索方法:可以不經過任何比較,一次直接從表中得到要搜索的元素。 如果構造一種存儲結構,通過某種函數(hashFunc)使元素的存儲位置與它的關鍵碼之間能夠建立一一映射的關係,那麼在查找時通過該函數可以很快找到該元素。

以上提到的方式即爲哈希(散列)方法,哈希方法中使用的轉換函數稱爲哈希(散列)函數,構造出來的結構稱爲哈希表(HashTable)(或者稱散列表)。

哈希表中,使用元素和主鍵一一對應的方法來提高搜索的時間效率。
插入元素時,調用元素的哈希函數生成唯一標識的哈希值,在哈希表中找到該值所對應的存儲空間,進而將元素存放到對應的空間中。因此,搜索時,元素將調用哈希函數生成唯一標識的鍵碼,然後從哈希表結構中找到該鍵碼的位置,與要搜索的數進行比對,若相同,則查找成功。

2.哈希衝突

(1)衝突的概念

多個元素在調用相同的哈希函數計算哈希值時生成的哈希值卻是相同的,這就導致了哈希衝突

(2)衝突的避免

既然創建了哈希表,那麼衝突就是不可避免的,因爲一般我們創建的哈希結構底層數組的容量是小於實際存儲的元素數量的。我們要做的額就是儘可能的減少衝突

(3)哈希函數的設計

減少衝突最直觀的方式就是設置合理的哈希函數,哈希函數設計的規則:

  • 哈希函數的定義域必須包括需要存儲的全部關鍵碼,而如果散列表允許有m個地址時,其值域必須在0到m-1
    之間
  • 哈希函數計算出來的地址能均勻分佈在整個空間中
  • 哈希函數應該比較簡單

最常用的哈希函數:

  1. 直接定製法–(常用)
    取關鍵字的某個線性函數爲散列地址:Hash(Key)= A*Key + B
    優點:簡單、均勻
    缺點:需要事先知道關鍵字的分佈情況
    使用場景:適合查找比較小且連續的情況 面試題:字符串中第一個只出現一次字符
  2. 除留餘數法–(常用)
    設散列表中允許的地址數爲m,取一個不大於m,但最接近或者等於m的質數p作爲除數,按照哈希函數
    Hash(key) = key% p(p<=m),將關鍵碼轉換成哈希地址
  3. 平方取中法–(瞭解)
    假設關鍵字爲1234,對它平方就是1522756,抽取中間的3位227作爲哈希地址; 再比如關鍵字爲4321,對它平方就是18671041,抽取中間的3位671(或710)作爲哈希地址 平方取中法比較適合:不知道關鍵字的分佈,而位數又不是很大的情況
  4. 摺疊法–(瞭解)
    摺疊法是將關鍵字從左到右分割成位數相等的幾部分(最後一部分位數可以短些),然後將這幾部分疊加求和,並按散列表表長,取後幾位作爲散列地址。
    摺疊法適合事先不需要知道關鍵字的分佈,適合關鍵字位數比較多的情況
  5. 隨機數法–(瞭解)
    選擇一個隨機函數,取關鍵字的隨機函數值爲它的哈希地址,即H(key) = random(key),其中random爲隨機數
    函數。
    通常應用於關鍵字長度不等時採用此法
(4)負載因子

在哈希表中有一個負載因子的概念,負載因子的定義爲:a = 填入表中元素個數 / 散列長度
a是哈希表裝滿程度的一個表示,a越大,表示在該哈希表中填入的元素數量越多,相應的產生衝突的概率也就越大,反之a越小,衝突的概率也就越小。

負載因子和衝突率關係粗略演示:
在這裏插入圖片描述
對於開放定址法,負載因子應該嚴格的控制在0.7-0.8下,在Java中系統限定的負載因子爲0.75超過此值,就會重新爲散列表擴容。

(5)衝突的解決—閉散列

閉散列:也叫開放定址法,當發生哈希衝突時,如果哈希表未被裝滿,說明在哈希表中必然還有空位置,那麼可以把key存放到衝突位置中的“下一個” 空位置中去,找下一個空位置就有不同方法:

  1. 線性探測:從發生衝突的位置開始,依次向後探測,直到尋找到下一個空位置爲止。
  • 插入
  • 通過哈希函數獲取待插入元素在哈希表中的位置
  • 如果該位置中沒有元素則直接插入新元素,如果該位置中有元素髮生哈希衝突,使用線性探測找到
    下一個空位置,插入新元素

採用閉散列處理哈希衝突時,不能隨便物理刪除哈希表中已有的元素,若直接刪除元素會影響其他元素的搜索。

  1. 二次探測
    線性探測的缺陷是產生衝突的數據堆積在一塊,這與其找下一個空位置有關係,因爲找空位置的方式就是挨着往後逐個去找,因此二次探測爲了避免該問題,找下一個空位置的方法爲:HiH_i = (H0H_0 + i2i^2 )% m,或者:HiH_i = (H0H_0 - i2i^2 )% m。其中:i = 1,2,3…, H0H_0是通過散列函數Hash(x)對元素的關鍵碼 key進行計算得到的位置,m是表的大小。

研究表明:當表的長度爲質數且表裝載因子a不超過0.5時,新的表項一定能夠插入,而且任何一個位置都不會被探查兩次。因此只要表中有一半的空位置,就不會存在表滿的問題。在搜索時可以不考慮表裝滿的情況,但在插入時必須確保表的裝載因子a不超過0.5,如果超出必須考慮增容。

(6)衝突解決方法—開散列

閉散列最大的問題就是空間利用低,因此一般使用開散列。
開散列法又叫鏈地址法(開鏈法),首先對關鍵碼集合用散列函數計算散列地址,具有相同地址的關鍵碼歸於同一子集合,每一個子集合稱爲一個桶,各個桶中的元素通過一個單鏈錶鏈接起來,各鏈表的頭結點存儲在哈希表中。
在這裏插入圖片描述
開散列,可以認爲是把一個在大集合中的搜索問題轉化爲在小集合中做搜索。

3.性能分析

雖然看起來哈希表都是在和衝突打交道,但實際上我們認爲哈希表的衝突是不高的,衝突個數是可控的。因此通常意義下,我們認爲哈希表的插入、刪除、查找的時間複雜度都是O(1)


擴展:
java 中計算哈希值實際上是調用的類的 hashCode 方法,進行 key 的相等性比較是調用 key 的 equals 方法。所以如果要用自定義類作爲 HashMap 的 key 或者 HashSet 的值,必須覆寫 hashCode 和 equals 方法,而且要做到equals 相等的對象,hashCode 一定是一致的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章