資料結構與演算法分析:雜湊表

來源:互聯網
上載者:User

以下是閱讀了《演算法導論》後,對雜湊表的一些總結:

雜湊表又叫散列表,是實現字典操作的一種有效資料結構。雜湊表的查詢效率極高,在沒有衝突(後面會介紹)的情況下可做到一次存取便能得到所查記錄,在理想情況下,尋找一個元素的平均時間為O(1)(最差情況下散列表中尋找一個元素的時間與鏈表中尋找的時間相同:O(n),但實際情況中一般散列表的效能是比較好的)。

雜湊表就是描述key—value對的映射問題的資料結構,更詳細的描述是:在記錄的儲存位置和它的關鍵字之間建立一個確定的對應關係h,使每個關鍵字與雜湊表中唯一一個儲存位置相對應。我們稱這個對應關係f為雜湊/散列函數,這個儲存結構即為雜湊/散列表。

一、直接定址表

當關鍵字的全域U比較小時,直接定址是一種簡單而有效技術,它的雜湊函數很簡單:f(key) = key,即關鍵字大小直接與元素所在的表位置序號相等。如果關鍵字不是整數,我們需要通過某種手段將其轉換為整數,比如可以將字元關鍵字轉化為其在字母表中的序號作為關鍵字。直接定址法的優點是不會出現兩個關鍵字對應到同一個地址的情況(即不會出現f(key1) = f(key2)的情況),因此不用處理衝突。但是,直接定址表也有著天然的局限性,即如果全域U很大,則在一台標準的電腦可用記憶體容量中,要儲存大小為U的一張表並不實際。

二、散列表

上面說到,在處理實際資料的時候,全域U往往會很大,則在一台標準的電腦可用記憶體容量中,要儲存大小為U的一張表也許不太實際,此時實際需要儲存的關鍵字集合K可能相對U來說很小,這時散列表需要的儲存空間要比直接表少很多。

散列表T通過散列函數f計算出關鍵字key在表中的位置,這些位置被稱為“槽”。散列函數f將關鍵字域U映射到散列表T[0...m-1]的槽位上。由於關鍵字的個數要大於槽的個數,這裡會出現一個問題:若干個關鍵字可能映射到了表的同一個位置處,我們稱這種情形為衝突。我們希望散列表在節省空間的同時,其效能要接近於O(1),因此需要盡量避免衝突,通常的策略是儘可能地設計更好的雜湊函數f,將關鍵字儘可能隨機地映射到散列表的每個位置上(為什麼說儘可能。因為這裡關鍵字的個數|U|肯定大於散列表的槽個數m,因此至少有兩個關鍵字被映射到同一個槽中,只依靠雜湊函數f是無法完全避免衝突的)。

三、雜湊函數

雜湊函數的構造方法很多,最好的情況是:對於關鍵字結合中的任一個關鍵字,經雜湊函數映射到地址集合中任何一個地址的機率相等,也就是說,關鍵字經過雜湊函數得到一個隨機的地址,以便使一組關鍵字的雜湊地址均勻分布在整個地址空間中,從而減少衝突。同樣,由於多數雜湊函數都是假定關鍵字的全域為自然數集N={0、1、2….},因此所給關鍵字如果不是自然數,就要先想辦法將其轉換為自然數。下面我們就來看常用的雜湊函數。

1.直接定址法

對應前面的直接定址表,關鍵字與雜湊表中的地址有著一一對應關係,因此不需要處理衝突。

2.除法散列法

雜湊函數:f(key) = key % m

函數對所給關鍵字key取餘,這裡 m 必須不能大於雜湊表的長度len,通常 m 可取一個不太接近2的整數次冪的素數

3.乘法散列法

用關鍵字key乘上A(0 < A < 1),取出其小數部分,然後用 m 乘以小數的值,再向下取整,該函數寫為:f(key) = floor(m * (key * A % 1))

其中,(key * A % 1)是取(key * A)的小數部分,該函數涉及到參數A的取值問題。Knuth認為,A = (sqrt(5)-1)/2 = 0.6180339877...是個比較理想的值,事實上,該點就是黃金分割點。

除法散列法乘法散列法是較為常用的雜湊函數設計方法,事實上方法還有很多種,如全域散列法摺疊法數字分析法等,更多的介紹請參考相關書籍。

四、衝突處理

前面提到,為了節省空間的,表中槽的數目小於關鍵字的數目,只是通過設計好的雜湊函數不可能完全避免衝突。下面介紹兩種解決衝突的方法:連結法開放定址法

1.連結法(chaining)

