1. 程式人生 > >【java基礎 10】hash演算法衝突解決方法

【java基礎 10】hash演算法衝突解決方法

導讀:今天看了java裡面關於hashmap的相關原始碼(看了java6和java7),尤其是resize、transfer、put、get這幾個方法,突然明白了,為什麼我之前考資料結構死活考不過,就差那麼一點點。答:程式碼積累太少了!這段時間,看了java的原始碼、演變過程等,被虐的很慘,但是,很開心! 本篇文章,主要介紹解決hash演算法衝突的方法

一、基本概念

散列表:

hash:a mixture of meat, potatoes, and vegetables cut into small pieces and baked or fried

簡單說來,hash就是一組碎片的集合!所以,我們常說的hash函式,即雜湊函式指:資料元素的鍵值和儲存位置之間建立的對應關係H !而用鍵值通過雜湊函式獲取儲存位置的這種儲存方式構造的儲存結構稱為散列表(hash table),這一對映過程稱為雜湊。如果選定了某個雜湊函式H及相應的散列表L,則對每個資料元素X,函式值H(X.key)就是X在散列表L中的儲存位置,這個儲存位置也稱為雜湊地址(可以理解為hashcode)

PS:在理想情況下,應用的雜湊函式可以使每個鍵值與雜湊地址是意義對應的,但在實際應用中,這種情況很少或幾乎不會出現。經常出現的問題有:衝突

衝突:如果有雜湊函式H和鍵值A、B(A不等於B),但是H(A)=H(B)即算出的雜湊地址是一樣的,這種現象稱為衝突!          ——A、B為相對於H的同義詞

二、常用的解決hash衝突的方法

2.1,開放地址法

當衝突發生時,使用某種探查(亦稱探測)技術在散列表中形成一個探查(測)序列。沿此序列逐個單元地查詢,直到找到給定 的關鍵字,或者碰到一個開放的地址(即該地址單元為空)為止,常用的方法有:線性探測法、二次探測法(解決線性探測的堆積問題)、隨機探測法(和二次探測原理一致,不一樣的是:二次探測以定值跳躍,而隨機探測的雜湊地址跳躍長度是不定值)

缺點:1,刪除工作很困難,假如要從雜湊表 HT 中刪除一個記錄,應將這個記錄所在位置置為空,但我們只能標上已被刪除的標記,否則,將會影響以後的查詢。

2,不易探測到整個散列表的所有空間(線性探測法除外,但線性探測會出現堆積)

2.2,拉鍊法(鏈地址法)

將所有關鍵字為同義詞的結點連結在同一個單鏈表中,【例】設有 m = 5 , H(K) = K mod 5 ,關鍵字值序例 5 , 21 , 17 , 9 , 15 , 36 , 41 , 24 ,按外鏈地址法所建立的雜湊表如下圖所示:


優點:1,處理衝突簡單,且無堆積現象,即非同義詞決不會發生衝突,因此平均查詢長度較短。

2,由於拉鍊法中各連結串列上的結點空間是動態申請的,故它更適合於造表前無法確定表長的情況。

3,更易於實現插入和刪除

缺點:指標需要額外的空間,故當結點規模較小時,開放定址法較為節省空間,而若將節省的指標空間用來擴大散列表的規模,可使裝填因子變小,這又減少了開放定址法中的衝突,從而提高平均查詢速度。

2.3,多重雜湊法(再雜湊法)

這種方法是同時構造多個不同的雜湊函式:
    Hi=RH1(key)  i=1,2,…,k
當雜湊地址Hi=RH1(key)發生衝突時,再計算Hi=RH2(key)……,直到衝突不再產生。這種方法不易產生“堆積”,但增加了計算時間。

2.4,公共溢位區法

散列表由兩個一維陣列組成,一個稱為基本表,它實際上就是一個散列表。另外一個稱為溢位表。插入首先在基本表上進行,假如發生衝突,則將同義詞存入溢位表。這樣,可以保證基本表不會發生“堆積”

PS:基本表是不會發生堆積了,那溢位表呢?當進行查詢時,查詢到溢位表,這是不是又開啟了新一輪的衝突解決?

三、總結

再次看了一遍書,對於hash函式有了更深一層的理解。尤其是看了一些程式碼之後,發現自己真的很low!如上述介紹,綜合而言,開放地址法、再雜湊法、公共溢位區法都無可避免的多次衝突或堆積的解決或者消費了大量的時間,所以,選擇鏈地址法是相對而言最合適的。

現在,終於解決了一個自己之前的疑惑,為什麼hashmap或者hashtable會選擇用一個數組和連結串列的形式來實現?我那時候就在想,為啥不是全用陣列呢?我只想到了把資料放進去,卻沒有去想怎麼把資料拿出來用、綜合效率問題!