#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <assert.h>
typedef int Key;
typedef int(*HashFunc)(Key, int);
//定义每个位置的状态信息
typedef enum {
EXISTED,
DELETED,
EMPTY
} State;
//哈希表中每个元素不仅包含关键字,还有其状态信息
typedef struct Element{
Key key;
State state;
} Element;
//哈希表,包括元素类型,有效数据大小,容量和哈希函数
typedef struct HashTable {
Element *arr;
int size;
int capacity;
HashFunc hashfunc;
} HashTable;
//初始化,初始化表的大小,哈希函数,元素关键字和状态
void HashInit(HashTable *Hash, int capacity, HashFunc hashfunc)
{
assert(Hash);
Hash->arr = (Element *)malloc(sizeof(Element)*capacity);
assert(Hash->arr);
int index;
for (index = 0; index < capacity; index++) {
Hash->arr[index].key = 0;
Hash->arr[index].state = EMPTY;
}
Hash->size = 0;
Hash->capacity = capacity;
Hash->hashfunc = hashfunc;
}
//查找,传入想要查找的关键字,通过hashfunc计算出可能的下标
//首先数据在表中插入的时候先判断当前位置是否为空,如果不为空就接着往后直到找到空位置
//所以在查找的时候只需要查找从当前位置到往后第一个状态为空的位置,如果遇到空了还没找到就表示不存在
//需要注意查找到的第一条件是当前位置状态不为EMPTY,第二条件是状态为EXISTED而不是DELETED
//其次才是比较关键字是否相等
//查找的时候hashfunc开始计算的下标可能不是表的开头位置,所以需要将下标循环起来,以确保能查找到每个位置
//但是这样的话可能出项死循环,只需要判断查找次数是否大于capacity就行了
int Search(HashTable *Hash, Key key)
{
int index = Hash->hashfunc(key, Hash->capacity);
int iCount = 0;
while (Hash->arr[index].state != EMPTY) {
if (Hash->arr[index].key == key && Hash->arr[index].state == EXISTED) {
return 1;
}
//缺点 容易出现死循环
index = (index + 1) % Hash->capacity;
iCount++;
if (iCount >= Hash->capacity) {
return -1;
}
}
return -1;
}
//由于负载因子的存在,在表中有效数据占总容量的比例不得大于0.7
//所以在插入之前需要判断是否要扩容
void ExpandIfNeed(HashTable *Hash);
//通过关键字计算出下标,
int Insert(HashTable *Hash, Key key)
{
ExpandIfNeed(Hash);
int iCount = 1;
int index = Hash->hashfunc(key, Hash->capacity);
//while循环的作用是找到表中状态不为EXISTED的位置
while (Hash->arr[index].state == EXISTED) {
//确保已存在的key不是处于假删除状态
if (Hash->arr[index].key == key && Hash->arr[index].state == EXISTED) {
return -1;
}
//二次探测法
index = (index + iCount*iCount) % Hash->capacity;
iCount++;
}
Hash->arr[index].key = key;
Hash->arr[index].state = EXISTED;
return 1;
}
//扩容操作
//如果负载因子超标,则执行次操作
//初始化一个容量为之前二倍的新标
//将原来的元素重新按规则而不是无脑插入到新表中
//完成之后释放旧表,拿到新表
void ExpandIfNeed(HashTable *Hash)
{
assert(Hash);
if (Hash->size * 10 / Hash->capacity < 7) {
return;
}
HashTable newHash;
HashInit(&newHash, Hash->capacity * 2, Hash->hashfunc);
int i = 0;
for (i = 0; i < newHash.capacity; i++) {
if (Hash->arr[i].state == EXISTED) {
Insert(&newHash, Hash->arr[i].key);
}
}
free(Hash->arr);
Hash->arr = newHash.arr;
Hash->capacity = newHash.capacity;
}
//删除------假删除
//只所以要假删除是因为在表中数据不是连续排列的
//而查找的操作可能要对表中的位置挨个进行访问,如果遇到EMPTY的结点就会退出
int Remove(HashTable *Hash, Key key)
{
assert(Hash);
int index = Hash->hashfunc(key, Hash->capacity);
while (Hash->arr[index].state != EMPTY) {
if (Hash->arr[index].key == key && Hash->arr[index].state == EXISTED) {
Hash->arr[index].state = DELETED;
Hash->size--;
return 1;
}
index = (index + 1) % Hash->capacity;
}
return -1;
}
void Destory(HashTable *Hash)
{
free(Hash->arr);
Hash->arr = NULL;
}
//哈希函数,除留余数法
//还有直接定制法, 平方取中法,折叠法,随机数法,数学分析法
//哈希函数越巧妙,哈希碰撞的几率就越低,但是不可避免
int chuliuyushufa(Key key, int capacity) {
return key % capacity;
}
void HashPrint(HashTable *Hash)
{
assert(Hash);
int index = 0;
for (index = 0; index < Hash->capacity; index++) {
printf("key : %-2d index : %-2d\n", Hash->arr[index].key, index);
}
}
int main()
{
HashTable Hash;
HashInit(&Hash, 13, chuliuyushufa);
Insert(&Hash, 3);
Insert(&Hash, 7);
Insert(&Hash, 19);
Insert(&Hash, 25);
Insert(&Hash, 26);
Insert(&Hash, 6);
Insert(&Hash, 12);
Insert(&Hash, 39);
Insert(&Hash, 41);
Insert(&Hash, 32);
HashPrint(&Hash);
//走到这一步会触发扩容
Insert(&Hash, 45);
HashPrint(&Hash);
system("pause");
return 0;
}