連結法的思路很簡單:如果多個關鍵字映射到了雜湊表的同一個位置處(將這些關鍵字稱為同義字),則將這些同義字記錄在同一個線性鏈表中,該槽有一個指標,它指向儲存所有散列到該槽的元素的鏈表表頭,如下圖所示:

圖中,關鍵字k1和k4映射到了雜湊表的同一個位置處,k5、k2、k7映射到了雜湊表的同一個位置處。《演算法導論》提到,為了更快地刪除某個元素,可以將鏈表設計為雙向鏈表。如果表是單連結的,則為了刪除元素x,首先必須在表T[h(x,key)]中找到元素x,然後通過更改x的前去元素的next屬性,把x從鏈表中刪除。在單鏈表的情況下,刪除和尋找操作的漸近已耗用時間相同。另一個例子如下:

2.開放定址法(open addressing)

在開放定址法中,所有的元素都存放在散列表中,也即是說,每個表項或包含動態集合的一個元素,或為空白,不再使用鏈表。雜湊表中的槽t不僅向雜湊函數值等於t的同義字開放,而且向雜湊函數值不等於t的記錄開放,允許以“搶佔”的方式爭取雜湊地址。

該方法採用如下公式記性再散列:

F(key,i) = (f(key) + di) % len

其中,f(key)為雜湊函數,len為雜湊表長,di為增量序列,它可能有如下三種情況:

di = 1,2,3...m-1

di = 1,-1,4,-4,9,-9...k^2,-k^2

di為偽隨機序列

採用第一種序列的叫做線性探測再散列,採用第二種序列的叫做二次探測再散列,採用第三種序列的叫做隨機探測再散列。說白了,就是在發生衝突時,將關鍵字應該放入的位置向前或向後移動若干位置,比如採取第一種序列時,如果遇到衝突,就向後移動一個位置來檢測,如果還發生衝突,繼續向後移動,直到遇到一個空槽,則將該關鍵字插入到該位置處。

線性探測比較容易實現,但是它存在一個問題,稱為一次群集。隨著連續被佔用的槽不斷增加,平均尋找時間也隨之不斷增加,群集現象很容易出現,這是因為當一個空槽前有i個滿槽時,該空槽為下一個將被佔用的機率為(di+1)len。

同樣採用二次探測的方法,會產生二次群集,因為每次遇到衝突時,尋找插入位置時都是在跳躍性前進或後退,因此這個相對於一次群集來說,比較輕度。

五、一些例子

已知關鍵字序列:26,36,41,38,44,15,68,12,06,51,25。

用除法散列法構造雜湊函數,線性探測再散列法解決衝突。現在需要:①建雜湊表;②求尋找成功和失敗的平均搜尋長度(ASL)。

解法:題中關鍵字個數n = 11,設裝載因子α = 0.75,m = n / α,取m為素數13。雜湊函數:f(key) = key % 13,以下圖片給出了建雜湊表的步驟:

雜湊表的尋找效能分析:

六、代碼實現

下面用代碼實現一個雜湊表,這裡採用連結法來處理衝突,描述資料結構的標頭檔的代碼如下:

#define M 7     //雜湊函數中的除數,必須小於等於表長typedef int ElemType;// 該雜湊表採用連結法解決衝突問題typedef struct Node {   //Node為鏈表節點的資料結構    ElemType data;    struct Node *next;}Node,*pNode;// 雜湊表每個槽的資料結構typedef struct HashNode{       pNode first;    // first指向鏈表的第一個節點}HashNode,*HashTable;// 建立雜湊表HashTable create_HashTable(int);// 在雜湊表中尋找資料pNode search_HashTable(HashTable, ElemType);// 插入資料到雜湊表bool insert_HashTable(HashTable,ElemType);// 從雜湊表中刪除資料bool delete_HashTable(HashTable,ElemType);// 銷毀雜湊表void destroy_HashTable(HashTable,int);

首先建立一個空雜湊表,而然後執行插入、刪除、查詢等操作,最後銷毀雜湊表,雜湊表的實現代碼如下:

