哈希表的基本操作(二):哈希桶处理哈希冲突

开散列:(链地址法或者也叫开链法)
首先对关键码集合用哈希函数计算哈希地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,每个桶中的元素通过单链表链接起来,个链表的头结点存储在哈希表中。由此也有一个形象的叫法是哈希桶。
这里写图片描述
这里写图片描述
哈希桶处理哈希冲突C语言实现

//hash.h文件内容
#pragma once

#define max_size 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 HashTable
{
    //如果我们的hash桶上面的链表是一个不带头结点的链表
    //类型就用HashElem*
    //如果是一个带头结点的链表
    //类型就用HashElem
    HashElem *data[max_size];
    int size;//有效元素个数
    HashFunc func;//哈希函数
}HashTable;

//初始化
void HashInit(HashTable *ht,HashFunc Hash_func);
//销毁
void HashDestroy(HashTable *ht);
//插入数据
void HashInsert(HashTable *ht,KeyType key,ValType value);
//查找数据
int HashFind(HashTable *ht,KeyType key,ValType *value);
//删除数据
void HashRemove(HashTable *ht,KeyType key);

以下为相应的函数实现

//初始化
void HashInit(HashTable *ht,HashFunc Hash_func)
{
    if(ht == NULL)
    {
        //非法输入
        return;
    }
    ht->size = 0;
    ht->func = Hash_func;
    int i = 0;
    for(;i < max_size;i++)
    {
        ht->data[i] = NULL;
    }
    return;
}
//销毁
void HashDestroy(HashTable *ht)
{
    if(ht == NULL)
    {
        //非法输入
        return;
    }
    ht->size = 0;
    ht->func = NULL;
    //遍历所有的链表并进行释放
    int i = 0;
    for(;i < max_size;i++)
    {
        HashElem *cur = ht->data[i];
        while(cur != NULL)
        {
            HashElem *next = cur->next;
            DestroyElem(cur);
            cur = next;
        }
    }
    return;
}
//测试一下
void TestInit()
{
    Test_Header;
    HashTable ht;
    HashInit(&ht,Hash_func);
    printf("expect size = 0,actual size = %d\n",ht.size);
    printf("expect func = %p,actual func = %p\n",Hash_func,ht.func);
}

测试结果:
这里写图片描述

//查找元素是否存在于对应的链表中的函数,成功找到则返回对应的元素节点
HashElem *HashBucketFind(HashElem *head,KeyType to_find)
{
    HashElem *cur = head;
    for(;cur != NULL;cur = cur->next)
    {
        if(cur->key == to_find)
        {
            //在当前链表中找到了该元素
            return cur;
        }
    }
    //在当前链表中没找到该元素
    return NULL;
}
//插入数据
void HashInsert(HashTable *ht,KeyType key,ValType value)
{
    if(ht == NULL)
    {
        //非法输入
        return;
    }
    if(ht->size >= 0.8*max_size)
    {
        //此时哈希表已经达到了负载因子的上限
        //不能再继续插入元素
        return;
    }
    //可以继续插入元素
    //先由key计算出offset
    int offset = ht->func(key);
    //在offset对应的链表中查找当前待插入的元素是否已经存在
    HashElem *ret = HashBucketFind(ht->data[offset],key);
    if(ret != NULL)
    {
        //走到这里说明待插入的元素已经存在
        //约定哈希表中不存在重复的元素
        //插入失败直接返回
        return;
    }
    else
    {
        //不存在重复的元素
        //可以插入(头插法)
        HashElem *new_elem = CreateElem(key,value);
        //将新的元素头插法插入当前链表中
        new_elem->next = ht->data[offset];
        //更新链表的头结点为新插入的元素
        ht->data[offset] = new_elem;
        //别忘了将哈希表的有效元素个数+1
        ++ht->size;
    }
}
//测试用打印哈希表元素的函数
void HashPrint(HashTable *ht,const char *msg)
{
    printf("[%s]\n",msg);
    int i = 0;
    for(;i < max_size;i++)
    {
        if(ht->data[i] != NULL)
        {

            printf("i = %d\n",i);
            HashElem *cur = ht->data[i];
            for(;cur != NULL;cur = cur->next)
            {
                printf("[%d,%d] ",cur->key,cur->value);
            }
            printf("\n");
        }//if结束
    }//for结束
}
//测试一下
void TestInsert()
{
    Test_Header;
    HashTable ht;
    HashInit(&ht,Hash_func);
    HashInsert(&ht,1,1);
    HashInsert(&ht,1,10);
    HashInsert(&ht,2,20);
    HashInsert(&ht,1000,100);
    HashInsert(&ht,2000,200);
    HashPrint(&ht,"插入5个元素");
}

测试结果:
这里写图片描述

//查找数据
int HashFind(HashTable *ht,KeyType key,ValType *value)
{
    if(ht == NULL || value == NULL)
    {
        //非法输入
        return 0;
    }
    if(ht->size == 0)
    {
        //空哈希表
        return 0;
    }
    //由key值计算出offset
    int offset = ht->func(key);
    //从offset指向的链表位置开始
    //遍历链表查找
    HashElem *ret = HashBucketFind(ht->data[offset],key);
    if(ret == NULL)
    {
        //没找到
        return 0;
    }
    else
    {
        //找到了
        *value = ret->value;
        return 1;
    }
}
//测试一下
void TestFind()
{
    Test_Header;
    HashTable ht;
    HashInit(&ht,Hash_func);
    HashInsert(&ht,1,1);
    HashInsert(&ht,1,10);
    HashInsert(&ht,2,20);
    HashInsert(&ht,1000,100);
    HashInsert(&ht,2000,200);
    ValType value;
    int ret = HashFind(&ht,1000,&value);
    printf("查找数据为1000的元素结果为:");
    printf("expect ret = 1,actual ret = %d;",ret);
    printf("expect value = 100,actual value = %d\n",value);
    ret = HashFind(&ht,3000,&value);
    printf("查找数据为3000的元素结果为:");
    printf("expect ret = 0,actual ret = %d\n",ret);
}

测试结果:
这里写图片描述

//查找元素是否存在于对应链表中的函数。成功返回1,失败返回0
int HashBucketFindEx(HashElem *head,KeyType to_find,\
                     HashElem **pre_node,HashElem **cur_node)
{
    HashElem *pre = NULL;
    HashElem *cur = head;
    for(;cur != NULL;pre = cur,cur = cur->next)
    {
        if(cur->key == to_find)
        {
            //找到了,跳出循环
            break;
        }
    }
    if(cur == NULL)
    {
        //走到这儿说明是链表遍历完了
        //没找到
        return 0;
    }
    *pre_node = pre;
    *cur_node = cur;
    return 1;
}
//删除数据
void HashRemove(HashTable *ht,KeyType key)
{
    if(ht == NULL)
    {
        //非法输入
        return;
    }
    if(ht->size == 0)
    {
        //空哈希表
        return;
    }
    //由key计算出offset值
    int offset = ht->func(key);
    //在当前offset对应的链表下
    //查找待删除的元素是否存在
    HashElem *pre = NULL;
    HashElem *cur = NULL;
    int ret = HashBucketFindEx(ht->data[offset],key,&pre,&cur);
    if(ret == 0)
    {
        //当前链表下没有找到待删除的元素
        return;
    }
    else
    {
        //找到了,可以进行删除
        if(pre == NULL)
        {
            //要删除的元素是链表的头结点
            //更新当前链表的头结点
            ht->data[offset] = cur->next;
        }
        else
        {
            //pre的下一个元素本来指向的是cur(待删除的元素)
            //cur如果要被删除,则pre指向的应该是cur的下一个元素
            pre->next = cur->next;
        }
        DestroyElem(cur);
        //将哈希表的有效元素个数-1
        --ht->size;
    }//else(ret=1)结束
}
//测试一下
void TestRemove()
{
    Test_Header;
    HashTable ht;
    HashInit(&ht,Hash_func);
    HashInsert(&ht,1,1);
    HashInsert(&ht,1,10);
    HashInsert(&ht,2,20);
    HashInsert(&ht,1000,100);
    HashInsert(&ht,2000,200);
    HashRemove(&ht,2);
    HashPrint(&ht,"删除数据为2的元素后的结果:");
    HashRemove(&ht,20);
    HashPrint(&ht,"删除数据为20的元素后的结果:");
}

测试结果:
这里写图片描述
·

发布了110 篇原创文章 · 获赞 47 · 访问量 7万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章