C编程练习|【数据结构】哈希表及哈希桶的基本操作

顺序搜索和二叉搜索树中,元素存储位置和元素各关键码之间没有对应的关系,这就导致在查找一个元素时,必须经过关键码的多次比较。那么是否有这样一种数据结构,可以不经过任何比较,直接找到想要搜索的元素呢?答案是肯定的,那就是通过某种函数(hashFunc)使得元素的存储位置与它的关键码之间建立一种一一映射的关系,那么在查找时就可以快速地找到该元素,我们将这种数据的存储结构称为哈希。
哈希表的实现

hash.h

# ifndef __HASH_H__ # define __HASH_H__# include # include # include # include typedef int Key; // 关键码 typedef int Value; // 实际数据//KV结构 typedef struct KVPair { Key key; Value value; }KVPair; //哈希表里的状态 typedef enum State { EMPTY, // 之前没有存过数 EXIST, // 目前存在数 DELETED // 曾经存在数,现在被删除了 }State; //哈希表里每个数据的类型 typedef struct Element { State state; KVPair data; }Element; // 哈希函数的函数指针声明 typedef unsigned long (* HashFuncType)(Key, unsigned long); //哈希表 typedef struct HashTable { Element * table; // 实际存储空间 unsigned long capacity; // 容量 unsigned long size; // 实际已存的数据个数 HashFuncType hashFuncType; //哈希函数 }HashTable; unsigned long Mod(Key key, unsigned long capacity); // 取模函数 void HTInit(HashTable * pHT, unsigned long capacity, HashFuncType hashFunc); // 哈希表的初始化 unsigned long HTSearch(HashTable * pHT, Key key); // 哈希表的查找,找到返回下标,没找到的话返回(unsigned long)-1 unsigned long HTInsert(HashTable * pHT, Key key, Value value); // 哈希表的插入,如果插入成功则返回下标,失败(key重复)的话返回(unsigned long)-1 unsigned long HTRemove(HashTable * pHT, Key key); // 哈希表的删除,如果删除成功则返回0,失败(key不存在)的话返回(unsigned long)-1 void HTDestory(HashTable * pHT); // 哈希表的销毁# endif // __HASH_H__

hash.c
本篇实现的哈希函数是在除留余数法的基础上实现的,设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key % p(p<=m),将关键码转换成哈希地址。
即使哈希函数设计的再合理,也不可避免地会出现哈希冲突(哈希碰撞),这种现象表现为不同关键码通过相同的哈希函数计算出相同的哈希地址。
解决哈希冲突的常见方法是 闭散列 和 开散列。
闭散列,也叫作开放地址法,当发生哈希冲突时,如果哈希表还没装满,那就说明在哈希表中还有空闲位置,那么可以把关键码存放到表中“下一个”空闲位置。
接下来提供两种寻找空闲位置的方法,分别是线性探测法和二次检测法。
线性探测法:从发生冲突的位置开始,依次继续向后探测,直至找到空闲位置为止。
线性探测法,实现起来比较简单,但是有一个缺陷,那就是:一旦发生哈希冲突,所有的冲突就会连在一起,容易产生数据“堆积”,即就是不同的关键码占据了可利用的空闲位置,使得寻找某个关键码的位置就需要比较很多次,从而导致搜索的效率变低。
二次探测法:发生哈希冲突时,按照times * times(也就是二次方)的方式去检测空闲位置,这样的方式就是为了避免阶段性的哈希冲突造成的堵塞。
关于查找、插入以及删除,这里作出以下几点说明:
查找:如果找到对应的关键码那就表示找到了,如果找到空位那就表示没有找到。
插入:不能插入相同数字,这样会破坏哈希表的结构。
删除:如果存在哈希冲突,那么删除掉第一个存在哈希冲突的数字之后,该位置变为空,那么在查找第二个乃至其他与该数字存在哈希冲突关系的数字时,因为第一个位置空余的缘故,就会被认为没查找到。这里采用假删除,就是说在这个大的哈希表中,存在三种状态,分别是EMPTY、EXIST、DELETED,那么在删除某个数字的时候,把状态改为EMPTY即可。