#include<stdio.h>#include<stdlib.h>#include "data_structure.h"// 建立n個槽的雜湊表HashTable create_HashTable(int n){    int i;    HashTable hashtable = (HashTable)malloc(n*sizeof(HashNode));    if(!hashtable)    {        printf("hashtable malloc faild,program exit...");        exit(-1);    }    // 將雜湊表置空    for(i=0;i<n;i++)        hashtable[i].first = NULL;    return hashtable;}// 在雜湊表中尋找資料,雜湊函數為H(key)=key % M// 尋找成功則返回在鏈表中的位置// 尋找不成功則返回NULLpNode search_HashTable(HashTable hashtable, ElemType data){    if(!hashtable)        return NULL;    pNode pCur = hashtable[data%M].first;    while(pCur && pCur->data != data)        pCur = pCur->next;    return pCur;}// 向雜湊表中插入資料,雜湊函數為H(key)=key%M// 如果data已存在,則返回fasle// 否則,插入對應鏈表的最後並返回truebool insert_HashTable(HashTable hashtable,ElemType data){    // 如果已經存在,返回false    if(search_HashTable(hashtable,data))        return false;    // 否則為插入資料分配空間    pNode pNew = (pNode)malloc(sizeof(Node));    if(!pNew)    {        printf("pNew malloc faild,program exit...");        exit(-1);    }    pNew->data = data;    pNew->next = NULL;    // 將節點插入到對應鏈表的最後    pNode pCur = hashtable[data%M].first;    if(!pCur)   // 插入位置為鏈表第一個節點的情況        hashtable[data%M].first = pNew;    else    // 插入位置不是鏈表第一個節點的情況    {   // 只有用pCur->next才可以將pNew節點連到鏈表上,        // 用pCur連不到鏈表上,而是連到了pCur上        // pCur雖然最終指向鏈表中的某個節點,但是它並不在鏈表中        while(pCur->next)            pCur = pCur->next;        pCur->next = pNew;    }    return true;}// 從雜湊表中刪除資料,雜湊函數為H(key)=key % M// 如果data不存在,則返回fasle,// 否則,刪除並返回truebool delete_HashTable(HashTable hashtable,ElemType data){    // 如果沒尋找到,返回false    if(!search_HashTable(hashtable,data))        return false;    // 否則,刪除資料    pNode pCur = hashtable[data%M].first;    pNode pPre = pCur;  // 被刪節點的前一個節點,初始值與pCur相同    if(pCur->data == data)  // 被刪節點是鏈表的第一個節點的情況        hashtable[data%M].first = pCur->next;    else    {   // 被刪節點不是第一個節點的情況        while(pCur && pCur->data != data)        {            pPre = pCur;            pCur = pCur->next;        }        pPre->next = pCur->next;    }    free(pCur);    pCur = 0;    return true;}// 銷毀槽數為n的雜湊表void destroy_HashTable(HashTable hashtable,int n){    int i;    // 先逐個鏈表釋放    for(i=0;i<n;i++)    {        pNode pCur = hashtable[i].first;        pNode pDel = NULL;        while(pCur)        {            pDel = pCur;            pCur = pCur->next;            free(pDel);            pDel = 0;        }    }    // 最後釋放雜湊表    free(hashtable);    hashtable = 0;}

最後測試代碼:

#include<stdio.h>#include "data_structure.h"int main(){    int len = 15;   // 雜湊表長,亦即表中槽的數目    printf("We set the length of hashtable %d\n",len);    //建立雜湊表並插入資料    HashTable hashtable = create_HashTable(len);    if(insert_HashTable(hashtable,1))        printf("insert 1 success\n");    else         printf("insert 1 fail,it is already existed in the hashtable\n");    if(insert_HashTable(hashtable,8))        printf("insert 8 success\n");    else         printf("insert 8 fail,it is already existed in the hashtable\n");    if(insert_HashTable(hashtable,3))        printf("insert 3 success\n");    else         printf("insert 3 fail,it is already existed in the hashtable\n");    if(insert_HashTable(hashtable,10))        printf("insert 10 success\n");    else         printf("insert 10 fail,it is already existed in the hashtable\n");    if(insert_HashTable(hashtable,8))        printf("insert 8 success\n");    else         printf("insert 8 fail,it is already existed in the hashtable\n");    //尋找資料    pNode pFind1 = search_HashTable(hashtable,10);    if(pFind1)        printf("find %d in the hashtable\n",pFind1->data);          else         printf("not find 10 in the hashtable\n");    pNode pFind2 = search_HashTable(hashtable,4);    if(pFind2)        printf("find %d in the hashtable\n",pFind2->data);          else         printf("not find 4 in the hashtable\n");    //刪除資料    if(delete_HashTable(hashtable,1))        printf("delete 1 success\n");    else         printf("delete 1 fail");    pNode pFind3 = search_HashTable(hashtable,1);    if(pFind3)        printf("find %d in the hashtable\n",pFind3->data);          else         printf("not find 1 in the hashtable,it has been deleted\n");    // 銷毀雜湊表    destroy_HashTable(hashtable,len);    return 0;}

七、參考資料

《演算法導論》 第三版 (美)科曼(Cormen,T.H.) 等著,殷建平 等譯
http://blog.csdn.net/ns_code

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.