哈希表
若关键字为k
,则其值存放在f(k)
的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系f()
为散列函数,按这个思想建立的表为散列表(即哈希表).
对不同的关键字可能得到同一散列地址,即k1≠k2
,而f(k1)=f(k2)
这种现象称为碰撞
散列函数能使对一个数据序列的访问过程更加迅速有效,通过散列函数,数据元素将被更快地定位.
散列函数有很多种
常用的有:
- 直接寻址法:取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a·key + b,其中a和b为常数(这种散列函数叫做自身函数)。若其中H(key)中已经有值了,就往下一个找,直到H(key)中没有值了,就放进去。
- 平方取中法:当无法确定关键字中哪几位分布较均匀时,可以先求出关键字的平方值,然后按需要取平方值的中间几位作为哈希地址。这是因为:平方后中间几位和关键字中每一位都相关,故不同关键字会以较高的概率产生不同的哈希地址。
- 除留余数法:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p,p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词。
同样处理碰撞问题的方法也有很多种
常用的有:
- 线性探测: 如果
k1≠k2
,而f(k1)=f(k2)
, 就继续往后寻找, 找到一个空的位置, 然后放进去. - 链地址法: 这种方法数组的每个元素是一个链表的头指针, 如果
k1≠k2
,而f(k1)=f(k2)
, 就往链表中头插节点.
实现代码
线性探测
/*================================================================
# File Name: hash.h
# Author: rjm
# mail: [email protected]
# Created Time: 2018年05月18日 星期五 18时26分13秒
================================================================*/
// 哈希表
#pragma once
#define TEST_HEAD printf("\n==================%s===============\n", __FUNCTION__)
#define HashMaxSize 1000
typedef int KeyType; // 要插入的值
typedef int ValueType;
// 计算下标的函数
typedef int (*HashFunc)(KeyType key);
typedef enum Stat {
Empty, // 空状态, 表示这个位置还没有元素插入
valid, // 有效状态, 表示这个位置已经有元素插入了
deleted, // 已删除状态, 表示这个位置的元素已经被删除了
} Stat;
// 键值对
typedef struct HashElem {
KeyType key;
ValueType value;
Stat stat; //每个位置的状态
} HashElem;
int func(KeyType key)
{
return key % HashMaxSize;
}
typedef struct HashTable {
HashElem data[HashMaxSize]; //哈希表数组, 存的是包含一组键值对的结构体
size_t size; //哈希表当前有效元素的个数
HashFunc func; //计算下标的函数
} HashTable;
// 初始化
void HashInit(HashTable* ht, HashFunc hash_func);
// 插入元素
void HashInsert(HashTable* ht, KeyType key, ValueType value);
// 查找
int HashFind(HashTable* ht, KeyType key, int* to_find);
// 删除
void HashRemove(HashTable* ht, KeyType key);
// 销毁
void HashDestroy(HashTable* ht);
/*================================================================
# File Name: hash.c
# Author: rjm
# mail: [email protected]
# Created Time: 2018年05月18日 星期五 18时25分38秒
================================================================*/
// 哈希表
#include <stdio.h>
#include "hash.h"
// 初始化
void HashInit(HashTable* ht, HashFunc hash_func)
{
ht->size = 0;
ht->func = hash_func;
for(int i=0; i<HashMaxSize; i++)
{
ht->data[i].stat = Empty;
}
return ;
}
// 插入
void HashInsert(HashTable* ht, KeyType key, ValueType value)
{
if(ht->size == HashMaxSize)
{
// 哈希表已经满了
return ;
}
int offset = func(key);
ValueType val;
if(HashFind(ht, key, &val) == 1)
return ;
if(ht->data[offset].stat != valid)
{
ht->data[offset].key = key;
ht->data[offset].value = value;
ht->data[offset].stat = valid;
++ht->size;
}
else
{
while(1)
{
offset++;
if(offset == HashMaxSize)
{
offset = 0;
}
if(ht->data[offset].stat != valid)
break;
}
ht->data[offset].key = key;
ht->data[offset].value = value;
ht->data[offset].stat = valid;
++ht->size;
}
}
// 查找
int HashFind(HashTable* ht, KeyType key, int* to_find)
{
if(ht->size == 0)
{
// 哈希表为空
return 0;
}
int offset = func(key);
if(ht->data[offset].key == key)
{
*to_find = offset;
return 1;
}
else
{
while(ht->data[offset].stat != Empty)
{
++offset;
if(offset == HashMaxSize)
{
offset = 0;
}
if(ht->data[offset].key == key && ht->data[offset].stat == valid)
{
*to_find = offset;
return 1;
}
}
return 0;
}
}
// 删除
void HashRemove(HashTable* ht, KeyType key)
{
if(ht->size == 0)
{
// 哈希表为空
return ;
}
int offset = 0;
int ret = HashFind(ht, key, &offset);
if(ret == 0)
{
// 元素不存在
return ;
}
// 如果元素存在, 则把它的状态改为已删除
ht->data[offset].stat = deleted;
--ht->size;
}
// 销毁
void HashDestroy(HashTable* ht)
{
ht->size = 0;
ht->func = NULL;
for(int i = 0; i<HashMaxSize; i++)
{
ht->data[i].stat = Empty;
}
}
////////////////////////////////////
// 测试函数
////////////////////////////////////
void HashPrint(HashTable* ht, char* msg)
{
printf("[ %s ]\n", msg);
for(int i=0; i<HashMaxSize; i++)
{
if(ht->data[i].stat == valid)
{
printf("[%d| %d:%d]\n", i, ht->data[i].key, ht->data[i].value);
}
}
printf("\n");
}
void TestInit()
{
TEST_HEAD;
HashTable ht;
HashInit(&ht, func);
HashPrint(&ht, "初始化哈希表");
}
void TestInsert()
{
TEST_HEAD;
HashTable ht;
HashInit(&ht, func);
HashInsert(&ht, 1, 10);
HashInsert(&ht, 1001, 12);
HashInsert(&ht, 2, 10);
HashInsert(&ht, 2, 15);
HashInsert(&ht, 1002, 10);
HashPrint(&ht, "插入 5 个元素, 其中 2 插入两次");
}
void TestFind()
{
TEST_HEAD;
HashTable ht;
HashInit(&ht, func);
HashInsert(&ht, 1, 10);
HashInsert(&ht, 1001, 12);
HashInsert(&ht, 2, 10);
HashInsert(&ht, 2, 15);
HashInsert(&ht, 1002, 10);
int offset = 0;
int ret = 0;
HashPrint(&ht, "查找元素 1001");
ret = HashFind(&ht, 1001, &offset);
printf("ret expect 1, actual %d\n", ret);
printf("offset expect 2, actual %d\n", offset);
printf("\n");
HashPrint(&ht, "查找元素 1003");
ret = HashFind(&ht, 1003, &offset);
printf("ret expect 0, actual %d\n", ret);
}
void TestRemove()
{
TEST_HEAD;
HashTable ht;
HashInit(&ht, func);
HashInsert(&ht, 1, 10);
HashInsert(&ht, 1001, 12);
HashInsert(&ht, 2, 10);
HashInsert(&ht, 2, 15);
HashInsert(&ht, 1002, 10);
HashRemove(&ht, 1001);
HashPrint(&ht, "删除一个元素 1001");
HashRemove(&ht, 1002);
HashPrint(&ht, "再删除一个元素 1002");
HashRemove(&ht, 2);
HashPrint(&ht, "再删除一个元素 2");
HashRemove(&ht, 1);
HashPrint(&ht, "再删除一个元素 1");
HashRemove(&ht, 2);
HashPrint(&ht, "再删除一个元素 2");
HashRemove(&ht, 2);
HashPrint(&ht, "对空表删除");
printf("size = %d\n", ht.size);
}
void TestDestroy()
{
TEST_HEAD;
HashTable ht;
HashInit(&ht, func);
HashInsert(&ht, 1, 10);
HashInsert(&ht, 1001, 12);
HashInsert(&ht, 2, 10);
HashInsert(&ht, 2, 15);
HashInsert(&ht, 1002, 10);
HashDestroy(&ht);
HashPrint(&ht, "销毁哈希表");
}
int main()
{
TestInit();
TestInsert();
TestFind();
TestRemove();
TestDestroy();
printf("\n");
printf("\n");
printf("\n");
return 0;
}
链地址法
/*================================================================
# File Name: hash.h
# Author: rjm
# mail: [email protected]
# Created Time: 2018年05月20日 星期日 13时06分57秒
================================================================*/
// 哈希桶
// 数组的每个元素都是一张链表的头指针
#pragma once
#include <stdio.h>
#define TEST_HEAD printf("\n=================%s================\n", __FUNCTION__)
#define BucketMaxSize 1000 // 桶的个数为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 HashBucket {
HashElem* data[BucketMaxSize];
size_t size; // 当前元素的个数
HashFunc func;
} HashBucket;
// 初始化
void HashInit(HashBucket* hb, HashFunc func);
// 插入元素
void HashInsert(HashBucket* hb, KeyType key, ValType value);
// 查找元素
HashElem* HashFind(HashBucket* hb, KeyType key, ValType* value);
// 删除元素
void HashRemove(HashBucket* hb, KeyType key);
// 销毁
void HashDestroy(HashBucket* hb);
/*================================================================
# File Name: hash.c
# Author: rjm
# mail: [email protected]
# Created Time: 2018年05月20日 星期日 13时06分36秒
================================================================*/
#include <stdlib.h>
#include "hash.h"
int func(KeyType key)
{
return key % BucketMaxSize;
}
// 初始化
void HashInit(HashBucket* hb, HashFunc func)
{
if(hb == NULL)
return ;
for(int i=0; i<BucketMaxSize; i++)
{
hb->data[i] = NULL;
}
hb->size = 0;
hb->func = func;
}
// 创建新节点
HashElem* CreateNewNode(KeyType key, ValType value)
{
HashElem* he = (HashElem*)malloc(sizeof(HashElem));
he->key = key;
he->value = value;
he->next = NULL;
return he;
}
// 插入元素
void HashInsert(HashBucket* hb, KeyType key, ValType value)
{
if(hb == NULL)
return ;
// 根据key算出下标
int offset = func(key);
// 创建一个节点保存key和value
HashElem* new_node = CreateNewNode(key, value);
// 如果这个下标所保存链表的头指针为 NULL
// 就直接让头指针指向新节点
if(hb->data[offset] == NULL)
{
hb->data[offset] = new_node;
}
// 如果这个下标所保存链表的头指针已经指向了一个节点
// 就把新节点头插到这个链表中
else
{
if(key == hb->data[offset]->key)
{
// 元素相同, 不能插入
return ;
}
else
{
// 直接头插
HashElem* pre = hb->data[offset];
hb->data[offset] = new_node;
new_node->next = pre;
}
}
++hb->size;
}
// 查找元素
HashElem* HashFind(HashBucket* hb, KeyType key, ValType* value)
{
if(hb == NULL)
return NULL;
if(hb->size == 0)
return NULL;
int offset = func(key);
while(hb->data[offset] != NULL)
{
if(hb->data[offset]->key == key)
{
*value = hb->data[offset]->value;
return hb->data[offset];
}
hb->data[offset] = hb->data[offset]->next;
}
return NULL;
}
// 删除元素
void HashRemove(HashBucket* hb, KeyType key)
{
if(hb == NULL)
return ;
if(hb->size == 0)
return ;
int offset = func(key);
HashElem* cur = hb->data[offset];
HashElem* pre = hb->data[offset];
if(cur == NULL)
// 元素不存在
return ;
if(cur->next == NULL)
{
// 该位置的链表只有一个节点
free(hb->data[offset]);
hb->data[offset] = NULL;
--hb->size;
return ;
}
while(cur->next != NULL)
{
if(cur->key == key)
{
pre = cur;
}
cur = cur->next;
}
pre->next = cur->next;
free(cur);
cur = NULL;
--hb->size;
}
// 销毁
void HashDestroy(HashBucket* hb)
{
if(hb == NULL)
return ;
for(int i=0; i<BucketMaxSize; i++)
{
hb->data[i] = NULL;
}
hb->size = 0;
hb->func = NULL;
}
///////////////////////////////////////////
// 测试函数
///////////////////////////////////////////
void HashPrint(HashBucket* hb, char* msg)
{
printf("[ %s ]\n", msg);
if(hb == NULL)
return ;
if(hb->size == 0)
return ;
for(int i=0; i<BucketMaxSize; i++)
{
HashElem* he = hb->data[i];
if(hb->data[i] != NULL)
{
printf("[%d]\n", i);
while(hb->data[i] != NULL)
{
printf("---[%d:%d]\n", hb->data[i]->key, hb->data[i]->value);
hb->data[i] = hb->data[i]->next;
}
}
hb->data[i] = he;
}
}
void TestInit()
{
TEST_HEAD;
HashBucket hb;
HashInit(&hb, func);
HashPrint(&hb, "初始化哈希桶");
}
void TestInsert()
{
TEST_HEAD;
HashBucket hb;
HashInit(&hb, func);
HashInsert(&hb, 1001, 18);
HashInsert(&hb, 1007, 13);
HashInsert(&hb, 7, 11);
HashInsert(&hb, 101, 12);
HashPrint(&hb, "插入 4 个元素");
HashInsert(&hb, 101, 12);
HashPrint(&hb, "再插入一个已有的元素");
}
void TestFind()
{
TEST_HEAD;
HashBucket hb;
HashInit(&hb, func);
HashInsert(&hb, 1001, 18);
HashInsert(&hb, 1007, 13);
HashInsert(&hb, 7, 11);
HashInsert(&hb, 101, 12);
ValType value;
HashElem* ret = HashFind(&hb, 1007, &value);
printf("[查找元素 1007]\n");
printf("ret excepted 1007, actual %d\n", ret->key);
printf("value excepted 13, actual %d\n", value);
ret = HashFind(&hb, 1008, &value);
printf("[查找不存在的元素 1008]\n");
printf("ret excepted NULL, actual %p\n", ret);
}
void TestRemove()
{
TEST_HEAD;
HashBucket hb;
HashInit(&hb, func);
HashInsert(&hb, 1001, 18);
HashInsert(&hb, 1007, 13);
HashInsert(&hb, 7, 11);
HashInsert(&hb, 101, 12);
HashPrint(&hb, "插入 4 个元素");
HashRemove(&hb, 1007);
HashPrint(&hb, "删除一个元素 1007");
HashRemove(&hb, 1001);
HashRemove(&hb, 7);
HashPrint(&hb, "再删除2个元素 1001, 7");
HashRemove(&hb, 101);
HashPrint(&hb, "再删除1个元素 101");
HashRemove(&hb, 101);
HashPrint(&hb, "对空表进行删除");
}
void TestDestroy()
{
TEST_HEAD;
HashBucket hb;
HashInit(&hb, func);
HashInsert(&hb, 1001, 18);
HashInsert(&hb, 1007, 13);
HashInsert(&hb, 7, 11);
HashInsert(&hb, 101, 12);
HashPrint(&hb, "插入 4 个元素");
HashDestroy(&hb);
HashPrint(&hb, "销毁哈希表");
}
int main()
{
TestInit();
TestInsert();
TestFind();
TestRemove();
TestDestroy();
printf("\n");
printf("\n");
printf("\n");
return 0;
}