HashTable淺析


本文轉載自: http://rock3.info/blog/2013/12/05/hashtable%E6%B5%85%E6%9E%90/

一、Hash特點

        Hash,就是雜湊算法,Hash(str1)=str2,具備四種特性:

  • 長變短:Hash算法可以將任意長度的數據Hash成固定長度的數據。
  • 速度快:Hash算法基本上是異或和位移操作,速度很快。
  • 不可逆:由hash結果找到hash前的字符串是困難的。
  • 低碰撞:存在這樣的情況,Hash前輸入不同,Hash后輸出相同,但絕大多數情況是輸入不同,輸出不同。

 二、HashTable

       據《算法導論》上講:“很多應用中,都需要一種動態的集合結構,它僅僅支持INSERT、SEARCH和DELETE字典操作...實現字典操作的一種有效的數據結構為散列表(Hash Table)...在散列表中,查找一個元素的時間與在鏈表中查找一個元素的時間相同,最壞情況下都是O(n)...散列表是普通數組概念的推廣...”。也就是說,Hash Table是為了解決動態的插入、搜索、刪除等操作,而專門設置的一種數據結構,目的為了降低這些操作的時間復雜度。

        這里面有個“字典操作”,“字典”模型時這樣的:通過某個關鍵字,能夠查到該關鍵字相關的信息,比如通過身份證號,可以查到姓名、性別、年齡、婚否等信息。這樣,就抽象出兩個關鍵的部分key和value,身份證號碼——key,姓名、性別、年齡、婚否——value。考慮普通的數據存儲方式——數組和鏈表,來存儲key和value,先假設指有100個元素,對於數組通常這樣存:

struct person {

char key[128];

struct value v;

}p[100];

       對於鏈表這樣存:

struct person_list{

struct person_list *next;

char key[128];

struct value v;

};

        array1

        下文中,將上圖中的1,2,3,4,5,6這樣的數組節點稱為“數組節點“,而對p1,p4,p9,p12,p13,p15這種具體的代表key和value的結構體鏈表節點,稱為Entry。

        對於INSERT、SEARCH和DELETE,以及存儲空間的影響(假設key兩兩不同,插入、搜索、刪除均以key為對象):

        為了平衡數組不易插入,鏈表、數組均不易索引的問題,很容易想到將key值轉化為數組的下標、建立一個映射(將字符串轉化為整數,或者直接就是整數,先這么簡單理解,這也可能產生碰撞),然后再通過指針的方式指向具體的元素,即能快速查找,又能方便插入、刪除:

array

        這實際上已經有了Hash Table的原型:“橫向數組,縱向鏈表”,key到下標的映射就是Hash Table中的hash。下面的樣子顯得更“好看”一些,這就是一種常見的Hash Table實現:

hashtable

        橫向數組的下標為key的hash值,縱向鏈表為hash值相同的元素組成的鏈表(這里就是舉個例子,hash不會產生非常多的碰撞,hash值也的長度也是較長的)。這樣做有如下好出:

  • 由於hash計算速度快,對於任意一個key,可以快速的找到其所屬的鏈表(O(n),n為hash數組的長度)。
  • 在鏈表里進行插入、刪除操作,比較方便(O(a),取決於鏈表中元素的個數a)。
  • 整體上可以將查找的時間復雜度從原來的O(x)降低到O(1+a),其中x為原來Entry的條目數量,a=x/n,也即平均每個鏈表元素的個數,也叫裝載因子。

        但是畢竟由於碰撞的存再,使得搜索的時間復雜度沒能夠達到O(1)。Hash Table的一個關鍵工作就是盡量的降低碰撞。

