你還應該知道的雜湊衝突解決策略
本文首發於 vivo網際網路技術 微信公眾號
連結: https://mp.weixin.qq.com/s/5vxYoeARG1nC7Z0xTYXELA
作者:Xuegui Chen
雜湊是一種透過對資料進行壓縮, 從而提高效率的一種解決方法,但由於雜湊函式有限,資料增大等緣故,雜湊衝突成為資料有效壓縮的一個難題。本文主要介紹雜湊衝突、解決方案,以及各種雜湊衝突的解決策略上的優缺點。
一、雜湊表概述
雜湊表的雜湊函式輸入一個鍵,並向返回一個雜湊表的索引。可能的鍵的集合很大,但是雜湊函式值的集合只是表的大小。
雜湊函式的其他用途包括密碼系統、訊息摘要系統、數字簽名系統,為了使這些應用程式按預期工作,衝突的機率必須非常低,因此需要一個具有非常大的可能值集合的雜湊函式。
密碼系統:給定使用者密碼,作業系統計算其雜湊,並將其與儲存在檔案中的該使用者的雜湊進行比較。(不要讓密碼很容易被猜出雜湊到相同的值)。
訊息摘要系統:給定重要訊息,計算其雜湊,並將其與訊息本身分開發布。希望檢查訊息有效性的讀者也可以使用相同的演算法計算其雜湊,並與釋出的雜湊進行比較。(不要希望偽造訊息很容易,仍然得到相同的雜湊)。
這些應用的流行雜湊函式演算法有:
-
md5 : 2^128個值(找一個衝突鍵,需要雜湊大約2 ^ 64個值)
-
sha-1:2^160個值(找一個衝突鍵,需要大約2^80個值)
二、雜湊衝突
來看一個簡單的例項吧,假設採用hash函式:H(K) = K mod M,插入這些值:217、701、19、30、145
H(K) = 217 % 7 = 0
H(K) = 701 % 7 = 1
H(K) = 19 % 7 = 2
H(K) = 30 % 7 = 2
H(K) = 145 % 7 = 5
上面例項很明顯 19 和 30 就發生衝突了。
三、衝突解決策略
除非您要進行“完美的雜湊”,否則必須具有衝突解決策略,才能處理表中的衝突。
同時,該策略必須允許查詢,插入和刪除正確執行的操作!
衝突解決技術可以分為兩類:開雜湊方法( open hashing,也稱為拉鍊法,separate chaining )和閉雜湊方法( closed hashing,也稱為開地址方法,open addressing )。這兩種方法的不同之處在於:開雜湊法把發生衝突的關鍵碼儲存在雜湊表主表之外,而閉雜湊法把發生衝突的關鍵碼儲存在表中另一個槽內。
下面介紹業內比較流行的hash衝突解決策略:
-
線性探測(Linear probing)
-
雙重雜湊(Double hashing)
-
隨機雜湊(Random hashing)
-
分離連結(Separate chaining)
上面線性探測、雙重雜湊、隨機雜湊都是閉雜湊法,而分離連結則是開雜湊法。
1、線性探測(Linear probing)
插入一個值
使用雜湊函式H(K)在大小為M的表中插入金鑰K時:
-
設定 indx = H(K)
-
如果表位置indx已經包含金鑰,則無需插入它。Over
-
否則,如果表位置indx為空,則在其中插入鍵。Over
-
其他碰撞。設定 indx =(indx + 1)mod M.
-
如果 indx == H(K),則表已滿!就只能做雜湊表的擴容了
因此,線性探測基本上是在發生碰撞時對空槽進行線性搜尋。
優點:易於實施;總是找到一個位置(如果有);當表不是很滿時,平均情況下的效能非常好。
缺點:表的相鄰插槽中會形成“叢集”或“叢集”鍵;當這些簇填滿整個陣列的大部分時,效能會嚴重下降,因為探針序列執行的工作實際上是對大部分陣列的窮舉搜尋。
簡單例子
如雜湊表大小M = 7, 雜湊函式:H(K) = K mod M。插入這些值:701, 145, 217, 19, 13, 749
H(K) = 701 % 7 = 1
H(K) = 145 % 7 = 5
H(K) = 217 % 7 = 0
H(K) = 19 % 7 = 2
H(K) = 13 % 7 = 1(衝突) --> 2(已經有值) --> 3(插入位置3)
H(K) = 749 % 7 = 2(衝突) --> 3(已經有值) --> 4(插入位置4)
可見,如果雜湊表如果不是很大,隨著資料插入,衝突也會元件發生,探針遍歷次數將會逐漸變低,檢索過程也就成為窮舉。
檢索一個值
如果使用線性探測將鍵插入表中,則線性探測將找到它們!
當使用雜湊函式 H(K)在大小為N的表中搜尋鍵K時:
-
設定 indx = H(K)
-
如果表位置indx包含鍵,則返回FOUND。
-
否則,如果表位置 indx 為空,則返回NOT FOUND。
-
否則設定 indx =(indx + 1)modM。
-
如果 indx == H(K),則返回NOT FOUND。就只能做雜湊表的擴容了
問題:如何從使用線性探測的表中刪除鍵?
能否進行“延遲刪除”,而只是將已刪除金鑰的插槽標記為空?
很明顯,線上性探測很難做到,如果把位置置為空,那麼如果後面的值也是雜湊衝突,線性探測插入,則再也無法遍歷這些值了。
2、雙重雜湊(Double hashing)
線性探測衝突解決方案會導致表中出現簇,因為如果兩個鍵發生碰撞,則探測到的下一個位置對於這兩個鍵都是相同的。
雙重雜湊的思想:使偏移到下一個探測到的位置取決於鍵值,因此對於不同的鍵可以不同。
需要引入第二個雜湊函式 H 2(K),用作探測序列中的偏移量(將線性探測視為 H 2(K)== 1 的雙重雜湊)。
對於大小為 M 的雜湊表,H 2(K)的值應在 1到M-1 的範圍內;如果M為質數,則一個常見選擇是 H2(K)= 1 +((K / M)mod(M-1))。
然後,用於雙雜湊的插入演算法為:
-
設定 indx = H(K); offset = H 2(K)
-
如果表位置indx已經包含金鑰,則無需插入它。Over
-
否則,如果表位置 indx 為空,則在其中插入鍵。Over
-
其他碰撞。設定 indx =(indx + offset)mod M.
-
如果 indx == H(K),則表已滿!就只能做雜湊表的擴容了
雜湊表為質數情況,雙重hash在實踐中非常有效
雙重 Hash 也見: https://blog.csdn.net/chenxuegui1234/article/details/103454285
3、隨機雜湊(Random hashing)
與雙重雜湊一樣,隨機雜湊透過使探測序列取決於金鑰來避免聚類。
使用隨機雜湊時,探測序列是由金鑰播種的偽隨機數生成器的輸出生成的(可能與另一個種子元件一起使用,該元件對於每個鍵都是相同的,但是對於不同的表是不同的)。
然後,用於隨機雜湊的插入演算法為:
-
建立以 K 為種子的 RNG。設定indx = RNG.next() mod M。
-
如果表位置 indx 已經包含金鑰,則無需插入它。Over
-
否則,如果表位置 indx 為空,則在其中插入鍵。Over
-
其他碰撞。設定 indx = RNG.next() mod M.
-
如果已探測所有M個位置,則放棄。就只能做雜湊表的擴容了。
隨機雜湊很容易分析,但是由於隨機數生成的“費用”,它並不經常使用。雙重雜湊在實踐中還是經常被使用。
4、分離連結(Separate chaining)
在具有雜湊函式 H(K)的表中插入鍵K時
-
設定 indx = H(K)
-
將關鍵字插入到以 indx 為標題的連結列表中。(首先搜尋列表,以避免重複。)
在具有雜湊函式H(K)的表中搜尋鍵K時
-
設定 indx = H(K)
-
使用線性搜尋在以 indx 為標題的連結串列中搜尋關鍵字。
使用雜湊函式 H(K)刪除表中的鍵K時
-
設定 indx = H(K)
-
刪除連結列表中以 indx 為標題的鍵
優點:隨著條目數量的增加,平均案例效能保持良好。甚至超過M;刪除比開放地址更容易實現。
缺點:需要動態資料,除資料外還需要儲存指標,本地性較差,導致快取效能較差。
很明顯,Java7 的 HashMap 就是一種分裂連結的實現方式。
分離鏈雜湊分析
請記住表的填充程度的負載係數度量:α = N / M。
其中M是表格的大小,並且 N 是表中已插入的鍵數。
透過單獨的連結,可以使 α> 1 給定負載因子α,我們想知道在最佳,平均和最差情況下的時間成本。
成功找到
新鍵插入和查詢失敗(這些相同),最好的情況是O(1),最壞的情況是O(N)。讓我們分析平均情況
分裂連結的平均成本
假設負載係數為 α = N / M 的表
在M個連結列表中總共分配了N個專案(其中一些可能為空),因此每個連結列表的平均專案數為:
-
如果查詢/插入失敗,則必須窮舉搜尋表中的連結串列之一,並且表中連結串列的平均長度為α。因此,使用單獨連結進行插入或不成功查詢的比較平均次數為
-
成功查詢後,將搜尋包含目標金鑰的連結列表。除目標金鑰外,該列表中平均還有(N-1)/ M個金鑰;在找到目標之前,將平均搜尋其中一半。因此,使用單獨連結成功找到的比較平均次數為
當α<1時,它們分別小於1和1.5。並且即使當α超過1時,它們仍然是O(1),與N無關。
四、開雜湊方法 VS 閉雜湊方法
如果將鍵保留為雜湊表本身中的條目,則可以使用線性探測,雙重和隨機雜湊... 這樣做稱為“開放式定址”,也稱為“封閉式雜湊”。
另一個想法:雜湊表中的條目只是指向連結串列(“鏈”)頭部的指標;連結列表的元素包含鍵... 這稱為“單獨連結”,也稱為“開放式雜湊”。
透過單獨的連結,衝突解決變得容易:只要在其連結串列中插入一個鍵,就可以將其插入(為此,可以使用比連結串列更高階的資料結構;但是正如我們將看到的,連結串列在一般情況下效果很好)。
讓下面我們看一下這些策略的時間成本。
開放式地址雜湊分析
分析雜湊表“查詢”或“插入”效能時,一個有用的引數是負載係數 α = N / M。
其中 M 是表格的大小,並且 N 是表中已插入的鍵數負載係數是表滿度的一種度量。
給定負載因子 α ,我們想知道在最佳,平均和最差情況下的時間成本。
成功找到
對所有鍵,最好的情況是O(1),最壞的情況是O(N),新鍵插入和查詢失敗(這些相同),所以讓我們分析平均情況。
我們將給出隨機雜湊和線性探測的結果。實際上,雙重雜湊類似於隨機雜湊;
平均不成功的查詢/插入成本
假定負載係數為α= N / M的表。考慮隨機雜湊,因此聚類不是問題。每個探針位置是隨機且獨立生成的對於每個探針,找到空位置的可能性為(1-α)。查詢空位置將停止查詢或插入,這是一個伯努利過程,成功機率為(1-α)。該過程的預期一階到達時間為 1 /(1-α)。所以:
使用隨機雜湊進行插入或不成功查詢的探針的平均數量為
使用線性探測時,探頭的位置不是獨立的。團簇形成,當負載係數高時會導致較長的探針序列。可以證明,用於線性探測的插入或未成功發現的探針的平均數量約為
當 α 接近1時,這些平均案例時間成本很差,僅受M限制;但當 α 等於或小於7.75(與M無關)時,效果還不錯(分別為4和8.5)
平均成功查詢成本
假定負載係數為 α= N / M 的表。考慮隨機雜湊,因此聚類不是問題。每個探針位置是隨機且獨立生成的。
對於表中的鍵,成功找到它所需的探針數等於將其插入表中時所採用的探針數。每個新金鑰的插入都會增加負載係數,從0開始到α。
因此,透過隨機雜湊成功發現的探測器的平均數量為
透過線性探測,會形成簇,從而導致更長的探針序列。可以證明,透過線性探測成功發現的平均探針數為
當α接近1時,這些平均案例時間成本很差,僅受M限制;但當α等於或小於7.75時好(分別為1.8和2.5),與M無關。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69912579/viewspace-2690034/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 雜湊衝突詳解
- 雜湊衝突
- Python:說說字典和雜湊表,雜湊衝突的解決原理Python
- 聊天室原始碼開發中應對雜湊衝突的解決方案原始碼
- 在Java中,HashMap中是用哪些方法來解決雜湊衝突的?JavaHashMap
- 【面試普通人VS高手系列】HashMap是怎麼解決雜湊衝突的?面試HashMap
- 題解 洛谷 P3396 【雜湊衝突】(根號分治)
- 你知道雜湊演算法,但你知道一致性雜湊嗎?演算法
- 演算法與資料結構——雜湊衝突演算法資料結構
- Git 解決衝突Git
- git 解決衝突Git
- 如何克服解決Git衝突的恐懼症?(Git分支策略)Git
- Git衝突解決技巧Git
- hash衝突解決方法
- 程式衝突及其解決
- git pull 衝突解決Git
- git pull衝突的解決方案Git
- JAR衝突問題的解決JAR
- 你應該知道的RocketMQMQ
- 你應該知道的FlutterFlutter
- SVN解決衝突 記錄
- css命名衝突解決方法CSS
- 雜湊表 ADT 開放地址法解決衝突【資料結構與演算法分析 c 語言描述】資料結構演算法
- 依賴衝突時的解決方法
- 你應該知道的JS —— 物件JS物件
- Git 衝突了怎麼辦,如何高效快速的解決程式碼衝突?Git
- git 解決版本衝突問題Git
- 解決動態庫的符號衝突符號
- 解決預設方法衝突的規則
- hash解決衝突的方法優缺點
- 雜湊表(雜湊表)原理詳解
- 你應該知道的Linux歷史Linux
- 你應該知道的Redis事務Redis
- 你應該知道的前端——快取前端快取
- 你應該知道的程式集版本
- 關於 jwt ,你應該知道的JWT
- 你應該知道的前端--儲存前端
- 你應該知道的前端--渲染原理前端