1. 程式人生 > >資料結構與算法系列15(上)--散列表(雜湊表)

資料結構與算法系列15(上)--散列表(雜湊表)

什麼是散列表

散列表的英文是“Hash Table”,也叫“雜湊表”或者“Hash 表”。他是一種根據關鍵碼值(Key value)而直接進行訪問的資料結構。也就是說,它通過把關鍵碼值對映到表中一個位置來訪問記錄,以加快查詢的速度。這個對映函式叫做雜湊函式,存放記錄的陣列叫做散列表。

散列表的思想

散列表用的是陣列支援按照下標進行隨機訪問的時候,時間複雜度是O(1)的特性。所以散列表其實是陣列的一種擴充套件,可以說沒有陣列就沒有散列表。
我們通過雜湊函式把元素的鍵值對映為下標,然後將資料儲存在陣列對應下標的位置。當我們需要按照鍵值查詢元素時,我們用同樣的雜湊函式,將鍵值轉化為陣列下標,從對應的陣列下標的位置找元素。

什麼是雜湊函式

雜湊函式其實就是一個普通的函式,它的主要功能就是要將鍵值轉換為對應的下標,我們可以把它定義為hash(key),其中key表示元素的鍵值,hash(key)的值就是我們經過雜湊函式計算得到的下標(雜湊值)。

如何設計雜湊函式

1.雜湊函式計算得到的雜湊值是一個非負整數。
2.若key1=key2,則hash(key1)=hash(key2)。
3.若key≠key2,則hash(key1)≠hash(key2)。
正是由於第3點要求,所以產生了幾乎無法避免的雜湊衝突問題。

雜湊衝突的解放方法

1.開放定址法

思想:
如果出現雜湊衝突,我們就重新探測出一個空閒的位置,然後將其插入。這裡探測出是否有空閒位置的方法我們使用的是“線性探測法

”。
使用線性探測法插入資料:
當我們想要往散列表插入資料時,我們先通過雜湊函式計算出相應的儲存位置,如果該位置已經儲存有資料了,那我們就從當前位置開始,依次往後面查詢,看是否有空閒的位置,直到查詢到為止。
使用線性探測法查詢資料:
我們通過雜湊函式求出要查詢元素的鍵值對應的雜湊值,然後比較陣列中下標為雜湊值的元素,看是否相等,如果相等,說明就是我們要查詢的元素,如果不相等,就順序往後依次查詢。如果遍歷到陣列的空閒位置還沒有找到元素,就說明要查詢的元素沒有在散列表中。
使用線性探測法刪除資料:
為了不讓查詢演算法失效,可以讓要刪除的資料做一個特殊的標記deleted,當線性探測查詢的時候,遇到標記為deleted的空間,我們會繼續往下查詢而不是停下來。
線性探測法存在的問題

當散列表插入的資料越來越多時,空閒的位置就會越來越少,極端情況下,我們可能要探測完整個散列表,所以最壞情況下的時間複雜度為O(n)。

除了上面講的通過線性探測法來尋找一個空閒位置,還有另外兩種比較經典的探測方法,二次探測和雙重探測。
二次探測(Quadratic probing):
線性探測每次探測的步長為1,即在陣列中一個一個探測,而二次探測的步長變為原來的平方。
雙重雜湊(Double hashing):
使用一組雜湊函式,直到找到空閒位置為止。

不管採用哪種探測方法,當散列表中空閒位置不多的時候,雜湊衝突的概率就會大大提高。為了儘可能保證散列表的操作效率,一般情況下,我們會盡可能保證散列表中有一定比例的空閒槽位。我們用裝載因子(load factor)來表示空位的多少。
裝載因子的計算公式是:

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

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

2.連結串列法(更常用)

在散列表中,每一個“桶”,“槽”會對應一條連結串列,所有散列表相同的元素,我們都把他們放到相同槽位的對應連結串列中。
使用連結串列法插入資料:
我們只需要通過雜湊函式計算出對應的雜湊槽位, 將其插入到對應的連結串列中就可以。所以插入資料的時間複雜度是O(1)。
使用連結串列法查詢和刪除資料
我們同樣通過雜湊函式計算對應的雜湊槽位,然後遍歷連結串列查詢或者刪除。對於雜湊比較均勻的雜湊函式,連結串列的節點個數k=n/m,其中n表示散列表中資料的個數,m表示散列表中槽的個數,所以是時間複雜度為O(k)。