四、“鏈接法”和“開放尋址法”

        上面一個圖畫得就是“鏈接法”:對於碰撞,選擇在相同的數組節點上建立鏈接。還有一中方法來處理“碰撞”——“開放尋址法”(時間復雜度更低):每個數組節點上就一個元素,如果待插入的Entry計算出來的hash值所在的數組節點非空閑(已經有一個Enry了),就采取某種方法再選擇一個空閑的數組節點,插入該Enry。這種情況下,整個數組必須支持動態擴容:當數組空閑節點低於一個閥值時,將擴展數組容量為原來的一倍。這個閥值通常是0.72,也即數組節點有72%的比例為非空閑,就需要將數組擴容至原來的2倍。

        “開放尋址法”中如何找到下一個空閑數組,有以下幾種方法但並不局限於以下方法,這里就不展開了:

  • 線形探查
  • 二次探查
  • 雙重散列(較好)

五、HashTable的搜索復雜度

        HashTable平衡了查找速度、插入速度,但是某些情況下,碰撞是不可避免的,只要有碰撞存在,就無法使搜索的時間復雜度達到O(1)。

        對於“鏈接法”,搜索的時間復雜度為O(1+a),a為轉載因子。而對於“開放尋址法”,因為每個數組節點上就1個Entry,基本上能夠達到O(1)。

        還有一種“完全散列”最好,能夠使得最壞的情況時間復雜度仍然為O(1),這里就不討論了。

       因此,如果簡單的用一下hash table,可以采用“鏈接法”,如果追求速度,或者數據量比較大,應該采用“完全散列”或者“開放尋址法”(我猜的,沒驗證)。

六、HashTable的應用

        估計,HashTable肯定會應用到數據庫的實現中,數據庫是典型的字典模型。另外HashTable在Linux內核中也有應用,很多場景均使用了hash table(hlist),如tasklet、頁表維護等,其類型定義於include/linux/types.h:

1
2
3
4
5
6
7
struct hlist_head {
     struct hlist_node *first;
};
 
struct hlist_node {
     struct hlist_node *next, **pprev;
};

        Linux內核中的hlist采用的是”鏈接法“,此處不展開了。

七、總結

        java里有HashMap這個詞,看了一些文章,感覺與《算法導論》上說的Hash Table沒有太大的區別,只不過是java的一種實現而已,java里面也有一種叫做HashTable的,是散列表的不同實現。

        HashTable是根據Hash的特點去解決這種問題:海量數據的索引、插入、刪除時,可以先hash一下,將海量數據進行分塊,然后再進行搜索、插入、刪除等操作,以便降低時間復雜度。在最好的情況下,能夠將時間復雜度降低到O(1)。

        HashTable的“橫向數組、縱向鏈表”的樣子,可以以“鏈接法”解決時間復雜度的問題,也可以以“開放尋值法”解決時間復雜度的問題,后者每個數組節點上就一個元素,hash后在相應的數組節點位置順序向后查找,在找到的第一個位置插入該元素,這種情況下,整個數組必須支持動態擴容:當數組空閑節點低於一個閥值時,將擴展數組容量為原來的一倍。這個閥值通常是0.72。

        實際上Hash以后可以接多種數據結構,HashTable就是接的鏈表,如果銜接樹也是可以的,就是HashTree。

 

存疑:

1、我列的hash特點是傳統的md5、sha1等hash算法的特點,hash table中用的hash算法比md5、sha1要簡單很多,可能有一定的區別。

2、感覺對hash  table的理解還是不太到位,《算法導論》上比較關注hash的構造方法,怎么才能構造出好的hash,但在我理解,hash就是一個使用O(1)時間完成長變短的操作,至於分布是不是均勻,那就看設計了,設計的好一些可能更均勻吧(還得看輸出,不僅僅是hash算法本身),但不好也就哪樣了。但是《算法導論》上認為分布是否均勻與查找的時間復雜度密切相關,而且應該盡量避免碰撞的情況,並給出了幾種處理碰撞的方法。

3、關於hash的設計的方法和技巧,如何能夠更加均勻分布,如何盡量快,如何碰撞少,不列了,可能里面涉及比較多的數學知識。


免責聲明!

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



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