1.哈希表概念
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
2.哈希函数概念
给定表M,存在函数hashfunc(key),对任意给定的关键字key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数hashfunc(key)为哈希(Hash) 函数。常见的构建哈希函数的方法有:直接制定法,除留余数法,平方取中法,折叠法,随机数法,数学分析法等。
注意:哈希函数设计的越精妙,产生哈希冲突的可能性越低,但无法避免哈希冲突。
3.哈希冲突
对于两个数据元素的关键字Ki和Kj(i != j),有Ki != Kj,但有:
HashFunc(Ki)==HashFunc(Kj)
即不同关键字通过相同哈希函数计算出相同哈希地址,该种现象称为哈希冲突或哈希碰撞。把具有不同关键字而具有相同哈希地址的数据元素称为“同义词”。
那么,发生哈希冲突该如何处理呢?
4.处理哈希冲突
常见的两种方法是:闭散列和开散列
本篇博客主要介绍闭散列。闭散列,也叫开放地址法,当发生哈希冲突时,如果哈希表未被填满,说明哈希表中必然有空位置,那么可以把key放到表中“下一个”空位值中。基本原理如图所示:
具体实现代码:
①hash_table.h
#pragma once
#include <stddef.h>
#define HashMaxSize 1000
typedef enum Stat {
Empty,
Valid,
Invalid // 当前元素被删除了
} Stat;
typedef int KeyType;
typedef int ValType;
typedef size_t(*HashFunc)(KeyType key);
typedef struct HashElem {
KeyType key;
ValType value;
Stat stat; // 引入一个 stat 标记来作为是否有效的标记
} HashElem;
typedef struct HashTable {
HashElem data[HashMaxSize];
size_t size;
HashFunc hash_func;
} HashTable;
void HashInit(HashTable* ht, HashFunc hash_func);
int HashInsert(HashTable* ht, KeyType key, ValType value);
// 输入key, 查找对应key的value.
int HashFind(HashTable* ht, KeyType key, ValType* value);
void HashRemove(HashTable* ht, KeyType key);
int HashEmpty(HashTable* ht);
size_t HashSize(HashTable* ht);
void HashDestroy(HashTable* ht);
②hash_table.c
#include "hash_table.h"
void HashInit(HashTable* ht, HashFunc hash_func)
{
if (ht == NULL)
{
return; //非法输入
}
ht->size = 0;
ht->hash_func = hash_func;
size_t i = 0;
for (; i < HashMaxSize; ++i)
{
ht->data[i].stat = Empty;
}
return;
}
int HashInsert(HashTable* ht, KeyType key, ValType value)
{
if (ht == NULL)
{
return 0; //非法输入
}
//设定负载因子为0.8
if (ht->size >= HashMaxSize*0.8)
{
return 0; //hash表已满,插入失败
}
//先找到key对应的下标
size_t offset = ht->hash_func(key);
while (1)
{
if (ht->data[offset].stat == Valid)
{
if (ht->data[offset].key == key)
{
return 0; //存在相同元素,插入失败
}
//出现hash冲突,使用线性探测的方式查找下一个元素
++offset;
if (offset >= HashMaxSize)
{
offset -= HashMaxSize;
}
}
else
{
//由于负载因子是80%,一定能够找到一个位置插入
ht->data[offset].key = key;
ht->data[offset].value = value;
ht->data[offset].stat = Valid;
++ht->size;
return 1;
}
}
return 0;
}
int HashFind(HashTable* ht, KeyType key, ValType* value)
{
if (ht == NULL || value == NULL)
{
return 0; //非法输入
}
//1.先通过hash函数计算数组下标
size_t offset = ht->hash_func(key);
//2.从这个数组下标开始,线性的向后查找
while (1)
{
// a)如果找到某个元素的key是想要的,并且状态stat为Vaild,就认为是找到了,将value赋值给输出参数
if (ht->data[offset].key == key && ht->data[offset].stat == Valid)
{
*value = ht->data[offset].value;
return 1;
}
// b)如果当前位置状态为Empty,就认为没找到
else if (ht->data[offset].stat == Empty)
{
return 0;
}
// c)继续尝试查找下一个位置
++offset;
if (offset >= HashMaxSize)
{
offset -= HashMaxSize;
}
}
return 0;
}
void HashRemove(HashTable* ht, KeyType key)
{
if (ht == NULL)
{
return;
}
//1.先通过hash函数计算数组下标
size_t offset = ht->hash_func(key);
//2.从这个数组下标开始,线性的向后查找
while (1)
{
// a)如果找到某个元素的key是想要的,并且状态stat为Vaild,就认为是找到了,将状态置为InVaild,并--size
if (ht->data[offset].key == key && ht->data[offset].stat == Valid)
{
ht->data[offset].stat = Invalid;
--ht->size;
return;
}
// b)如果当前位置状态为Empty,就认为没找到
else if (ht->data[offset].stat == Empty)
{
return;
}
// c)继续尝试查找下一个位置
++offset;
if (offset >= HashMaxSize)
{
offset -= HashMaxSize;
}
}
return;
}
int HashEmpty(HashTable* ht)
{
if (ht == NULL)
{
return 1;
}
return ht->size == 0 ? 1 : 0;
}
size_t HashSize(HashTable* ht)
{
if (ht == NULL)
{
return 0;
}
return ht->size;
}
void HashDestroy(HashTable* ht)
{
size_t i = 0;
for (; i < HashMaxSize; ++i)
{
ht->data[i].stat = Empty;
}
ht->size = 0;
ht->hash_func = NULL;
return;
}
③test.c
#include "hash_table.h"
#include <stdio.h>
#include <windows.h>
#define TEST_HEADER printf("\n=======================%s======================\n",__FUNCTION__)
size_t HashFuncDefault(KeyType key)
{
return key % HashMaxSize;
}
void HashPrintInt(HashTable* ht, const char* msg)
{
printf("[%s]:\n", msg);
size_t i = 0;
for (; i < HashMaxSize; ++i)
{
if (ht->data[i].stat != Empty)
{
printf("[%lu]: key=%d, value=%d\n", i, ht->data[i].key, ht->data[i].value);
}
}
}
void CountNum()
{
TEST_HEADER;
int array[] = { 1, 1, 1, 2, 2, 3,101,1001};
HashTable ht;
HashInit(&ht, HashFuncDefault);
size_t i = 0;
for (; i < sizeof(array) / sizeof(array[0]); ++i)
{
int value = 0;
int ret = HashFind(&ht, array[i], &value);
if (ret == 0)
{
//没找到,直接插入
HashInsert(&ht, array[i], 1);
}
else
{
//该元素存在,先删除该元素,然后再插入
HashRemove(&ht, array[i]);
HashInsert(&ht, array[i], value + 1);
}
}
HashPrintInt(&ht, "最终结果为");
}
void TestInit()
{
TEST_HEADER;
HashTable ht;
HashInit(&ht, HashFuncDefault);
printf("ht->size expect 0,actual %lu\n", ht.size);
printf("ht->hash_func expect %p,actual %p\n", HashFuncDefault, ht.hash_func);
size_t i = 0;
for (; i < HashMaxSize; ++i)
{
if (ht.data[i].stat != Empty)
{
printf("pos[%lu] elem error!\n", i);
}
}
}
void TestInsert()
{
TEST_HEADER;
HashTable ht;
HashInit(&ht, HashFuncDefault);
HashInsert(&ht, 1, 100);
HashInsert(&ht, 2, 200);
HashInsert(&ht, 1001, 300);
HashInsert(&ht, 1002, 400);
HashInsert(&ht, 1, 500);
HashPrintInt(&ht, "插入五个元素");
}
void TestFind()
{
TEST_HEADER;
HashTable ht;
HashInit(&ht, HashFuncDefault);
HashInsert(&ht, 1, 100);
HashInsert(&ht, 2, 200);
HashInsert(&ht, 1001, 300);
HashInsert(&ht, 1002, 400);
HashInsert(&ht, 1, 500);
HashPrintInt(&ht, "插入五个元素");
int value = 0;
int ret = HashFind(&ht, 1, &value);
printf("[查找1]:\n");
printf("ret expect 1,actual %d\n", ret);
printf("value expect 100,actual %d\n\n", value);
ret = HashFind(&ht, 1001, &value);
printf("[查找1001]:\n");
printf("ret expect 1,actual %d\n", ret);
printf("value expect 300,actual %d\n\n", value);
ret = HashFind(&ht, 1003, &value);
printf("[查找1003]:\n");
printf("ret expect 0,actual %d\n", ret);
}
void TestRemove()
{
TEST_HEADER;
HashTable ht;
HashInit(&ht, HashFuncDefault);
HashInsert(&ht, 1, 100);
HashInsert(&ht, 2, 200);
HashInsert(&ht, 1001, 300);
HashInsert(&ht, 1002, 400);
HashInsert(&ht, 1, 500);
HashPrintInt(&ht, "插入五个元素");
HashRemove(&ht, 1);
int value = 0;
int ret = HashFind(&ht, 1, &value);
printf("[删除1之后]:\n");
printf("ret expect 0,actual %d\n\n", ret);
HashRemove(&ht, 1001);
ret = HashFind(&ht, 1001, &value);
printf("[删除1001之后]:\n");
printf("ret expect 0,actual %d\n\n", ret);
HashRemove(&ht, 1003);
ret = HashFind(&ht, 1003, &value);
printf("[删除1003之后]:\n");
printf("ret expect 0,actual %d\n", ret);
}
void TestEmpty()
{
TEST_HEADER;
HashTable ht;
HashInit(&ht, HashFuncDefault);
int ret = HashEmpty(&ht);
printf("ret expect 1, actual %d\n", ret);
HashInsert(&ht, 1, 100);
HashInsert(&ht, 2, 200);
HashInsert(&ht, 1001, 300);
HashInsert(&ht, 1002, 400);
HashInsert(&ht, 1, 500);
HashPrintInt(&ht, "插入五个元素");
ret = HashEmpty(&ht);
printf("ret expect 0, actual %d\n", ret);
HashRemove(&ht, 1);
HashRemove(&ht, 2);
HashRemove(&ht, 1001);
HashRemove(&ht, 1002);
ret = HashEmpty(&ht);
printf("ret expect 1, actual %d\n", ret);
}
void TestSize()
{
TEST_HEADER;
HashTable ht;
HashInit(&ht, HashFuncDefault);
size_t ret = HashSize(&ht);
printf("ret expect 0, actual %d\n", ret);
HashInsert(&ht, 1, 100);
HashInsert(&ht, 2, 200);
HashInsert(&ht, 1001, 300);
HashInsert(&ht, 1002, 400);
HashInsert(&ht, 1, 500);
HashPrintInt(&ht, "插入五个元素");
ret = HashSize(&ht);
printf("ret expect 4, actual %d\n", ret);
HashRemove(&ht, 1);
HashRemove(&ht, 2);
HashRemove(&ht, 1001);
HashRemove(&ht, 1002);
ret = HashSize(&ht);
printf("ret expect 0, actual %d\n", ret);
}
void TestDestroy()
{
TEST_HEADER;
HashTable ht;
HashInit(&ht, HashFuncDefault);
HashInsert(&ht, 1, 100);
HashInsert(&ht, 2, 200);
HashInsert(&ht, 1001, 300);
HashInsert(&ht, 1002, 400);
HashInsert(&ht, 1, 500);
HashPrintInt(&ht, "插入五个元素");
HashDestroy(&ht);
printf("销毁哈希表后:\n");
printf("ht->size expect 0,actual %lu\n", ht.size);
printf("ht->hash_func expect NULL,actual %p\n", ht.hash_func);
size_t i = 0;
for (; i < HashMaxSize; ++i)
{
if (ht.data[i].stat != Empty)
{
printf("pos[%lu] elem error!\n", i);
}
}
}
int main()
{
TestInit();
TestInsert();
TestFind();
TestRemove();
TestEmpty();
TestSize();
TestDestroy();
CountNum();
system("pause");
return 0;
}
对应结果截图:
未完待续……