哈希表及其常用算法(代碼實例)
<hash表的特性>
Hash 表是使用 O(1) 時間進行數據的插入刪除和查找,但是 hash 表不保證表中數據的有序性,這樣在 hash 表中查找最大數據或者最小數據的時間是 O(N) 。
<尋址和 hash 函數>
理想狀態下 hash 足夠大,每一數據保存在一個 hash 存儲單元內,這樣對於插入刪除和查找某一個數據就可以直接得到。但是現實情況下 hash 表不可能無限大,而且理論上保存的數據的個數是沒有限制的,這樣保存的數據的數量就遠遠大於 hash 表的存儲單元的數量。
為了實現在 O(1) 內對數據進行插入刪除和查找,就必須將一個數據映射到 hash 表中的固定位置,這個映射函數就是 hash 函數。 Hash 函數通過對數據進行計算得到一個在 hash 表中的位置地址。
圖 1.1 理想的 hash 表
要選擇較好的 hash 函數,以及 hash 表存儲單元的數量,這樣才能使保存在 hash 表中的數據均勻分布。理想狀態不太可能實現,由於存儲的數據數量遠遠大於 hash 表存儲單元的數量,所以再好的 hash 函數也可能使不同的數據得到相同的映射位置,這就造成了沖突。但是好的 hash 函數可以將這種沖突降到最低。
<分離鏈接>
解決這種沖突的第一種方法是借助鏈表來實現,就是將數據實際存放在與 hash 表存儲單元相鏈接的鏈表中,而不是 hash 的存儲單元中。
圖 2.1 分離鏈表
當產生沖突的時候,將兩個數據都鏈接在同一 hash 存儲單元保存的鏈表中。當一個存儲單元保存的鏈表中有多個數據的時候,對於鏈表後面的數據的查找添加和刪除就是不是嚴格意義上的 O(1) 了。一個好的 hash 函數可以使得這個鏈表很短。最壞情況下,當所有的數據都保存在一個 hash 單元指定的鏈表中的時候,那麽這個 hash 就和鏈表一樣了。
<開放地址>
使用開放地址方法解決沖突的時候,數據仍然保存在 hash 表的存儲單元中,但是當沖突發生的時候,要再次計算新的地址。
常用的開放地址法是線性探查,就是當對一個數據進行插入刪除或者查找的時候,通過 hash 函數計算,發現這個位置不是要找的數據,這時候就檢查下一個存儲單元,一直找到要操作的數據為止。
除了線性探查外還有二次探查,再 hash 等等方法,都是當一次計算得到的位置不是要找到的數據的時候,怎樣再次確定新的位置。
<完全 hash 表>
采用分離鏈表的方式解決沖突的時候,當多個數據被映射到一個地址的時候,它們就形成了一個鏈表,要操作這其中的一個數據,那麽就必須在這個鏈表中進行操作。如果 hash 函數選擇的好的話,鏈表會很短,這樣的操作近似 O(1) ,但並不是精確的等於 O(1) ,它與鏈表的長度有關。對於數據的訪問的最壞情況的訪問也是 O(1) 的 hash 叫做完全 hash 表。
這樣的 hash 表是一個兩級的 hash 表,第一級的 hash 與使用分離鏈接方法的 hash 一樣,但是 hash 存儲單元中指向的不是一個鏈表,而是另一個 hash 表。
圖 4.1 完全 hash 表
要小心的選擇一級以及二級的 hash 函數可以完全保證在二級 hash 表中不會出現沖突。
?常用算法
?直接定址法 :地址集合 和 關鍵字集合大小相同
?數字分析法 :根據需要hash的 關鍵字的特點選擇合適hash算法,盡量尋找每個關鍵字的 不同點
?平方取中法:取關鍵字平方之後的中間極為作為哈希地址,一個數平方之後中間幾位數字與數的每一位都相關,取得位數由表長決定。比如:表長為512,=2^9,可以取平方之後中間9位二進制數作為哈希地址。
?折疊法:關鍵字位數很多,而且關鍵字中每一位上的數字分布大致均勻的時候,可以采用折疊法得到哈希地址,除留取余法除P取余,可以選P為質數,或者不含有小於20的質因子的合數
?隨機數法:通常關鍵字不等的時候采用此法構造哈希函數較恰當。
?實際工作中需要視不同的情況采用不同的hash函數
?考慮因素:計算哈希函數所需要的時間,硬件指令等因素。
?關鍵字長度
?哈希表大小
?關鍵字分布情況
?記錄查找的頻率。(huffeman樹)
?具體代碼如下
#include<stdlib.h> #include<math.h> struct HashTable; struct ListNote; typedef struct HashTable *HashTbl; typedef struct ListNote *Position; typedef Position List; int Hash(int key,int tablesize); int NextPrime(int x); HashTbl InitalizeTable(int TableSize); void DestroyTable(HashTbl H); Position Find(int key,HashTbl H); void Insert(int key, HashTbl H); void Delete(int key,HashTbl H); struct HashTable{ int TableSize; Position *TheList; }; struct ListNote{ int element; Position next; }; int Hash(int key,int tablesize){ return key%tablesize; } int NextPrime(int x){ int flag; while(1){ flag = 0; int i; int n = sqrt((float)x); for(i = 2 ;i <= n;i++){ if(x % i == 0){ flag = 1; break; } } if(flag == 0) return x; else x++; } } HashTbl InitalizeTable(int TableSize){ if(TableSize <= 0){ printf("散列大小有問題\n"); return NULL; } HashTbl table = (HashTbl)malloc(sizeof(struct HashTable)); if(table == NULL) printf("分配失敗"); table->TableSize = NextPrime(TableSize); table->TheList = (Position*)malloc(sizeof(List) * table->TableSize); if(table->TheList == NULL) printf("分配失敗"); table->TheList[0] = (Position)malloc(table->TableSize*sizeof(struct ListNote)); if(table->TheList == NULL) printf("分配失敗"); int i; for(i = 0;i < table->TableSize;i++){ table->TheList[i] = table->TheList[0] + i; table->TheList[i]->next = NULL; } return table; } Position Find(int key,HashTbl H){ Position p; List L = H->TheList[Hash(key,H->TableSize)]; p = L->next; while(p != NULL && p->element != key) p = p->next; if(p == NULL) return L; else return p; } void Insert(int key,HashTbl H){ Position p,NewCell; p = Find(key,H); if(p->element != key){ NewCell = (Position)malloc(sizeof(struct ListNote)); if(NewCell == NULL) printf("分配失敗"); else{ p = H->TheList[Hash(key,H->TableSize)]; NewCell->next = p->next; p->next = NewCell; NewCell->element = key; } } else printf("已經存在該值了\n"); } void Delete(int key,HashTbl H){ Position p ,NewCell; p = Find(key,H); if(p->element == key){ NewCell = H->TheList[Hash(key,H->TableSize)]; while(NewCell->next != p) NewCell = NewCell->next; NewCell->next = p->next; free(p); } else printf("沒有該值"); } int main(){ HashTbl table = InitalizeTable(10); Position p = NULL; p = Find(10,table); printf("%d\n",p->element); Insert(55,table); Insert(90,table); Insert(35,table); Insert(33,table); p = Find(55,table); printf("%d\n",p->element); p = Find(33,table); printf("%d\n",p->element); Delete(33,table); Delete(44,table); system( "pause" ); return 0 ; }
<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">
哈希表及其常用算法(代碼實例)