为什么要引入哈希表?
顺序搜索以及二叉搜索树中,元素存储位置和元素各关键码之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。搜索的效率取决于搜索过程中元素的比较次数。
我们希望可以不经过任何比较,一次直接从表中得到要搜索的元素。
哈希表的特点
通过哈希(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通
过该函数可以很快找到该元素。
哈希冲突
通过计算key值的偏移量来确定键值对在哈希表的位置,会发生不同的key值偏移量却相同的情况,这一现象称为哈希冲突。
解决哈希冲突的方法
闭散列:也叫开放地址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到表中“下一个” 空位中去。
开散列:开散列法又叫链地址法(开链法)。首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
通过代码来实现开散列和闭散列
闭散列
hash_table.h
#include<stddef.h>
typedef int keyType;
typedef int valueType;
typedef size_t (*hashFuncDefault)(keyType key);
typedef enum Stat{
Empty,
Valid,
Deleted
}Stat;
#define HashMaxSize 1000
typedef struct hashElem{//键值对结构体
keyType key;
valueType value;
Stat stat;//引入一个stat标记作为是否有效的标记
}hashElem;
typedef struct hashTable{//哈希表结构体
hashElem data[HashMaxSize];
size_t size;
hashFuncDefault hash_func;
}hashTable;
void HashInit(hashTable* ht,hashFuncDefault hash_func);//对哈希表进行初始化
int HashInsert(hashTable* ht,keyType key,valueType value);//在哈希表中插入键值对
int HashFind(hashTable* ht,keyType key,valueType* 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"
#include<stdio.h>
size_t HashFunc(keyType key){
return key%HashMaxSize;
}
void HashInit(hashTable* ht,hashFuncDefault 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,valueType value){
if(ht==NULL){
return 0;
}
if(ht->size>=HashMaxSize*0.8){
return 0;
}
//2.根据key来计算offset
size_t offset=ht->hash_func(key);
//3.从offset位置开始线性的往后找,找到第一个状态为empty这样的元素来插入。
while(1){
if(ht->data[offset].stat!=Valid){
ht->data[offset].key=key;
ht->data[offset].value=value;
ht->data[offset].stat=Valid;
++ht->size;
return 1;
}else{
if(ht->data[offset].key==key){
return 0;
}
++offset;
}
}
//4.如果发现了key相同的元素,此时认为插入失败。
//5.++size
}
int HashFind(hashTable* ht,keyType key,valueType* value){
if(ht==NULL){
return 0;
}
//1.根据key计算出offset
//2.从offset开始往后开始查找,每次取到一个元素,使用key进行比较
//如果找到了key相同的元素,此时直接把value返回回去进行,并且认为查找成功。
//如果发现当前的key不相同,就继续往后查找
//如果发现当前的元素是一个空元素,此时认为查找失败
size_t offset=ht->hash_func(key);
while(1){
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;
}
++offset;
}
}
}
void HashRemove(hashTable* ht,keyType key){
if(ht==NULL){
return;
}
//1.根据key计算offset
//2.从offset开始,一次判定当前元素的key和要删除元素的key是不是相同
//如果当前的key就是要删除的key,删除当前元素即可,删除元素要引入一个新的状态标记Deleted
//如果当前的元素为空元素,key在hash表中没有找到,删除失败
//剩下的情况++offset,线性探测尝试查找下一个元素。
size_t offset=ht->hash_func(key);
while(1){
if(ht->data[offset].key==key&&ht->data[offset].stat==Valid){
ht->data[offset].stat=Deleted;
--ht->size;
return;
}else{
if(ht->data[offset].stat==Empty){
printf("key不存在\n");
return;
}
++offset;
}
}
}
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){
if(ht==NULL){
return;
}
size_t i=0;
for(;i<HashMaxSize;i++){
ht->data[i].stat=Empty;
}
ht->size=0;
ht->hash_func=NULL;
return;
}
///
#define HeaderPrint printf("\n==========%s=========\n",__FUNCTION__)
void HashPrint(hashTable* ht,const char* msg){
if(ht==NULL){
return;
}
printf("%s\n",msg);
size_t i=0;
for(;i<HashMaxSize*0.8;i++){
if(ht->data[i].stat==Valid){
printf("[%u]key:%lu value:%lu\n",i,ht->data[i].key,ht->data[i].value);
}
}
}
void TesthashInit(){
HeaderPrint;
hashTable ht;
HashInit(&ht,HashFunc);
HashInsert(&ht,1,1);
HashInsert(&ht,2,11);
HashInsert(&ht,5,9);
HashInsert(&ht,101,1);
HashInsert(&ht,101,2);
HashPrint(&ht,"往哈希表插入四个元素");
}
void TestFind(){
HeaderPrint;
hashTable ht;
HashInit(&ht,HashFunc);
HashInsert(&ht,1,1);
HashInsert(&ht,2,11);
HashInsert(&ht,5,9);
HashInsert(&ht,101,1);
valueType value;
int ret=HashFind(&ht,102,&value);
printf("expect 0,actual %d\n",ret);
int ret1=HashFind(&ht,2,&value);
printf("expect 11,actual %d\n",value);
}
void TestRemove(){
HeaderPrint;
hashTable ht;
HashInit(&ht,HashFunc);
HashInsert(&ht,1,1);
HashInsert(&ht,2,11);
HashInsert(&ht,5,9);
HashInsert(&ht,101,1);
HashRemove(&ht,5);
HashPrint(&ht,"删除一个哈希值5");
}
int main(){
TesthashInit();
TestFind();
TestRemove();
}
开散列
hashBucket.h
#include<stddef.h>
#define HashMaxsize 10
typedef int keyType;
typedef int valType;
typedef size_t (*HashFunc)(keyType key);
typedef struct HashElem{//哈希桶链表结构体
keyType key;
valType value;
struct HashElem* next;
}HashElem;
typedef struct hashBucket{
HashElem* data[HashMaxsize];
size_t size;
HashFunc func;
}hashBucket;
void HashInit(hashBucket* ht,HashFunc func);//哈希表的初始化
void HashDestroy(hashBucket* ht);//销毁哈希表
void HashInsert(hashBucket* ht,keyType key,valType value);//在哈希表中插入元素
int HashFind(hashBucket* ht,keyType key);//在哈希表中查找元素
int HashRemove(hashBucket* ht,keyType key);//在哈希表中删除元素
hashBucket.c
#include"hashBucket.h"
#include<stdio.h>
#include<stdlib.h>
HashElem* CreateElem(keyType key,valType value){//为哈希桶创建节点
HashElem* new_node=(HashElem*)malloc(sizeof(HashElem));
new_node->key=key;
new_node->value=value;
new_node->next=NULL;
return new_node;
}
void HashFree(HashElem* cur){//释放动态开辟的内存
free(cur);
return;
}
size_t hash_func(keyType key){
return key%HashMaxsize;
}
void HashInit(hashBucket* ht,HashFunc func){
if(ht==NULL){
return;
}
ht->size=0;
ht->func=func;
size_t i=0;
for(;i<HashMaxsize;i++){
ht->data[i]=NULL;
}
return;
}
void HashDestroy(hashBucket* ht){
if(ht==NULL){
return;
}
ht->size=0;
ht->func=NULL;
size_t i=0;
for(;i<HashMaxsize;i++){
HashElem* cur=ht->data[i];
while(cur!=NULL){
HashElem* next=cur->next;
HashFree(cur);
cur=next;
}
}
}
void HashInsert(hashBucket* ht,keyType key,valType value)
{
if(ht==NULL){
return;
}
size_t offset=ht->func(key);
HashElem* new_node=CreateElem(key,value);
int ret=HashFind(ht,key);
if(ret==1){
return;
}else{
HashElem* new_node=CreateElem(key,value);
new_node->next=ht->data[offset];
ht->data[offset]=new_node;
++ht->size;
return;
}
}
int HashFind(hashBucket* ht,keyType key){
if(ht==NULL){
return 0;
}
size_t offset=ht->func(key);
if(ht->data[offset]==NULL){
return 0;
}
HashElem* cur=ht->data[offset];
while(cur!=NULL){
if(cur->key==key){
return 1;
}else{
cur=cur->next;
}
}
return 0;
}
int HashRemove(hashBucket* ht,keyType key){
if(ht==NULL){
return 0;
}
if(ht->size==0){
return 0;
}
size_t offset=ht->func(key);
if(ht->data[offset]==NULL){
return 0;
}
HashElem* pre=ht->data[offset];
if(pre->key==key){
HashFree(pre);
pre=NULL;
return 1;
}
HashElem* cur=pre->next;
while(cur!=NULL){
if(cur->key==key){
pre->next=cur->next;
HashFree(cur);
return 1;
}
pre=cur;
cur=cur->next;
}
return 0;
}
//
#define HeaderPrint printf("\n=========%s======\n",__FUNCTION__)
void HashPrint(hashBucket* ht,const char* msg){
if(ht==NULL){
return;
}
printf("[%s]\n",msg);
size_t i=0;
for(;i<HashMaxsize;i++){
if(ht->data[i]!=NULL){
HashElem* cur=ht->data[i];
while(cur!=NULL){
printf("[%u] key:%d value:%d-> ",i,cur->key,cur->value);
cur=cur->next;
}
printf("\n");
}
}
}
void TestInsert(){
HeaderPrint;
hashBucket ht;
HashInit(&ht,hash_func);
HashInsert(&ht,1,100);
HashInsert(&ht,11,100);
HashInsert(&ht,111,100);
HashInsert(&ht,15,100);
HashInsert(&ht,5,17);
HashInsert(&ht,80,10);
HashInsert(&ht,3,88);
HashPrint(&ht,"往哈希表插入四个元素");
}
void TestFind(){
HeaderPrint;
hashBucket ht;
HashInit(&ht,hash_func);
HashInsert(&ht,1,100);
HashInsert(&ht,11,100);
HashInsert(&ht,111,100);
HashInsert(&ht,15,100);
HashInsert(&ht,5,17);
HashInsert(&ht,80,10);
HashInsert(&ht,3,88);
int ret=HashFind(&ht,11);
printf("expect 1,actual %d\n",ret);
int ret1=HashFind(&ht,6);
printf("expect 0,actual %d\n",ret1);
}
void TestRemove(){
HeaderPrint;
hashBucket ht;
HashInit(&ht,hash_func);
HashInsert(&ht,1,100);
HashInsert(&ht,11,100);
HashInsert(&ht,111,100);
HashInsert(&ht,15,100);
HashInsert(&ht,5,17);
HashInsert(&ht,80,10);
HashInsert(&ht,3,88);
HashPrint(&ht,"往哈希表插入四个元素");
int ret=HashRemove(&ht,11);
printf("expect 1,actual %d\n",ret);
HashPrint(&ht,"往哈希表插入四个元素");
}
int main(){
TestInsert();
TestFind();
TestRemove();
}