【資料結構與演算法】HashTable相關操作實現(附完整原始碼)
轉載請註明出處:http://blog.csdn.net/ns_code/article/details/20763801
前言
學過Java的人肯定對Hash這個詞非常之熟悉,HashTable、HashSet、HashMap等都是對雜湊表的封裝或改進。這次我們來看下雜湊表用C語言表示的封裝實現。
雜湊表
雜湊表又叫雜湊表,是實現字典操作的一種有效資料結構。雜湊表的查詢效率極高,在沒有衝突(後面會介紹)的情況下不需經過任何比較,一次存取便能得到所查記錄,因此理想情況下,查詢一個元素的平均時間為O(1)。
雜湊表就是描述key—value對的對映問題的資料結構,這在Java中大家都知道,更詳細的描述是:在記錄的儲存位置和它的關鍵字之間建立一個確定的對應關係f,使每個關鍵字與雜湊表中唯一一個儲存位置相對應。我們稱這個對應關係f為雜湊函式,這個儲存結構即為雜湊表。
直接定址表
當關鍵字的全域U比較小時,直接定址是一種簡單而有效的技術,它的雜湊函式很簡單:f(key) = key,即關鍵字大小直接與元素所在的位置序號相等。另外,如果關鍵字不是自然數,我們需要通過某種手段將其轉換為自然數,比如可以將字元關鍵字轉化為其在字母表中的序號作為關鍵字。直接定址法不會出現兩個關鍵字對應到同一個地址的情況,既不會出現f(key1) = f(key2)的情況,因此不用處理衝突,這便是其優點所在。
雜湊表
直接定址的缺點非常明顯,如果全域U很大,則在一臺標準的計算機可用記憶體容量中,要儲存大小為U的一張表也許不太實際,而且,實際需要儲存的關鍵字集合K可能相對U來說很小,這時雜湊表需要的儲存空間要比直接表少很多。雜湊表通過雜湊函式f計算出關鍵字key在槽的位置。雜湊函式f將關鍵字域U對映到雜湊表T[0...m-1]的槽位上。但是這裡會存在一個問題:若干個關鍵字可能對映到了表的同一個位置處(演算法導論上名其曰“槽”),我們稱這種情形為衝突。當然理想的方法是儘量避免衝突,我們可以儘可能第將關鍵字通過f隨即地對映到雜湊表的每個位置上。
雜湊函式
雜湊函式的構造方法很多,最好的情況是:對於關鍵字結合中的任一個關鍵字,經雜湊函式對映到地址集合中任何一個地址的概率相等,也就是說,關鍵字經過雜湊函式得到一個隨機的地址,以便使一組關鍵字的雜湊地址均勻分佈在整個地址空間中,從而減少衝突。同樣,由於多數雜湊函式都是假定關鍵字的全域為自然數集N={0、1、2....},因此所給關鍵字如果不是自然數,就要先想辦法將其轉換為自然數。下面我們就來看常用的雜湊函式。
直接定址法
對應前面的直接定址表,關鍵字與雜湊表中的地址有著一一對應關係,因此不需要處理衝突。
除法雜湊法
雜湊函式如下:
f(key)= key%m
即對所給關鍵字key取餘,這裡m必須不能大於雜湊表的長度len,通常m取一個不太接近2的整數次冪的素數是一個較好的選擇。
乘法雜湊法
用關鍵字key先乘上A(0<A<1),取出其小數部分,然後用m乘以這個值,再向下取整,該雜湊函式為:
f(key)= floor(m*(key*A%1))
通常,A=(sqrt(5)-1)/2 = 0.6180339877...(黃金分割點)是個比較理想的值。
其他還有一些,諸如數字分析法、摺疊法、全域雜湊法等,這裡不再一一介紹,有興趣瞭解的可以參考相關書籍(其實我們一般用的比較多的可能也就是除法雜湊法和乘法雜湊法)。
衝突處理
但我們前面提到,為了節省空間,表中槽的數目應該是小於關鍵字的數目的,因此完全避免衝突是不可能的。下面介紹兩種解決衝突的方法:連結法和開放定址法。
連結法
連結法的思路很簡單:如果多個關鍵字對映到了雜湊表的同一個位置處,則將這些關鍵字記錄在同一個線性連結串列中,掛在該位置處,如下圖所示:
圖中,關鍵字k1和k4對映到了雜湊表的同一個位置處,k5、k2和k7對映到了雜湊表的同一個位置處。另外,為了更快地刪除某個元素,可以將連結串列設計為雙向連結串列。後面的程式碼中我們採用的是單向連結串列。
開放定址法
在開放定址法中,所有的元素都存放在雜湊表中,也即是說,每個表項或包含動態集合的一個元素,或為空。該方法採用如下公式記性再雜湊:
F(key,i) = (f(key) + i)%len
其中,f(key)為雜湊函式,len為雜湊表長,i為增量序列,它可能有如下三種情況:
1)i = 1,2,3...m-1
2)i = 1,-1,4,-4,9,-9...k^2,-k^2
3)i為偽隨機序列
採用第一種序列的叫做線性探測再雜湊,採用第二種序列的叫做二次探測再雜湊,採用第三種序列的叫做隨機探測再雜湊。說白了,就是在發生衝突時,將關鍵字應該放入的位置向前或向後移動若干位置,比如採取第一種序列時,如果遇到衝突,就向後移動一個位置來檢測,如果還發生衝突,繼續向後移動,直到遇到一個空槽,則將該關鍵字插入到該位置處。
線性探測比較容易實現,但是它存在一個問題,稱為一次群集。隨著連續被佔用的槽不斷增加,平均查詢時間也隨之不斷增加,群集現象很容易出現,這是因為當一個空槽前有i個滿槽時,該空槽為下一個將被佔用的概率為(i+1)len。
同樣採用二次探測的方法,會產生二次群集,因為每次遇到衝突時,尋找插入位置時都是在跳躍性前進或後退,因此這個相對於一次群集來說,比較輕度。
程式碼實現
下面我們要來看下程式碼的實現了,我們這裡採用連結法來處理衝突,因此描述資料結構的h檔案的程式碼如下:
#define M 7 //雜湊函式中的除數,必須小於等於表長
typedef int ElemType;
/*
該雜湊表採用連結法解決衝突問題
*/
typedef struct Node
{ //Node為連結串列節點的資料結構
ElemType data;
struct Node *next;
}Node,*pNode;
typedef struct HashNode
{ //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;
// memset(hashtable,0,sizeof(hashtable));
return hashtable;
}
/*
在雜湊表中查詢資料data,查詢成功則返回在連結串列中的位置,
查詢不成功則返回NULL,其中雜湊函式為H(key)=key%M
*/
pNode 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;
}
/*
向雜湊表中插入資料data,如果data已存在,則返回fasle,
否則,插入對應連結串列的最後並返回true,其中雜湊函式為H(key)=key%M
*/
bool insert_HashTable(HashTable hashtable,ElemType data)
{
//如果已經存在,返回false
if(search_HashTable(hashtable,data))
return false;
//否則為data分配空間
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;
}
/*
從雜湊表中刪除資料data,如果data不存在,則返回fasle,
否則,刪除之並返回true,其中雜湊函式為H(key)=key%M
*/
bool 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;
}
我們採用如下程式碼來測試:/*******************************
雜湊表
Author:蘭亭風雨 Date:2014-03-07
Email:zyb_maodun@163.com
*******************************/
#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;
}
輸出結果如下:完整原始碼下載
完整原始碼下載地址:http://download.csdn.net/detail/mmc_maodun/7008669
相關文章
- 【資料結構與演算法】字典樹(附完整原始碼)資料結構演算法原始碼
- 【資料結構與演算法】Huffman樹&&Huffman編碼(附完整原始碼)資料結構演算法原始碼
- 【資料結構與演算法】自己動手實現圖的BFS和DFS(附完整原始碼)資料結構演算法原始碼
- 【資料結構與演算法】二叉排序樹C實現(含完整原始碼)資料結構演算法排序原始碼
- 【資料結構與演算法】模式匹配——從BF演算法到KMP演算法(附完整原始碼)資料結構演算法模式KMP原始碼
- 6-C/C++實現資料結構連結串列相關操作C++資料結構
- js實現資料結構及演算法之雜湊表(Hashtable)JS資料結構演算法
- 【演算法與資料結構專場】BitMap演算法基本操作程式碼實現演算法資料結構
- 【資料結構與演算法】內部排序之三:堆排序(含完整原始碼)資料結構演算法排序原始碼
- 【資料結構與演算法】內部排序總結(附各種排序演算法原始碼)資料結構演算法排序原始碼
- 『資料結構與演算法』棧:詳解與程式碼實現資料結構演算法
- 【資料結構與演算法】內部排序之一:插入排序和希爾排序的N中實現(不斷優化,附完整原始碼)資料結構演算法排序優化原始碼
- 資料結構和演算法-雜湊表 (HashTable)資料結構演算法
- 每週一練 之 資料結構與演算法(Dictionary 和 HashTable)資料結構演算法
- 資料結構與演算法——常用高階資料結構及其Java實現資料結構演算法Java
- 南郵資料結構實驗1.1:順序表的相關操作資料結構
- 【資料結構與演算法】內部排序之二:氣泡排序和選擇排序(改進優化,附完整原始碼)資料結構演算法排序優化原始碼
- 演算法與資料結構-棧(Stack)-Java實現演算法資料結構Java
- react原始碼ReactTreeTraversal.js之資料結構與演算法React原始碼JS資料結構演算法
- Redis系列(九):資料結構Hash(ZipList、HashTable)原始碼解析和HSET、HGET命令Redis資料結構原始碼
- 二叉樹(資料結構)——利用“遞迴”思想實現相關演算法問題二叉樹資料結構遞迴演算法
- PostgreSQL 原始碼解讀(109)- WAL#5(相關資料結構)SQL原始碼資料結構
- 資料結構相關知識資料結構
- 資料結構:二叉查詢樹的相關操作資料結構
- html 列印相關操作與實現詳解HTML
- 資料結構與演算法-資料結構(棧)資料結構演算法
- Redis資料結構—跳躍表 skiplist 實現原始碼分析Redis資料結構原始碼
- 那些關於前端資料結構與演算法前端資料結構演算法
- 資料結構與演算法系列(一)陣列實現資料結構演算法陣列
- 資料結構與演算法 | 棧的實現及應用資料結構演算法
- 資料結構與演算法——B樹的C++實現資料結構演算法C++
- 資料結構與演算法 | 如何實現LRU快取淘汰演算法資料結構演算法快取
- PostgreSQL 原始碼解讀(178)- 查詢#95(聚合函式)#1相關資料結構SQL原始碼函式資料結構
- 【資料結構與演算法】內部排序之五:計數排序、基數排序和桶排序(含完整原始碼)資料結構演算法排序原始碼
- 資料結構與演算法資料結構演算法
- 【資料結構與演算法】快速排序(三種程式碼實現以及工程優化)資料結構演算法排序優化
- 【演算法資料結構Java實現】歐幾里得演算法演算法資料結構Java
- 資料結構與演算法--簡單棧實現及其應用資料結構演算法