數據結構—散列表(哈希表)

一、數組和鏈表的弊端

1、對於所有可能的關鍵字集合U,如果用數組來存儲U中的關鍵字,則需要分配的存儲單元至少等於U中關鍵字的個數。這種方法的弊端很明顯,實際中U可能很大,計算機的內存畢竟有限,用數組去存儲這麼大一個集合不切實際

2、對於鏈表來說也是同樣的道理,對每個關鍵字需要分配一個節點來存儲,這樣空間上就會受到限制

3、還有,實際出現的關鍵字集合K有可能遠遠小於U,這樣用數組鏈表來存儲整個全集U,就會有大部分元素(節點)沒有被使用到,那麼這些空間就是嚴重的浪費

4、最後,數組和鏈表在查找插入刪除操作上各有不足。

               比如,數組的查找效率非常高,時間複雜度爲O(1),而它的插入和刪除操作就非常麻煩

               鏈表剛好相反,插入刪除方便,查找則需要O(N)的時間複雜度

 

二、散列表的思想

        把全集U的每一個關鍵字通過一個散列函數(Hash Function)h映射到表T{0, 1, ... , m-1}的某個下標,表T{0, 1, ... , m-1}可以簡單的理解爲有m個元素的數組               h:U—>{0, 1, ... , m-1} 

                                            

我們用散列表的一個關鍵目的就是爲了節省存儲空間,所以一般情況下m要遠遠小於|U|。

這裏就有一個問題,兩個不同的關鍵字通過h計算可能會得到同一個哈希值(函數值),例如上圖的k2 != k5,但h(k2) == h(k5),我們稱這種情形爲衝突或碰撞(collision)。

我們選擇散列函數h的時候,要儘可能地減少衝突次數。要做到這一點,就需要散列函數h儘可能的"隨機",即函數值分佈儘可能均勻,不要集中在某個區間。

同時也要明白,把一個大集合U硬塞到一個小數組T{0, 1, ... , m-1},是不可能避免衝突的。

那麼,解決衝突主要有兩種方法,一是鏈接法,二是開放地址法,這裏着重介紹第一種。

 

三、通過鏈接法解決衝突

在鏈接法中,我們把映射到同一下標的關鍵字存儲在一個鏈表中,數組中保存指向該鏈表的指針(有可能爲空)。

                   

裝載因子α=N / m,N爲所有關鍵字的個數,m爲數組的元素個數。α表示的是每個鏈表中平均存儲的元素個數。

顯然,鏈接法的最壞情形是所有關鍵字都映射到一個下標上,這樣所有關鍵字都被存儲到一個鏈表中,其性能退化爲和鏈表,還得加上散列函數h的開銷。

所以,鏈接法的性能依賴於散列函數的選擇,我們要選擇儘量使關鍵字均勻分佈在數組的各個元素上的散列函數。

 

四、散列函數

1、除法散列法

     通過取k除以m的餘數,將關鍵字k映射到含有m個元素的數組的一個下標上。

                    h(k) = k % m

     一個不太接近2的整數冪的素數,通常是m的一個較好的選擇

2、乘法散列法

     構造散列函數的乘法散列法包含兩個步驟:

     ①用關鍵字k乘上常數A(0<A<1),並提取kA的小數部分

     ②用m乘以這個小數部分,再向下取整

                    h(k) = floor(kA % 1)

        A = (√5 -1) / 2 ≈ 0.618是個比較理想的值

開放地址法是

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章