【資料結構與演算法】HashTable相關操作實現(附完整原始碼)

蘭亭風雨發表於2014-03-12

轉載請註明出處: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


相關文章