開散列:(鏈地址法或者也叫開鏈法)
首先對關鍵碼集合用哈希函數計算哈希地址,具有相同地址的關鍵碼歸於同一子集合,每一個子集合稱爲一個桶,每個桶中的元素通過單鏈錶鏈接起來,個鏈表的頭結點存儲在哈希表中。由此也有一個形象的叫法是哈希桶。
哈希桶處理哈希衝突C語言實現
//hash.h文件內容
#pragma once
#define max_size 1000
typedef int KeyType;
typedef int ValType;
typedef int (*HashFunc)(KeyType key);
typedef struct HashElem
{
KeyType key;
ValType value;
struct HashElem *next;
}HashElem;
typedef struct HashTable
{
//如果我們的hash桶上面的鏈表是一個不帶頭結點的鏈表
//類型就用HashElem*
//如果是一個帶頭結點的鏈表
//類型就用HashElem
HashElem *data[max_size];
int size;//有效元素個數
HashFunc func;//哈希函數
}HashTable;
//初始化
void HashInit(HashTable *ht,HashFunc Hash_func);
//銷燬
void HashDestroy(HashTable *ht);
//插入數據
void HashInsert(HashTable *ht,KeyType key,ValType value);
//查找數據
int HashFind(HashTable *ht,KeyType key,ValType *value);
//刪除數據
void HashRemove(HashTable *ht,KeyType key);
以下爲相應的函數實現
//初始化
void HashInit(HashTable *ht,HashFunc Hash_func)
{
if(ht == NULL)
{
//非法輸入
return;
}
ht->size = 0;
ht->func = Hash_func;
int i = 0;
for(;i < max_size;i++)
{
ht->data[i] = NULL;
}
return;
}
//銷燬
void HashDestroy(HashTable *ht)
{
if(ht == NULL)
{
//非法輸入
return;
}
ht->size = 0;
ht->func = NULL;
//遍歷所有的鏈表並進行釋放
int i = 0;
for(;i < max_size;i++)
{
HashElem *cur = ht->data[i];
while(cur != NULL)
{
HashElem *next = cur->next;
DestroyElem(cur);
cur = next;
}
}
return;
}
//測試一下
void TestInit()
{
Test_Header;
HashTable ht;
HashInit(&ht,Hash_func);
printf("expect size = 0,actual size = %d\n",ht.size);
printf("expect func = %p,actual func = %p\n",Hash_func,ht.func);
}
測試結果:
//查找元素是否存在於對應的鏈表中的函數,成功找到則返回對應的元素節點
HashElem *HashBucketFind(HashElem *head,KeyType to_find)
{
HashElem *cur = head;
for(;cur != NULL;cur = cur->next)
{
if(cur->key == to_find)
{
//在當前鏈表中找到了該元素
return cur;
}
}
//在當前鏈表中沒找到該元素
return NULL;
}
//插入數據
void HashInsert(HashTable *ht,KeyType key,ValType value)
{
if(ht == NULL)
{
//非法輸入
return;
}
if(ht->size >= 0.8*max_size)
{
//此時哈希表已經達到了負載因子的上限
//不能再繼續插入元素
return;
}
//可以繼續插入元素
//先由key計算出offset
int offset = ht->func(key);
//在offset對應的鏈表中查找當前待插入的元素是否已經存在
HashElem *ret = HashBucketFind(ht->data[offset],key);
if(ret != NULL)
{
//走到這裏說明待插入的元素已經存在
//約定哈希表中不存在重複的元素
//插入失敗直接返回
return;
}
else
{
//不存在重複的元素
//可以插入(頭插法)
HashElem *new_elem = CreateElem(key,value);
//將新的元素頭插法插入當前鏈表中
new_elem->next = ht->data[offset];
//更新鏈表的頭結點爲新插入的元素
ht->data[offset] = new_elem;
//別忘了將哈希表的有效元素個數+1
++ht->size;
}
}
//測試用打印哈希表元素的函數
void HashPrint(HashTable *ht,const char *msg)
{
printf("[%s]\n",msg);
int i = 0;
for(;i < max_size;i++)
{
if(ht->data[i] != NULL)
{
printf("i = %d\n",i);
HashElem *cur = ht->data[i];
for(;cur != NULL;cur = cur->next)
{
printf("[%d,%d] ",cur->key,cur->value);
}
printf("\n");
}//if結束
}//for結束
}
//測試一下
void TestInsert()
{
Test_Header;
HashTable ht;
HashInit(&ht,Hash_func);
HashInsert(&ht,1,1);
HashInsert(&ht,1,10);
HashInsert(&ht,2,20);
HashInsert(&ht,1000,100);
HashInsert(&ht,2000,200);
HashPrint(&ht,"插入5個元素");
}
測試結果:
//查找數據
int HashFind(HashTable *ht,KeyType key,ValType *value)
{
if(ht == NULL || value == NULL)
{
//非法輸入
return 0;
}
if(ht->size == 0)
{
//空哈希表
return 0;
}
//由key值計算出offset
int offset = ht->func(key);
//從offset指向的鏈表位置開始
//遍歷鏈表查找
HashElem *ret = HashBucketFind(ht->data[offset],key);
if(ret == NULL)
{
//沒找到
return 0;
}
else
{
//找到了
*value = ret->value;
return 1;
}
}
//測試一下
void TestFind()
{
Test_Header;
HashTable ht;
HashInit(&ht,Hash_func);
HashInsert(&ht,1,1);
HashInsert(&ht,1,10);
HashInsert(&ht,2,20);
HashInsert(&ht,1000,100);
HashInsert(&ht,2000,200);
ValType value;
int ret = HashFind(&ht,1000,&value);
printf("查找數據爲1000的元素結果爲:");
printf("expect ret = 1,actual ret = %d;",ret);
printf("expect value = 100,actual value = %d\n",value);
ret = HashFind(&ht,3000,&value);
printf("查找數據爲3000的元素結果爲:");
printf("expect ret = 0,actual ret = %d\n",ret);
}
測試結果:
//查找元素是否存在於對應鏈表中的函數。成功返回1,失敗返回0
int HashBucketFindEx(HashElem *head,KeyType to_find,\
HashElem **pre_node,HashElem **cur_node)
{
HashElem *pre = NULL;
HashElem *cur = head;
for(;cur != NULL;pre = cur,cur = cur->next)
{
if(cur->key == to_find)
{
//找到了,跳出循環
break;
}
}
if(cur == NULL)
{
//走到這兒說明是鏈表遍歷完了
//沒找到
return 0;
}
*pre_node = pre;
*cur_node = cur;
return 1;
}
//刪除數據
void HashRemove(HashTable *ht,KeyType key)
{
if(ht == NULL)
{
//非法輸入
return;
}
if(ht->size == 0)
{
//空哈希表
return;
}
//由key計算出offset值
int offset = ht->func(key);
//在當前offset對應的鏈表下
//查找待刪除的元素是否存在
HashElem *pre = NULL;
HashElem *cur = NULL;
int ret = HashBucketFindEx(ht->data[offset],key,&pre,&cur);
if(ret == 0)
{
//當前鏈表下沒有找到待刪除的元素
return;
}
else
{
//找到了,可以進行刪除
if(pre == NULL)
{
//要刪除的元素是鏈表的頭結點
//更新當前鏈表的頭結點
ht->data[offset] = cur->next;
}
else
{
//pre的下一個元素本來指向的是cur(待刪除的元素)
//cur如果要被刪除,則pre指向的應該是cur的下一個元素
pre->next = cur->next;
}
DestroyElem(cur);
//將哈希表的有效元素個數-1
--ht->size;
}//else(ret=1)結束
}
//測試一下
void TestRemove()
{
Test_Header;
HashTable ht;
HashInit(&ht,Hash_func);
HashInsert(&ht,1,1);
HashInsert(&ht,1,10);
HashInsert(&ht,2,20);
HashInsert(&ht,1000,100);
HashInsert(&ht,2000,200);
HashRemove(&ht,2);
HashPrint(&ht,"刪除數據爲2的元素後的結果:");
HashRemove(&ht,20);
HashPrint(&ht,"刪除數據爲20的元素後的結果:");
}
測試結果:
·