#define _CRT_SECURE_NO_WARNINGS 1 /* * Copyright (c) 2018, code farmer from sust * All rights reserved. * * 文件名称:hash.c * 功能:哈希表基本操作内部实现细节 * * 当前版本:V1.0 * 作者:sustzc * 完成日期:2018年6月27日18:43:59 */# include "hash.h"/* * 函数名称:GetNextPrime * * 函数功能:在素数表中找一个最接近并且不小于num的素数,降低哈希冲突 * * 入口参数:num * * 出口参数:(unsigned long)-1 or _PrimeList[i] * * 返回类型:unsigned long */unsigned long GetNextPrime(unsigned long num) { unsigned long i = 0; const unsigned long PrimeSize = 28; static const unsigned long _PrimeList [28] = { 53ul, 97ul, 193ul, 389ul, 769ul, 1543ul, 3079ul, 6151ul, 12289ul, 24593ul, 49157ul, 98317ul, 196613ul, 393241ul, 786433ul, 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul, 50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul, 1610612741ul, 3221225473ul, 4294967291ul }; for (i = 0; i < PrimeSize; i++) { if (_PrimeList[i] >= num) { return _PrimeList[i]; } else { ; } } return (unsigned long)-1; }/* * 函数名称:Mod * * 函数功能:取模函数 * * 入口参数:key, capacity * * 出口参数:key % capacity * * 返回类型:unsigned long */unsigned long Mod(Key key, unsigned long capacity) { return (unsigned long)key % capacity; }/* * 函数名称:LinearDective * * 函数功能:线性探测法 * * 入口参数:index, capacity * * 出口参数:(index++) % capacity * * 返回类型:unsigned long */unsigned long LinearDective(unsigned long index, unsigned long capacity) { return (++index) % capacity; }/* * 函数名称:SquareDective * * 函数功能:二次探测法 * * 入口参数:index, capacity, times * * 出口参数:(index + times * times) % capacity * * 返回类型:unsigned long */unsigned long SquareDective(unsigned long index, unsigned long capacity, int times) { return (index + times * times) % capacity; }/* * 函数名称:HTInit * * 函数功能:初始化哈希表 * * 入口参数:pHT, capacity, hashFunc * * 出口参数:void * * 返回类型:void */void HTInit(HashTable * pHT, unsigned long capacity, HashFuncType hashFunc) { unsigned long i = 0; assert(NULL != pHT); capacity = GetNextPrime(capacity); pHT->table = (Element *)malloc(sizeof(Element) * capacity); assert(pHT->table); pHT->size = 0; pHT->capacity = capacity; pHT->hashFuncType = hashFunc; for (i = 0; i < capacity; i++) { pHT->table[i].state = EMPTY; } return; }/* * 函数名称:HTSearch * * 函数功能:查找哈希表中的关键码,找到返回下标,没找到的话返回(unsigned long)-1 * * 入口参数:pHT, key * * 出口参数:(unsigned long)-1 or index * * 返回类型:unsigned long */unsigned long HTSearch(HashTable * pHT, Key key) { int times = 1; unsigned long index = 0; unsigned long start = 0; assert(NULL != pHT); index = pHT->hashFuncType(key, pHT->capacity); while (EMPTY != pHT->table[index].state) { if ((key == pHT->table[index].data.key) && (EXIST == pHT->table[index].state)) { return index; } else { //线性探测 //index = LinearDective(index, pHT->capacity); //二次探测 index = SquareDective(start, pHT->capacity, times); times++; } } return (unsigned long)-1; }/* * 函数名称:ExpandIfRequired * * 函数功能:判断是否扩容,注意:扩容时不使用realloc, malloc重新分配内存 * * 入口参数:pHT * * 出口参数:(unsigned long)-1 or 0 * * 返回类型:void */void ExpandIfRequired(HashTable * pHT) { if ((pHT->size) * 10 / (pHT->capacity) < 7) { return; } else { unsigned long i = 0; HashTable newHT; unsigned long capacity = pHT->capacity * 2; HTInit(&newHT, capacity, pHT->hashFuncType); for (i = 0; i < pHT->capacity; i++) { if (EXIST == pHT->table[i].state) { HTInsert(&newHT, pHT->table[i].data.key, pHT->table[i].data.value); } else { ; } } free(pHT->table); pHT->table = newHT.table; pHT->capacity = newHT.capacity; } return; }/* * 函数名称:HTInsert * * 函数功能:插入数据,如果插入成功则返回下标,失败(key重复)的话返回(unsigned long)-1 * * 入口参数:pHT, key, value * * 出口参数:(unsigned long)-1 or 0 * * 返回类型:unsigned long */unsigned long HTInsert(HashTable * pHT, Key key, Value value) { int times = 1; unsigned long index = 0; unsigned long start = 0; assert(NULL != pHT); ExpandIfRequired(pHT); index = pHT->hashFuncType(key, pHT->capacity); start = index; while (EXIST == pHT->table[index].state) { if ((key == pHT->table[index].data.key)) { return (unsigned long)-1; } else { //线性探测 //index = LinearDective(index, pHT->capacity); //二次探测 index = SquareDective(start, pHT->capacity, times); times++; } } pHT->table[index].data.key = key; pHT->table[index].data.value = https://www.it610.com/article/value; pHT->table[index].state = EXIST; pHT->size++; return 0; }/* * 函数名称:HTRemove * * 函数功能:删除数据,如果删除成功则返回0,失败(key不存在)的话返回(unsigned long)-1 * * 入口参数:pHT, key * * 出口参数:(unsigned long)-1 or 0 * * 返回类型:unsigned long */unsigned long HTRemove(HashTable * pHT, Key key) { int times = 1; unsigned long index = 0; unsigned long start = 0; assert(NULL != pHT); index = pHT->hashFuncType(key, pHT->capacity); start = index; while (EMPTY != pHT->table[index].state) { if ((key == pHT->table[index].data.key) && (EXIST == pHT->table[index].state)) { pHT->table[index].state = DELETED; pHT->size--; return 0; } else { //线性探测 //index = LinearDective(index, pHT->capacity); //二次探测 index = SquareDective(start, pHT->capacity, times); times++; } } return (unsigned long)-1; }/* * 函数名称:HTDestory * * 函数功能:销毁哈希表 * * 入口参数:pHT * * 出口参数:void * * 返回类型:void */void HTDestory(HashTable * pHT) { assert(NULL != pHT); free(pHT->table); return; }

