1. 程式人生 > >轉載:散列沖突的解決策略

轉載:散列沖突的解決策略

次數 第一個 找到 效率 dom 設計 插入記錄 主表 負載

沖突解決的策略 盡管散列函數的目標是使得沖突最少,但實際上沖突是無法避免的。因此,我們必須研究沖突解決策略。沖突解決技術可以分為兩類:開散列方法( open hashing,也稱為拉鏈法,separate chaining )和閉散列方法( closed hashing,也稱為開地址方法,open addressing )。這兩種方法的不同之處在於:開散列法把發生沖突的關鍵碼存儲在散列表主表之外,而閉散列法把發生沖突的關鍵碼存儲在表中另一個槽內。 開散列方法: 1、拉鏈法 開散列方法的一種簡單形式是把散列表中的每個槽定義為一個鏈表的表頭。散列到一個特定槽的所有記錄都放到這個槽的鏈表中。圖9-5說明了一個開散列的散列表,這個表中每一個槽存儲一個記錄和一個指向鏈表其余部分的指針。這7個數存儲在有11個槽的散列表中,使用的散列函數是h(K) = K mod 11。數的插入順序是77、7、110、95、14、75和62。有2個值散列到第0個槽,1個值散列到第3個槽,3個值散列到第7個槽,1個值散列到第9個槽。 2、桶式散列 桶式散列方法的基本思想是把一個文件的記錄分為若幹存儲桶,每個存儲桶包含一個或多個頁塊,一個存儲桶內的各頁塊用指針連接起來,每個頁塊包含若幹記錄。散列函數h把關鍵碼值K轉換為存儲桶號,即h(K)表示具有關鍵碼值K的記錄所在的存儲桶號。 圖9-6表示了一個具有B個存儲桶的散列文件組織。有一個存儲桶目錄表,存放B個指針,每個存儲桶一個,每個指針就是所對應存儲桶的第一個頁塊的地址。 有些存儲桶僅僅由一個頁塊組成,如下圖中的1號存儲桶。有的存儲桶由多個頁塊組成,每一個頁塊的塊頭上有一個指向下一個頁塊的指針,例如,如下圖中的第B-1號存儲桶由b4,b5,b6三個頁塊組成,每個存儲桶中最後一個頁塊的頭上為空指針。 閉散列方法: 閉散列方法把所有記錄直接存儲在散列表中。每個記錄關鍵碼key有一個由散列函數計算出來的基位置,即h(key)。如果要插入一個關鍵碼,而另一個記錄已經占據了R的基位置(發生碰撞),那麽就把R存儲在表中的其它地址內,由沖突解決策略確定是哪個地址。 閉散列表解決沖突的基本思想是:當沖突發生時,使用某種方法為關鍵碼K生成一個散列地址序列d0,d1,d2,... di ,...dm-1。其中d0=h(K)稱為K的基地址地置( home position );所有di(0< i< m)是後繼散列地址。當插入K時,若基地址上的結點已被別的數據元素占用,則按上述地址序列依次探查,將找到的第一個開放的空閑位置di作為K的存儲位置;若所有後繼散列地址都不空閑,說明該閉散列表已滿,報告溢出。相應地,檢索K時,將按同值的後繼地址序列依次查找,檢索成功時返回該位置di ;如果沿著探查序列檢索時,遇到了開放的空閑地址,則說明表中沒有待查的關鍵碼。刪除K時,也按同值的後繼地址序列依次查找,查找到某個位置di具有該K值,則刪除該位置di上的數據元素(刪除操作實際上只是對該結點加以刪除標記);如果遇到了開放的空閑地址,則說明表中沒有待刪除的關鍵碼。因此,對於閉散列表來說,構造後繼散列地址序列的方法,也就是處理沖突的方法。 形成探查的方法不同,所得到的解決沖突的方法也不同。下面是幾種常見的構造方法。 1、線性探查法 將散列表看成是一個環形表,若在基地址d(即h(K)=d)發生沖突,則依次探查下述地址單元:d+1,d+2,......,M-1,0,1,......,d-1直到找到一個空閑地址或查找到關鍵碼為key的結點為止。當然,若沿著該探查序列檢索一遍之後,又回到了地址d,則無論是做插入操作還是做檢索操作,都意味著失敗。 用於簡單線性探查的探查函數是: p(K,i) = i 例9.7 已知一組關鍵碼為(26,36,41,38,44,15,68,12,06,51,25),散列表長度M= 15,用線性探查法解決沖突構造這組關鍵碼的散列表。 因為n=11,利用除余法構造散列函數,選取小於M的最大質數P=13,則散列函數為:h(key) = key%13。按順序插入各個結點: 26: h(26) = 0,36: h(36) = 10, 41: h(41) = 2,38: h(38) = 12, 44: h(44) = 5。 插入15時,其散列地址為2,由於2已被關鍵碼為41的元素占用,故需進行探查。按順序探查法,顯然3為開放的空閑地址,故可將其放在3單元。類似地,68和12可分別放在4和13單元中. 2、二次探查法 二次探查法的基本思想是:生成的後繼散列地址不是連續的,而是跳躍式的,以便為後續數據元素留下空間從而減少聚集。二次探查法的探查序列依次為:12,-12,22 ,-22,...等,也就是說,發生沖突時,將同義詞來回散列在第一個地址的兩端。求下一個開放地址的公式為: 3、隨機探查法 理想的探查函數應當在探查序列中隨機地從未訪問過的槽中選擇下一個位置,即探查序列應當是散列表位置的一個隨機排列。但是,我們實際上不能隨機地從探查序列中選擇一個位置,因為在檢索關鍵碼的時候不能建立起同樣的探查序列。然而,我們可以做一些類似於偽隨機探查( pseudo-random probing )的事情。在偽隨機探查中,探查序列中的第i個槽是(h(K) + ri) mod M,其中ri是1到M - 1之間數的“隨機”數序列。所有插入和檢索都使用相同的“隨機”數。探查函數將是 p(K,i) = perm[i - 1], 這裏perm是一個長度為M - 1的數組,它包含值從1到M – 1的隨機序列。 4、雙散列探查法 偽隨機探查和二次探查都能消除基本聚集——即基地址不同的關鍵碼,其探查序列的某些段重疊在一起——的問題。然而,如果兩個關鍵碼散列到同一個基地址,那麽采用這兩種方法還是得到同樣的探查序列,仍然會產生聚集。這是因為偽隨機探查和二次探查產生的探查序列只是基地址的函數,而不是原來關鍵碼值的函數。這個問題稱為二級聚集( secondary clustering )。 為了避免二級聚集,我們需要使得探查序列是原來關鍵碼值的函數,而不是基位置的函數。雙散列探查法利用第二個散列函數作為常數,每次跳過常數項,做線性探查。
四)散列的檢索效率分析 我們可以根據完成一次操作,即插入、刪除和檢索操作,所需要的記錄訪問次數來衡量散列方法的性能。由於散列表的插入和刪除操作都是基於檢索進行的:在刪除一條記錄之前必須先找到該記錄,因此刪除一條記錄之前需要的訪問數等於成功檢索到它需要的訪問數;而插入一條記錄時,必須找到探查序列的尾部(對於不考慮刪除的情況,是尾部的空槽;對於考慮刪除的情況,也要找到尾部,才能確定是否有重復記錄),這等於對這條記錄進行一次不成功的檢索。因此,散列表的效率實質上還是平均檢索長度,而且我們需要區別對待成功的檢索與不成功的檢索。 當散列表比較空的時候,所插入的記錄比較容易插入到其空閑的基地址。如果散列表中的記錄比較多,插入記錄時,很可能要靠沖突解決策略來尋找探查序列中合適的另一個槽。而且,檢索記錄時,很多時候需要沿著探查序列逐個查找。隨著散列表記錄不斷增加,越來越多的記錄有可能放到離其基地址更遠的地方。 根據這些討論,我們可以看到散列方法預期的代價與負載因子α= N/M有關。其中,M是散列表存儲空間大小,N是表中當前的記錄數目。 開散列方法的效率最好,實際系統中使用的散列大多都是開散列。開散列方法非常簡單、易於實現,它不會產生聚集現象(聚集導致更大的平均檢索長度),刪除也極為方便。大部分數據結構教材用比較多的篇幅來討論閉散列方法,是因為閉散列需要考慮的因素更多,因而更需要精心設計,閉散列在某些受限制的系統中(例如不能使用堆棧分配新空間)有獨到的用途。並且,經過精心設計的閉散列的效率比開散列穩定。

轉載:散列沖突的解決策略