哈希表,是根据键值对(Key --- value)进行访问的数据结构。也就是说,它通过把key值映射到表中一个位置来访问记录,以加快查找的速度。
给定表ht,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
总结上面的所说:
若在Hash表中存放数据的是一个结构体数组,结构体包括了key值、value值和表示数据状态的stat,共三个属性,假如这个数组有n个元素,那么就对应了0~n-1个位置,而要插入的每个元素都有一个key值,通过哈希函数f(key)将转为offset,这个offset的值就在0~n-1之间,然后在数组对应的offset位置上填入key值和value值,并将stat属性置为Valid(有效)
一个好的hash函数使得每个offset尽量均匀
哈希冲突:
简单的来说,当哈希函数计算当前key值得到的offset 与 之前一个key值计算得到的offset相等,这就是哈希冲突
解决哈希冲突
解决哈希冲突的办法主要有两种: 1) 闭散列 2) 开散列
1)闭散列:
当通过哈希函数计算得到的offset,如果这个位置可以插入,就直接插入,如果这个位置已经被占,那么就顺着往后查找,知道找到第一个可以插入的位置,在进行插入操作。
当通过哈希函数计算得到的offset,如果这个位置可以插入,就直接插入,如果这个位置已经被占,那么就顺着往后查找,知道找到第一个可以插入的位置,在进行插入操作。
原理图:
代码如下:
头文件(hash.h)
#pragma once
#include<unistd.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 func;
} HashTable;
void HashInit(HashTable* ht, HashFunc 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.c)
#include"hash.h"
// 哈虚函数-->对数组的最大值取余
size_t HashFuncDefault(KeyType key) {
return key%HashMaxSize;
}
//判断hash表是否是空
// -1 表示非法输入
// 0 表示空
// 1 表示非空
int HashEmpty(HashTable* ht) {
if(ht == NULL) {
return -1;
}
if(ht->size == 0) {
return 0;
}
return 1;
}
//获取hash的大小
size_t HashSize(HashTable* ht) {
if(ht == NULL) {
return 0;
}
return ht->size;
}
//hash表的初始化
void HashInit(HashTable* ht, HashFunc hash_func) {
if(ht == NULL) {
return;
}
ht->func = hash_func;
ht->size = 0;
size_t i;
for(i=0; i<HashMaxSize; i++) {
ht->data[i].stat = Empty;
}
}
//销毁hash表-->将所有元素的状态置为Empty
void HashDestroy(HashTable* ht) {
if(ht == NULL) {
return;
}
ht->func = NULL;
ht->size = 0;
size_t i = 0;
for(i=0; i<HashMaxSize; i++) {
ht->data[i].stat = Empty;
}
}
//向hash表中插入
int HashInsert(HashTable* ht, KeyType key, ValType value) {
if(ht == NULL) {
return -1;
}
//判断hash表是否能够再插入元素(根据负载因子)
if(ht->size > HashMaxSize * 0.8) {
return -1;
}
//根据key来计算offset
size_t offset = ht->func(key);
//从offset位置开始线性想后面查找,找到第一个为offset的位置插入
while(1) {
if(ht->data[offset].stat != Valid) {
//找到一个合适的位置插入
ht->data[offset].stat = Valid;
ht->data[offset].value = value;
ht->data[offset].key = key;
ht->size++;
} else if(ht->data[offset].stat == Valid && ht->data[offset].value == value){
//找到相同的元素直接返回
return 0;
} else {
offset++;
if(offset >= HashMaxSize) {
offset = 0;
}
}
}
}
//在中按key值查找元素
int HashFind(HashTable* ht, KeyType key, ValType* value) {
if(ht == NULL) {
//非法输入
return 0;
}
if(ht->size == 0) {
//空Hash表
return 0;
}
//根据key值计算offset
size_t offset = ht->func(key);
//从offset开始往后进行查找,没取到一个元素 和key进行比较
while(1) {
//如果找到key值相同,且有效的元素,查找成功,并返回value
if(ht->data[offset].key == key && ht->data[offset].stat == Valid) {
*value = ht->data[offset].value;
return 1;
}
//找到的位置为空,则说明要找的位置不存在
else if(ht->data[offset].stat == Empty) {
return 0;
}
//key值不相等,向后茶轴
else {
offset++;
if(offset > HashMaxSize) {
offset = 0;
}
}
}
}
//在hash表中 按key值删除元素
void HashRemove(HashTable* ht, KeyType key) {
if(ht == NULL) {
return;
}
if(ht->size == 0) {
return;
}
//根据key值计算offset
size_t offset = ht->func(key);
size_t flag = offset;
while(ht->data[offset].stat != Empty) {
if(ht->data[offset].stat == Valid && ht->data[offset].key == key) {
ht->data[offset].stat = Invalid;
return;
}
offset++;
if(offset == flag) {
return;
}
if(offset == HashMaxSize)
offset = 0;
}
}
2)开散列
当计算的到大量的相同的offset值,就在当前位置形成一个链表,把这些元素组织起来,在每一条链表上的key值可能不相同,但是通过哈希哈数计算出的offset一定相同
原理图:
源代码:
头文件(hash.h)
#pragma once
#include <unistd.h>
#define HashMaxSize 1000
typedef int KeyType;
typedef int ValType;
typedef size_t (*HashFunc)(KeyType key);
typedef struct HashElem {
KeyType key;
ValType value;
struct HashElem* next;
} HashElem;
// 数组的每一个元素是一个不带头结点的链表
// 对于空链表, 我们使用 NULL 来表示
typedef struct HashTable {
HashElem* data[HashMaxSize];
size_t size;
HashFunc hash_func;
} HashTable;
void HashInit(HashTable* ht, HashFunc hash_func);
// 约定哈希表中不能包含 key 相同的值.
int HashInsert(HashTable* ht, KeyType key, ValType value);
int HashFind(HashTable* ht, KeyType key, ValType* value);
void HashRemove(HashTable* ht, KeyType key);
size_t HashSize(HashTable* ht);
int HashEmpty(HashTable* ht);
void HashDestroy(HashTable* ht);
头文件实现(hash.c)
#include"hash.h"
#include<stdio.h>
#include<stdlib.h>
size_t hash_func(KeyType key) {
return key % HashMaxSize;
}
void HashInit(HashTable* ht, HashFunc hash_func) {
if(ht == NULL || hash_func == NULL) {
return;
}
int i = 0;
for(; i<HashMaxSize; i++) {
ht->data[i] = NULL;
}
ht->size = 0;
ht->hash_func = hash_func;
}
//插入函数返回值
//成功 返回1
//失败 返回-1
int HashInsert(HashTable* ht, KeyType key, ValType value) {
if(ht == NULL) {
return -1;
}
if(key < 0) {
return -1;
}
size_t offset = ht->hash_func(key);
HashElem* head = ht->data[offset];
//当前offset位置没有元素的情况
if(head == NULL) {
ht->data[offset] = (HashElem*)malloc(sizeof(HashElem));
ht->data[offset]->key = key;
ht->data[offset]->value = value;
ht->data[offset]->next = NULL;
return 1;
}
//当前offset有元素的情况
for(; head->next!=NULL; head=head->next);
head->next = (HashElem*)malloc(sizeof(HashElem));
head = head->next;
head->key = key;
head->value = value;
head->next = NULL;
return 1;
}
//在hash表中 通过key查找value
int HashFind(HashTable* ht, KeyType key, ValType* value) {
if(ht == NULL || value == NULL) {
return -1;
}
if(key < 0) {
return -1;
}
KeyType offset = ht->hash_func(key);
HashElem* head = ht->data[offset];
for(; head != NULL; head = head->next) {
if(head->key == key) {
*value = head->value;
return 1;
}
}
return 0;
}
//在hash表中,通过key值删除一个元素
void HashRemove(HashTable* ht, KeyType key) {
if(ht == NULL) {
return;
}
if(key < 0) {
return;
}
KeyType offset = ht->hash_func(key);
HashElem* head = ht->data[offset];
if(head == NULL) {
return;
}
while(head->next != NULL) {
if(head->next->key == key) {
HashElem* cur = head->next;
head->next = cur->next;
free(cur);
return;
}
head = head->next;
}
}
//得到当前hash表已占用的位置的格式
//而不是 hash表中元素的个数
size_t HashSize(HashTable* ht){
if(ht == NULL) {
return 0;
}
return ht->size;
}
// 判断hash表是否为空
// 输入错位 返回-1
// 为空 返回0
// 不为空 返回1
int HashEmpty(HashTable* ht) {
if(ht == NULL) {
return -1;
}
if(ht->size == 0) {
return 0;
}
return 1;
}
//递归free
//如果hash表上的一个位置上有很多个元素,则这些元素被一个链表组织起来
//head所指向hash表的一个位置上的链表的头部
void hash_free(HashElem* head) {
if(head == NULL) {
return;
}
hash_free(head->next);
free(head);
}
//删除哈希表
void HashDestroy(HashTable* ht) {
if(ht == NULL) {
return;
}
size_t i = 0;
for(; i<HashMaxSize; i++) {
if(ht->data[i] != NULL) {
hash_free(ht->data[i]);
}
} //end of for
ht->hash_func = NULL;
}