test.c
#define _CRT_SECURE_NO_WARNINGS 1/* * Copyright (c) 2018, code farmer from sust * All rights reserved. * * 文件名称:test.c * 功能:测试哈希表基本操作 * * 当前版本:V1.0 * 作者:sustzc * 完成日期:2018年6月27日18:44:52 */# include "hash.h"/* * 函数名称:main * * 函数功能:测试主程序 * * 入口参数:void * * 出口参数:0 * * 返回类型:int */int main(void) { //测试线性探测法 //int i = 0; //HashTable hashTable; //int numbers[] = {1, 19, 38, 27, 19, 27, 30, 20, 73, 126}; //HTInit(&hashTable, 10, Mod); //for (i = 0; i < sizeof(numbers) / sizeof(int); i++) //{ // unsigned long index = HTSearch(&hashTable, numbers[i]); // if ((unsigned long)-1 != index) // { //hashTable.table[index].data.value++; // } // else // { //HTInsert(&hashTable, numbers[i], 1); // } //} //HTRemove(&hashTable, 45); //HTRemove(&hashTable, 20); //HTInsert(&hashTable, 179, 1); //printf("查找%d, 查找结果是%lu\n", 179, HTSearch(&hashTable, 179)); //测试二次探测法 int i = 0; HashTable hashTable; int numbers[] = {0, 53, 106, 159, 212}; HTInit(&hashTable, 10, Mod); for (i = 0; i < sizeof(numbers) / sizeof(int); i++) { HTInsert(&hashTable, numbers[i], 285); } HTDestory(&hashTable); return 0; }

