散列沖突解決的方式


一、散列思想

       散列表的英文叫Hash Table,也叫哈希表或者Hash表。散列表用的是數組支持按照下標隨機訪問數據的特性,所以散列表其實就是數組的一種擴展,由數組演化而來。可以說,如果沒有數組,就沒有散列表。

       散列表時間復雜度是O(1)的特性。我們通過散列函數把元素的鍵值映射為下標,然后將數據存儲在數組中對應下標的位置。當我們按照鍵值查詢元素時,我們用同樣的散列函數,將鍵值轉化為數組下標,從對應的數組下標的位置取數據。

二、散列函數

       散列函數在散列表中起着非常關鍵的作用。散列函數,顧名思義,它是一個函數。可以把它定義成hash(key),其中key表示元素的鍵值,hash(key)的值表示經過散列函數計算得到的散列值。

       如何改造散列函數?散列函數設計的基本要求:

       1、散列函數計算得到的散列值是一個非負整數;

       2、如果key1=key2,那hash(key1)==hash(key2);

       3、如果key1≠key2,那hash(key1)≠hash(key2)

       解釋一下上述三點:其中,第一點理解起來應該沒有任何問題。因為數組下標是從0開始的,所以散列函數生成的散列值也要是非負整數。第二點也很好理解。相同的key,經過散列函數得到的散列值也應該是相同的。

       第三點看起來合情合理,但是在真實的情況下,要想找到一個不同key對應的散列值都不一樣的散列函數,幾乎是不可能的。即使像業界著名的MD5、SHA、CRC等哈希算法,也無法完全避免這種散列沖突。而且,因為數組的存儲空間有限,也會加大散列沖突的概率。

三、散列沖突

       再好的散列函數也無法避免散列沖突。如何解決散列沖突呢?常用的散列沖突解決方法有兩類:開放尋址法(open addressing)和鏈表法(chaining)

       1、開放尋址法

        開放尋址法的核心思想是,如果出現了散列沖突,我們就重新探測一個空閑位置,將其插入。那如何重新探測新的位置呢?先講一個比較簡單的探測方法,線性探測(Linear Probing)。

        線性探測:當我們往散列表中插入數據時,如果某個數據經過散列函數散列之后,存儲位置已經被占用了,我們就就從當前位置開始,依次往后查找,看是否有空閑位置,直到找到為止。

  從圖中可知,散列表大小為10,在元素x插入散列表之前,已經6個元素插入到散列表中。x經過Hash算法之后,被散列到下標為7的位置,但是這個位置已經有數據了,所以就產生了沖突。於是就順序往后一個一個找,遍歷到尾部都沒有找到空閑的位置,於是從表頭開始找,直到找到空閑位置2,於是將其插入到這個位置。

       線性探測法:當散列表中插入的數據越來越多時,散列沖突發生的可能性就會越來越大,空閑位置會越來越少,線性探測的時間就會越來越久。極端情況下,我們可能需要探測整個散列表,因此最壞時間復雜度為O(n)。

       對於開發尋址法,除了線性探測方法之外,還有另外兩種比較經典的探測方法,二分探測(Quadratic probing)和雙重散列(Double hashing)。

       不管采用哪種探測方法,當散列表中空間位置不多的時候,散列沖突的概率就會大大提高。一般情況下,為了盡可能保證散列表的操作效率,一般情況下,我們會盡可能保證散列表中有一定比例的空閑槽位。用裝載因子來表示空位的多少。

       裝載因子的計算公式是:

散列表的裝載因子 = 填入表中的元素個數 / 散列表的長度

       裝載因子越大,說明空閑位置越少,沖突越多,散列表的性能會下降。

  列表跟數組一樣,不僅支持插入、查找操作,還支持刪除操作。對於使用線性探測法解決沖突的散列表,刪除操作稍微有些特別。我們不能單純地把要刪除的元素設置為空。在查找的時候,一旦我們通過線性探測方法,找到一個空閑位置,我們就可以認定散列表中不

存在這個數據。但是,如果這個空閑位置是我們后來刪除的,就會導致原來的查找算法失效。本來存在的數據,會被認定為不存在。這個問題如何解決呢?

我們可以將刪除的元素,特殊標記為 deleted。當線性探測查找的時候,遇到標記為deleted 的空間,並不是停下來,而是繼續往下探測。

      2、鏈表法

       鏈表法是一種更加常用的散列沖突解決辦法,相比開放尋址法,它要簡單很多。如下圖,在散列表中,每個桶(bucket)或者槽位(slot)會對應一條鏈表,所有散列值相同的元素都放到相同的操作對應的鏈表中。

  插入的時候,只需要通過散列函數計算出對應的散列槽位,將其插入到對應鏈表中即可,所以插入的時間復雜度是O(1)。當查找一個元素時,我們同樣通過散列函數計算出對應的槽,然后遍歷鏈表查找。那查找的時間復雜度是多少呢?

       實際上,查找的時間復雜度跟鏈表的長度k成正比,也就是O(k)。對於散列比較均勻的散列函數來說,理論上,k=n/m,其中n表示散列中數據的個數,m表示散列表中“槽”的個數。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM