1. 程式人生 > >【資料結構】雜湊表及雜湊桶的基本操作

【資料結構】雜湊表及雜湊桶的基本操作

  順序搜尋和二叉搜尋樹中,元素儲存位置和元素各關鍵碼之間沒有對應的關係,這就導致在查詢一個元素時,必須經過關鍵碼的多次比較。那麼是否有這樣一種資料結構,可以不經過任何比較,直接找到想要搜尋的元素呢?答案是肯定的,那就是通過某種函式(hashFunc)使得元素的儲存位置與它的關鍵碼之間建立一種一一對映的關係,那麼在查詢時就可以快速地找到該元素,我們將這種資料的儲存結構稱為雜湊。

雜湊表的實現

hash.h

# ifndef __HASH_H__
# define __HASH_H__

# include <stdio.h>
# include <stdlib.h>
# include <assert.h>
# include <string.h>

typedef int Key; // 關鍵碼
typedef int Value; // 實際資料

//KV結構
typedef struct KVPair
{
	Key key;
	Value value;
}KVPair;

//雜湊表裡的狀態
typedef enum State
{
	EMPTY, // 之前沒有存過數
	EXIST, // 目前存在數
	DELETED // 曾經存在數,現在被刪除了
}State;

//雜湊表裡每個資料的型別
typedef struct Element
{
	State state;
	KVPair data;
}Element;

// 雜湊函式的函式指標宣告
typedef unsigned long (* HashFuncType)(Key, unsigned long);

//雜湊表
typedef struct HashTable
{
	Element * table; // 實際儲存空間
	unsigned long capacity; // 容量
	unsigned long size; // 實際已存的資料個數
	HashFuncType hashFuncType; //雜湊函式
}HashTable;

unsigned long Mod(Key key, unsigned long capacity); // 取模函式
void HTInit(HashTable * pHT, unsigned long capacity, HashFuncType hashFunc); // 雜湊表的初始化
unsigned long HTSearch(HashTable * pHT, Key key); // 雜湊表的查詢,找到返回下標,沒找到的話返回(unsigned long)-1
unsigned long HTInsert(HashTable * pHT, Key key, Value value); // 雜湊表的插入,如果插入成功則返回下標,失敗(key重複)的話返回(unsigned long)-1
unsigned long HTRemove(HashTable * pHT, Key key); // 雜湊表的刪除,如果刪除成功則返回0,失敗(key不存在)的話返回(unsigned long)-1
void HTDestory(HashTable * pHT); // 雜湊表的銷燬

# endif // __HASH_H__

hash.c

  本篇實現的雜湊函式是在除留餘數法的基礎上實現的,設散列表中允許的地址數為m,取一個不大於m,但最接近或者等於m的質數p作為除數,按照雜湊函式:Hash(key) = key % p(p<=m),將關鍵碼轉換成雜湊地址。

  即使雜湊函式設計的再合理,也不可避免地會出現雜湊衝突(雜湊碰撞),這種現象表現為不同關鍵碼通過相同的雜湊函式計算出相同的雜湊地址。

  解決雜湊衝突的常見方法是 閉雜湊 和 開雜湊。

  閉雜湊,也叫作開放地址法,當發生雜湊衝突時,如果雜湊表還沒裝滿,那就說明在雜湊表中還有空閒位置,那麼可以把關鍵碼存放到表中“下一個”空閒位置。

  接下來提供兩種尋找空閒位置的方法,分別是線性探測法和二次檢測法。

  線性探測法:從發生衝突的位置開始,依次繼續向後探測,直至找到空閒位置為止。

  線性探測法,實現起來比較簡單,但是有一個缺陷,那就是:一旦發生雜湊衝突,所有的衝突就會連在一起,容易產生資料“堆積”,即就是不同的關鍵碼佔據了可利用的空閒位置,使得尋找某個關鍵碼的位置就需要比較很多次,從而導致搜尋的效率變低。

  二次探測法:發生雜湊衝突時,按照times * times(也就是二次方)的方式去檢測空閒位置,這樣的方式就是為了避免階段性的雜湊衝突造成的堵塞。

  關於查詢、插入以及刪除,這裡作出以下幾點說明:

查詢:如果找到對應的關鍵碼那就表示找到了,如果找到空位那就表示沒有找到。

插入:不能插入相同數字,這樣會破壞雜湊表的結構。

刪除:如果存在雜湊衝突,那麼刪除掉第一個存在雜湊衝突的數字之後,該位置變為空,那麼在查詢第二個乃至其他與該數字存在雜湊衝突關係的數字時,因為第一個位置空餘的緣故,就會被認為沒查詢到。這裡採用假刪除,就是說在這個大的雜湊表中,存在三種狀態,分別是EMPTY、EXIST、DELETED,那麼在刪除某個數字的時候,把狀態改為EMPTY即可。

#define _CRT_SECURE_NO_WARNINGS 1
/*
* Copyright (c) 2018, code farmer from sust
* All rights reserved.
*
* 檔名稱:hash.c
* 功能:雜湊表基本操作內部實現細節
*
* 當前版本:V1.0
* 作者:sustzc
* 完成日期:2018年6月27日18:43:59
*/

# include "hash.h"

/*
*	函式名稱:GetNextPrime
*
*	函式功能:在素數表中找一個最接近並且不小於num的素數,降低雜湊衝突
*
*	入口引數:num
*
*	出口引數:(unsigned long)-1 or _PrimeList[i]
*
*	返回型別:unsigned long
*/

unsigned long GetNextPrime(unsigned long num)
{
	unsigned long i = 0;
	const unsigned long PrimeSize = 28;
	static const unsigned long _PrimeList [28] =
	{
		53ul, 97ul, 193ul, 389ul, 769ul,
		1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
		49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
		1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
		50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
		1610612741ul, 3221225473ul, 4294967291ul
	};

	for (i = 0; i < PrimeSize; i++)
	{
		if (_PrimeList[i] >= num)
		{
			return _PrimeList[i];
		}
		else
		{
			;
		}
	}

	return (unsigned long)-1;
}

/*
*	函式名稱:Mod
*
*	函式功能:取模函式
*
*	入口引數:key, capacity
*
*	出口引數:key % capacity
*
*	返回型別:unsigned long
*/

unsigned long Mod(Key key, unsigned long capacity)
{		
	return (unsigned long)key % capacity;
}

/*
*	函式名稱:LinearDective
*
*	函式功能:線性探測法
*
*	入口引數:index, capacity
*
*	出口引數:(index++) % capacity
*
*	返回型別:unsigned long
*/

unsigned long LinearDective(unsigned long index, unsigned long capacity)
{
	return (++index) % capacity;
}

/*
*	函式名稱:SquareDective
*
*	函式功能:二次探測法
*
*	入口引數:index, capacity, times
*
*	出口引數:(index + times * times) % capacity
*
*	返回型別:unsigned long
*/

unsigned long SquareDective(unsigned long index, unsigned long capacity, int times)
{
	return (index + times * times) % capacity;
}

/*
*	函式名稱:HTInit
*
*	函式功能:初始化雜湊表
*
*	入口引數:pHT, capacity, hashFunc
*
*	出口引數:void
*
*	返回型別:void
*/

void HTInit(HashTable * pHT, unsigned long capacity, HashFuncType hashFunc)
{
	unsigned long i = 0;

	assert(NULL != pHT);

	capacity = GetNextPrime(capacity);

	pHT->table = (Element *)malloc(sizeof(Element) * capacity);
	assert(pHT->table);

	pHT->size = 0;
	pHT->capacity = capacity;
	pHT->hashFuncType = hashFunc;
		
	for (i = 0; i < capacity; i++)
	{
		pHT->table[i].state = EMPTY;
	}

	return;
}

/*
*	函式名稱:HTSearch
*
*	函式功能:查詢雜湊表中的關鍵碼,找到返回下標,沒找到的話返回(unsigned long)-1
*
*	入口引數:pHT, key
*
*	出口引數:(unsigned long)-1 or index
*
*	返回型別:unsigned long
*/

unsigned long HTSearch(HashTable * pHT, Key key)
{
	int times = 1;
	unsigned long index = 0;
	unsigned long start = 0;

	assert(NULL != pHT);

	index = pHT->hashFuncType(key, pHT->capacity);

	while (EMPTY != pHT->table[index].state)
	{
		if ((key == pHT->table[index].data.key) && (EXIST == pHT->table[index].state))
		{
			return index;
		}
		else
		{	//線性探測
			//index = LinearDective(index, pHT->capacity);	

			//二次探測
			index = SquareDective(start, pHT->capacity, times);
			times++;
		}
	}
	
	return (unsigned long)-1;
}

/*
*	函式名稱:ExpandIfRequired
*
*	函式功能:判斷是否擴容,注意:擴容時不使用realloc, malloc重新分配記憶體
*
*	入口引數:pHT
*
*	出口引數:(unsigned long)-1 or 0
*
*	返回型別:void
*/

void ExpandIfRequired(HashTable * pHT)
{
	if ((pHT->size) * 10 / (pHT->capacity) < 7)
	{
		return;
	}
	else
	{
		unsigned long i = 0;
		HashTable newHT;
		unsigned long capacity = pHT->capacity * 2;

		HTInit(&newHT, capacity, pHT->hashFuncType);

		for (i = 0; i < pHT->capacity; i++)
		{
			if (EXIST == pHT->table[i].state)
			{
				HTInsert(&newHT, pHT->table[i].data.key, pHT->table[i].data.value);
			}
			else
			{
				;
			}
		}
	
		free(pHT->table);
		pHT->table = newHT.table;
		pHT->capacity = newHT.capacity;
	}

	return;
}

/*
*	函式名稱:HTInsert
*
*	函式功能:插入資料,如果插入成功則返回下標,失敗(key重複)的話返回(unsigned long)-1
*
*	入口引數:pHT, key, value
*
*	出口引數:(unsigned long)-1 or 0
*
*	返回型別:unsigned long
*/

unsigned long HTInsert(HashTable * pHT, Key key, Value value)
{
	int times = 1;
	unsigned long index = 0;
	unsigned long start = 0;

	assert(NULL != pHT);

	ExpandIfRequired(pHT);

	index = pHT->hashFuncType(key, pHT->capacity);
	start = index;

	while (EXIST == pHT->table[index].state)
	{
		if ((key == pHT->table[index].data.key))
		{
			return (unsigned long)-1;
		}
		else
		{	//線性探測
			//index = LinearDective(index, pHT->capacity);

			//二次探測
			index = SquareDective(start, pHT->capacity, times);
			times++;
		}
	}

	pHT->table[index].data.key = key;
	pHT->table[index].data.value = value;
	pHT->table[index].state = EXIST;
	pHT->size++;

	return 0;
}

/*
*	函式名稱:HTRemove
*
*	函式功能:刪除資料,如果刪除成功則返回0,失敗(key不存在)的話返回(unsigned long)-1
*
*	入口引數:pHT, key
*
*	出口引數:(unsigned long)-1 or 0
*
*	返回型別:unsigned long
*/

unsigned long HTRemove(HashTable * pHT, Key key)
{
	int times = 1; 
	unsigned long index = 0;
	unsigned long start = 0;

	assert(NULL != pHT);

	index = pHT->hashFuncType(key, pHT->capacity);
	start = index;

	while (EMPTY != pHT->table[index].state)
	{
		if ((key == pHT->table[index].data.key) && (EXIST == pHT->table[index].state))
		{
			pHT->table[index].state = DELETED;
			pHT->size--;

			return 0;
		}
		else
		{
			//線性探測
			//index = LinearDective(index, pHT->capacity);
			
			//二次探測
			index = SquareDective(start, pHT->capacity, times);
			times++;
		}

	}
	
	return (unsigned long)-1;
}

/*
*	函式名稱:HTDestory
*
*	函式功能:銷燬雜湊表
*
*	入口引數:pHT
*
*	出口引數:void
*
*	返回型別:void
*/

void HTDestory(HashTable * pHT)
{
	assert(NULL != pHT);
	
	free(pHT->table);
		
	return;
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1

/*
* Copyright (c) 2018, code farmer from sust
* All rights reserved.
*
* 檔名稱:test.c
* 功能:測試雜湊表基本操作
*
* 當前版本:V1.0
* 作者:sustzc
* 完成日期:2018年6月27日18:44:52
*/

# include "hash.h"

/*
*	函式名稱:main
*
*	函式功能:測試主程式
*
*	入口引數:void
*
*	出口引數:0
*
*	返回型別:int
*/

int main(void)
{
	//測試線性探測法

	//int i = 0;
	//HashTable hashTable;
	//int numbers[] = {1, 19, 38, 27, 19, 27, 30, 20, 73, 126};

	//HTInit(&hashTable, 10, Mod);

	//for (i = 0; i < sizeof(numbers) / sizeof(int); i++)
	//{
	//	unsigned long index = HTSearch(&hashTable, numbers[i]);

	//	if ((unsigned long)-1 != index)
	//	{
	//		hashTable.table[index].data.value++;
	//	}
	//	else
	//	{
	//		HTInsert(&hashTable, numbers[i], 1);
	//	}
	//}

	//HTRemove(&hashTable, 45);
	//HTRemove(&hashTable, 20);

	//HTInsert(&hashTable, 179, 1);

	//printf("查詢%d, 查詢結果是%lu\n", 179, HTSearch(&hashTable, 179));

	//測試二次探測法

	int i = 0;
	HashTable hashTable;
	int numbers[] = {0, 53, 106, 159, 212};

	HTInit(&hashTable, 10, Mod);

	for (i = 0; i < sizeof(numbers) / sizeof(int); i++)
	{
		HTInsert(&hashTable, numbers[i], 285);
	}

	HTDestory(&hashTable);

	return 0;
}

雜湊桶的實現

HashNode.h

# ifndef __HASHNODE_H__
# define __HASHNODE_H__

# include <stdio.h>
# include <stdlib.h>
# include <assert.h>
# include <string.h>

typedef int Key; // 關鍵碼
typedef int Value; // 實際資料

//KV結構
typedef struct KVPair
{
	Key key;
	Value value;
}KVPair;

typedef KVPair DataType;

//雜湊桶裡每個結點的型別
typedef struct Node
{
	DataType data;
	struct Node * pNext;
}Node;

typedef Node * SDataType;

// 雜湊函式的函式指標宣告
typedef unsigned long (* HashFuncType)(Key, unsigned long);

//雜湊桶
typedef struct HashBucket
{
	SDataType * table; // 實際儲存空間   也就是個指標陣列
	unsigned long capacity; // 容量
	unsigned long size; // 實際已存的資料個數
	HashFuncType hashFuncType; //雜湊函式
}HashBucket;

unsigned long Mod(Key key, unsigned long capacity); // 取模函式
void HTInit(HashBucket * pHB, unsigned long capacity, HashFuncType hashFunc); // 雜湊桶的初始化
void HTPrint(HashBucket * pHB); // 列印雜湊桶
SDataType HTSearch(HashBucket * pHB, Key key); // 查詢雜湊桶中的關鍵碼,如果找到了返回pCurNode的地址,沒有找到的話返回NULL
unsigned long HTInsert(HashBucket * pHB, Key key, Value value); // 雜湊桶的插入,如果插入成功則返回下標,失敗(key重複)的話返回(unsigned long)-1
unsigned long HTRemove(HashBucket * pHB, Key key); // 雜湊桶的刪除,如果刪除成功則返回0,失敗(key不存在)的話返回(unsigned long)-1
void HTDestory(HashBucket * pHB); // 雜湊桶的銷燬

# endif // __HASHNODE_H__

HashNode.c

  開雜湊法,又叫做鏈地址法(開鏈法),首先對關鍵碼集合用雜湊函式計算雜湊地址,具有相同地址的關鍵碼歸於同一個子集合,每一個子集合稱為一個桶,每個桶中的元素通過一個單鏈錶鏈接起來,各連結串列的頭結點儲存於雜湊表中。

  這裡需要注意的是雜湊桶的銷燬,應該先釋放連結串列開闢的結點,再去釋放指標陣列。

#define _CRT_SECURE_NO_WARNINGS 1

/*
* Copyright (c) 2018, code farmer from sust
* All rights reserved.
*
* 檔名稱:HashNode.c
* 功能:雜湊桶基本操作內部實現細節
*
* 當前版本:V1.0
* 作者:sustzc
* 完成日期:2018年6月27日18:43:59
*/

# include "HashNode.h"

/*
*	函式名稱:GetNextPrime
*
*	函式功能:在素數表中找一個最接近並且不小於num的素數,降低雜湊衝突
*
*	入口引數:num
*
*	出口引數:(unsigned long)-1 or _PrimeList[i]
*
*	返回型別:unsigned long
*/

unsigned long GetNextPrime(unsigned long num)
{
	unsigned long i = 0;
	const unsigned long PrimeSize = 28;
	static const unsigned long _PrimeList [28] =
	{
		53ul, 97ul, 193ul, 389ul, 769ul,
		1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
		49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
		1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
		50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
		1610612741ul, 3221225473ul, 4294967291ul
	};

	for (i = 0; i < PrimeSize; i++)
	{
		if (_PrimeList[i] >= num)
		{
			return _PrimeList[i];
		}
		else
		{
			;
		}
	}

	return (unsigned long)-1;
}

/*
*	函式名稱:Mod
*
*	函式功能:取模函式
*
*	入口引數:key, capacity
*
*	出口引數:key % capacity
*
*	返回型別:unsigned long
*/

unsigned long Mod(Key key, unsigned long capacity)
{		
	return (unsigned long)key % capacity;
}

/*
*	函式名稱:HTInit
*
*	函式功能:初始化雜湊桶
*
*	入口引數:pHB, capacity, hashFunc
*
*	出口引數:void
*
*	返回型別:void
*/

void HTInit(HashBucket * pHB, unsigned long capacity, HashFuncType hashFunc)
{
	unsigned long i = 0;

	assert(NULL != pHB);

	//產生素數的容量,減少雜湊衝突
	capacity = GetNextPrime(capacity);

	pHB->table = (SDataType *)malloc(sizeof(SDataType) * capacity);
	assert(pHB->table);

	pHB->size = 0;
	pHB->capacity = capacity;
	pHB->hashFuncType = hashFunc;
		
	for (i = 0; i < pHB->capacity; i++)
	{
		//實際上這裡的陣列元素都是存放了單鏈表的頭指標
		pHB->table[i] = NULL;
	}

	return;
}

/*
*	函式名稱:HTPrint
*
*	函式功能:列印雜湊桶
*
*	入口引數:pHB
*
*	出口引數:void
*
*	返回型別:void
*/

void HTPrint(HashBucket * pHB)
{
	unsigned long i = 0;

	assert(NULL != pHB);

	for (i = 0; i < pHB->capacity; i++)
	{
		SDataType pCurNode = pHB->table[i];

		printf("table[%d]->", i);

		while (NULL != pCurNode)
		{
			printf("%d ->", pCurNode->data.key);
			pCurNode = pCurNode->pNext;
		}

		printf("NULL\n");
	}

	return;
}

/*
*	函式名稱:HTSearch
*
*	函式功能:查詢雜湊桶中的關鍵碼,如果找到了返回pCurNode的地址,沒有找到的話返回NULL
*
*	入口引數:pHB, key
*
*	出口引數:NULL or pCurNode
*
*	返回型別:SDataType
*/

SDataType HTSearch(HashBucket * pHB, Key key)
{
	unsigned long index = 0;
	SDataType pCurNode = NULL;

	assert(NULL != pHB);

	index = pHB->hashFuncType(key, pHB->capacity);
	pCurNode = pHB->table[index];

	while (NULL != pCurNode)
	{
		if (key == pCurNode->data.key)
		{
			// key已經存在
			return pCurNode;
		}
		else
		{
			pCurNode = pCurNode->pNext;
		}
	}

	return NULL;
}

/*
*	函式名稱:HTInsert
*
*	函式功能:插入資料,如果插入成功則返回下標,失敗(key重複)的話返回(unsigned long)-1
*
*	入口引數:pHB, key, value
*
*	出口引數:(unsigned long)-1 or 0
*
*	返回型別:unsigned long
*/

unsigned long HTInsert(HashBucket * pHB, Key key, Value value)
{
	unsigned long index = 0;
	SDataType pCurNode = NULL;
	SDataType pNewNode = NULL;

	assert(NULL != pHB);

	index = pHB->hashFuncType(key, pHB->capacity);
	pCurNode = pHB->table[index];

	while (NULL != pCurNode)
	{
		if (key == pCurNode->data.key)
		{
			// key已經存在
			return (unsigned long)-1;
		}
		else
		{
			pCurNode = pCurNode->pNext;
		}
	}

	// key 不在雜湊桶裡
	pNewNode = (SDataType)malloc(sizeof(Node));
	assert(NULL != pNewNode);

	pNewNode->data.key = key;
	pNewNode->data.value = value;
	pHB->size++;

	pNewNode->pNext = pHB->table[index];
	pHB->table[index] = pNewNode;

	return 0;
}

/*
*	函式名稱:HTRemove
*
*	函式功能:刪除資料,如果刪除成功則返回0,失敗(key不存在)的話返回(unsigned long)-1
*
*	入口引數:pHB, key
*
*	出口引數:(unsigned long)-1 or 0
*
*	返回型別:unsigned long
*/

unsigned long HTRemove(HashBucket * pHB, Key key)
{
	unsigned long index = 0;
	SDataType pCurNode = NULL;
	SDataType pNextNode = NULL;
	SDataType pPrevNode = NULL;

	assert(NULL != pHB);

	index = pHB->hashFuncType(key, pHB->capacity);
	pCurNode = pHB->table[index];

	while (NULL != pCurNode)
	{
		if (key == pCurNode->data.key)
		{
			// 刪除的是第一個
			if (NULL == pPrevNode)
			{
				pHB->table[index] = pCurNode->pNext;
			}
			else
			{
				pPrevNode->pNext = pCurNode->pNext;
			}

			free(pCurNode);
			return 0;
		}
		else
		{
			pPrevNode = pCurNode;
			pCurNode = pCurNode->pNext;
		}
	}

	return (unsigned long)-1; 
}

/*
*	函式名稱:HTDestory
*
*	函式功能:銷燬雜湊桶,需要注意的是,先銷燬連結串列的結點,然後再銷燬指標陣列
*
*	入口引數:pHB
*
*	出口引數:void
*
*	返回型別:void
*/

void HTDestory(HashBucket * pHB)
{
	unsigned long i = 0;
	SDataType pCurNode = NULL;
	SDataType pNextNode = NULL;

	assert(NULL != pHB);

	for (i = 0; i < pHB->capacity; i++)
	{
		for (pCurNode = pHB->table[i]; NULL != pCurNode; pCurNode = pNextNode)
		{
			pNextNode = pCurNode->pNext;

			free(pCurNode);
		}
	}
	
	free(pHB->table);
		
	return;
}

HashNodeTest.c

#define _CRT_SECURE_NO_WARNINGS 1

/*
* Copyright (c) 2018, code farmer from sust
* All rights reserved.
*
* 檔名稱:HashNodeTest.c
* 功能:測試雜湊桶基本操作
*
* 當前版本:V1.0
* 作者:sustzc
* 完成日期:2018年6月27日18:44:23
*/

# include "HashNode.h"

/*
*	函式名稱:main
*
*	函式功能:測試主程式
*
*	入口引數:void
*
*	出口引數:0
*
*	返回型別:int
*/

int main(void)
{
	HashBucket hb;
	int i = 0;
	SDataType pFound = NULL;
	//int numbers[] = {1, 53, 1, 53, 106};
	int numbers[] = {1, 23, 46, 53, 106};

	HTInit(&hb, 5, Mod);

	printf("------------------before:\n");
	HTPrint(&hb);

	for (i = 0; i < sizeof(numbers) / sizeof(int); i++)
	{
		pFound = HTSearch(&hb, numbers[i]);

		if (NULL != pFound)
		{
			pFound->data.value++;
		}
		else
		{
			HTInsert(&hb, numbers[i], 1);
		}
	}
	
	printf("\n------------------after:\n");
	HTPrint(&hb);
	HTRemove(&hb, 23);
	printf("\n------------------delete:\n");
	HTPrint(&hb);

	return 0;
}