一、預備知識
1、非哈希表的特點:關鍵字在表中的位置和它之間不存在一個確定的關系,查找的過程為給定值一次和各個關鍵字進行比較,查找的效率取決於和給定值進行比較的次數。
2、哈希表的特點:關鍵字在表中位置和它之間存在一種確定的關系。
3、哈希函數:一般情況下,需要在關鍵字與它在表中的存儲位置之間建立一個函數關系,以f(key)作為關鍵字為key的記錄在表中的位置,通常稱這個函數f(key)為哈希函數。
4、hash:翻譯為“散列”,把任意長度的輸入通過hash算法變換成固定長度的輸出,這個輸出就是Hash值。這種轉換是一種壓縮映射,哈希值的空間遠小於輸入的空間,所以可能會發生“哈希碰撞”,即兩個不同的輸入,產生了同一個輸出,所以不可能從散列值來唯一的確定輸入值。簡單來說就是,一種將任意長度的消息壓縮到莫伊固定長度的消息摘要的函數,Hash算法常用於消息摘要的場景 MD5、SHA都屬於Hash算法的實現。
5、hash沖突:根據key即經過一個函數f(key)得到的結果的作為地址去存放當前的key value鍵值對(這個是hashmap的存值方式),但發現與其他的對象計算值一樣。
例如:當我們對某個元素進行哈希運算,得到一個存儲地址,要插入時發現已被其他元素占用。
二、解決方案
1、 開放定址法(再散列法):
當關鍵字key的哈希地址p出現沖突時,以p為基礎,產生另一個哈希地址p1,如果p1仍然沖突,再以p為基礎,產生另一個哈希地址,直到找出一個不沖突的哈希地址pi ,將相應元素存入其中。
一旦發生了沖突,就去尋找下一個空的散列地址,只要散列表足夠大,空的散列地址總能找到,並將記錄存入公式為:
fi(key) = (f(key)+di) MOD m (di=1,2,3,……,m-1)
※ 做法是:當沖突發生時,使用某種探測技術在散列表中形成一個探測序列。沿此序列逐個單元地查找,直到找到給定的關鍵字,或者碰到一個開放的地址(即該地址單元為空)為止(若要插入,在探查到開放的地址,則可將待插入的新結點存人該地址單元)。查找時探測到開放的地址則表明表中無待查的關鍵字,即查找失敗。
比如說,我們的關鍵字集合為{12,67,56,16,25,37,22,29,15,47,48,34},表長為12。
我們用散列函數f(key) = key mod l2
當計算前S個數{12,67,56,16,25}時,都是沒有沖突的散列地址,直接存入:
計算key = 37時,發現f(37) = 1,此時就與25所在的位置沖突。
我們應用上面的公式f(37) = (f(37)+1) mod 12 = 2。
於是將37存入下標為2的位置:
2、再哈希法(雙哈希法):
在發生沖突時,再用第二個,第三個...哈希函數算出哈希值,直到算出的哈希值不同為止。雖然不易發生聚集,但增加了計算時間。
3、鏈地址法(拉鏈法):
把同一個散列槽(數組的每一個槽)中的所有元素放到一個鏈表中。
每個哈希表節點都有一個next指針,多個哈希表節點可以用next指針構成一個單向鏈表,被分配到同一個索引上的多個節點可以用這個單向鏈表連接起來,如:
鍵值對k2, v2與鍵值對k1, v1通過計算后的索引值都為2,此時產生沖突,但是可以通道next指針將k2, k1所在的節點連接起來,這樣就解決了哈希的沖突問題。
4、建立公共溢出區:
將哈希表分為基本表和溢出表兩部分,凡是和基本表發生沖突的元素,一律填入溢出表。建立一個公共溢出區域,把沖突的都放在另一個地方,不在表里面。