哈希桶的实现
HashNode.h
# ifndef __HASHNODE_H__ # define __HASHNODE_H__# include # include # include # include typedef int Key; // 关键码 typedef int Value; // 实际数据//KV结构 typedef struct KVPair { Key key; Value value; }KVPair; typedef KVPair DataType; //哈希桶里每个结点的类型 typedef struct Node { DataType data; struct Node * pNext; }Node; typedef Node * SDataType; // 哈希函数的函数指针声明 typedef unsigned long (* HashFuncType)(Key, unsigned long); //哈希桶 typedef struct HashBucket { SDataType * table; // 实际存储空间也就是个指针数组 unsigned long capacity; // 容量 unsigned long size; // 实际已存的数据个数 HashFuncType hashFuncType; //哈希函数 }HashBucket; unsigned long Mod(Key key, unsigned long capacity); // 取模函数 void HTInit(HashBucket * pHB, unsigned long capacity, HashFuncType hashFunc); // 哈希桶的初始化 void HTPrint(HashBucket * pHB); // 打印哈希桶 SDataType HTSearch(HashBucket * pHB, Key key); // 查找哈希桶中的关键码,如果找到了返回pCurNode的地址,没有找到的话返回NULL unsigned long HTInsert(HashBucket * pHB, Key key, Value value); // 哈希桶的插入,如果插入成功则返回下标,失败(key重复)的话返回(unsigned long)-1 unsigned long HTRemove(HashBucket * pHB, Key key); // 哈希桶的删除,如果删除成功则返回0,失败(key不存在)的话返回(unsigned long)-1 void HTDestory(HashBucket * pHB); // 哈希桶的销毁# endif // __HASHNODE_H__

HashNode.c
开散列法,又叫做链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一个子集合,每一个子集合称为一个桶,每个桶中的元素通过一个单链表链接起来,各链表的头结点存储于哈希表中。
这里需要注意的是哈希桶的销毁,应该先释放链表开辟的结点,再去释放指针数组。

#define _CRT_SECURE_NO_WARNINGS 1/* * Copyright (c) 2018, code farmer from sust * All rights reserved. * * 文件名称:HashNode.c * 功能:哈希桶基本操作内部实现细节 * * 当前版本:V1.0 * 作者:sustzc * 完成日期:2018年6月27日18:43:59 */# include "HashNode.h"/* * 函数名称:GetNextPrime * * 函数功能:在素数表中找一个最接近并且不小于num的素数,降低哈希冲突 * * 入口参数:num * * 出口参数:(unsigned long)-1 or _PrimeList[i] * * 返回类型:unsigned long */unsigned long GetNextPrime(unsigned long num) { unsigned long i = 0; const unsigned long PrimeSize = 28; static const unsigned long _PrimeList [28] = { 53ul, 97ul, 193ul, 389ul, 769ul, 1543ul, 3079ul, 6151ul, 12289ul, 24593ul, 49157ul, 98317ul, 196613ul, 393241ul, 786433ul, 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul, 50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul, 1610612741ul, 3221225473ul, 4294967291ul }; for (i = 0; i < PrimeSize; i++) { if (_PrimeList[i] >= num) { return _PrimeList[i]; } else { ; } } return (unsigned long)-1; }/* * 函数名称:Mod * * 函数功能:取模函数 * * 入口参数:key, capacity * * 出口参数:key % capacity * * 返回类型:unsigned long */unsigned long Mod(Key key, unsigned long capacity) { return (unsigned long)key % capacity; }/* * 函数名称:HTInit * * 函数功能:初始化哈希桶 * * 入口参数:pHB, capacity, hashFunc * * 出口参数:void * * 返回类型:void */void HTInit(HashBucket * pHB, unsigned long capacity, HashFuncType hashFunc) { unsigned long i = 0; assert(NULL != pHB); //产生素数的容量,减少哈希冲突 capacity = GetNextPrime(capacity); pHB->table = (SDataType *)malloc(sizeof(SDataType) * capacity); assert(pHB->table); pHB->size = 0; pHB->capacity = capacity; pHB->hashFuncType = hashFunc; for (i = 0; i < pHB->capacity; i++) { //实际上这里的数组元素都是存放了单链表的头指针 pHB->table[i] = NULL; } return; }/* * 函数名称:HTPrint * * 函数功能:打印哈希桶 * * 入口参数:pHB * * 出口参数:void * * 返回类型:void */void HTPrint(HashBucket * pHB) { unsigned long i = 0; assert(NULL != pHB); for (i = 0; i < pHB->capacity; i++) { SDataType pCurNode = pHB->table[i]; printf("table[%d]->", i); while (NULL != pCurNode) { printf("%d ->", pCurNode->data.key); pCurNode = pCurNode->pNext; }printf("NULL\n"); } return; }/* * 函数名称:HTSearch * * 函数功能:查找哈希桶中的关键码,如果找到了返回pCurNode的地址,没有找到的话返回NULL * * 入口参数:pHB, key * * 出口参数:NULL or pCurNode * * 返回类型:SDataType */SDataType HTSearch(HashBucket * pHB, Key key) { unsigned long index = 0; SDataType pCurNode = NULL; assert(NULL != pHB); index = pHB->hashFuncType(key, pHB->capacity); pCurNode = pHB->table[index]; while (NULL != pCurNode) { if (key == pCurNode->data.key) { // key已经存在 return pCurNode; } else { pCurNode = pCurNode->pNext; } } return NULL; }/* * 函数名称:HTInsert * * 函数功能:插入数据,如果插入成功则返回下标,失败(key重复)的话返回(unsigned long)-1 * * 入口参数:pHB, key, value * * 出口参数:(unsigned long)-1 or 0 * * 返回类型:unsigned long */unsigned long HTInsert(HashBucket * pHB, Key key, Value value) { unsigned long index = 0; SDataType pCurNode = NULL; SDataType pNewNode = NULL; assert(NULL != pHB); index = pHB->hashFuncType(key, pHB->capacity); pCurNode = pHB->table[index]; while (NULL != pCurNode) { if (key == pCurNode->data.key) { // key已经存在 return (unsigned long)-1; } else { pCurNode = pCurNode->pNext; } } // key 不在哈希桶里 pNewNode = (SDataType)malloc(sizeof(Node)); assert(NULL != pNewNode); pNewNode->data.key = key; pNewNode->data.value = https://www.it610.com/article/value; pHB->size++; pNewNode->pNext = pHB->table[index]; pHB->table[index] = pNewNode; return 0; }/* * 函数名称:HTRemove * * 函数功能:删除数据,如果删除成功则返回0,失败(key不存在)的话返回(unsigned long)-1 * * 入口参数:pHB, key * * 出口参数:(unsigned long)-1 or 0 * * 返回类型:unsigned long */unsigned long HTRemove(HashBucket * pHB, Key key) { unsigned long index = 0; SDataType pCurNode = NULL; SDataType pNextNode = NULL; SDataType pPrevNode = NULL; assert(NULL != pHB); index = pHB->hashFuncType(key, pHB->capacity); pCurNode = pHB->table[index]; while (NULL != pCurNode) { if (key == pCurNode->data.key) { // 删除的是第一个 if (NULL == pPrevNode) { pHB->table[index] = pCurNode->pNext; } else { pPrevNode->pNext = pCurNode->pNext; }free(pCurNode); return 0; } else { pPrevNode = pCurNode; pCurNode = pCurNode->pNext; } } return (unsigned long)-1; }/* * 函数名称:HTDestory * * 函数功能:销毁哈希桶,需要注意的是,先销毁链表的结点,然后再销毁指针数组 * * 入口参数:pHB * * 出口参数:void * * 返回类型:void */void HTDestory(HashBucket * pHB) { unsigned long i = 0; SDataType pCurNode = NULL; SDataType pNextNode = NULL; assert(NULL != pHB); for (i = 0; i < pHB->capacity; i++) { for (pCurNode = pHB->table[i]; NULL != pCurNode; pCurNode = pNextNode) { pNextNode = pCurNode->pNext; free(pCurNode); } } free(pHB->table); return; }

