哈希表的基本操作(二):哈希桶處理哈希衝突

開散列:(鏈地址法或者也叫開鏈法)
首先對關鍵碼集合用哈希函數計算哈希地址,具有相同地址的關鍵碼歸於同一子集合,每一個子集合稱爲一個桶,每個桶中的元素通過單鏈錶鏈接起來,個鏈表的頭結點存儲在哈希表中。由此也有一個形象的叫法是哈希桶。
這裏寫圖片描述
這裏寫圖片描述
哈希桶處理哈希衝突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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章