【C编程练习|【数据结构】哈希表及哈希桶的基本操作】HashNodeTest.c
#define _CRT_SECURE_NO_WARNINGS 1/* * Copyright (c) 2018, code farmer from sust * All rights reserved. * * 文件名称:HashNodeTest.c * 功能:测试哈希桶基本操作 * * 当前版本:V1.0 * 作者:sustzc * 完成日期:2018年6月27日18:44:23 */# include "HashNode.h"/* * 函数名称:main * * 函数功能:测试主程序 * * 入口参数:void * * 出口参数:0 * * 返回类型:int */int main(void) { HashBucket hb; int i = 0; SDataType pFound = NULL; //int numbers[] = {1, 53, 1, 53, 106}; int numbers[] = {1, 23, 46, 53, 106}; HTInit(&hb, 5, Mod); printf("------------------before:\n"); HTPrint(&hb); for (i = 0; i < sizeof(numbers) / sizeof(int); i++) { pFound = HTSearch(&hb, numbers[i]); if (NULL != pFound) { pFound->data.value++; } else { HTInsert(&hb, numbers[i], 1); } } printf("\n------------------after:\n"); HTPrint(&hb); HTRemove(&hb, 23); printf("\n------------------delete:\n"); HTPrint(&hb); return 0; }

    推荐阅读