哈希表(闭散列)

1.哈希表概念

   散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

2.哈希函数概念

   给定表M,存在函数hashfunc(key),对任意给定的关键字key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数hashfunc(key)为哈希(Hash) 函数。常见的构建哈希函数的方法有:直接制定法除留余数法平方取中法折叠法随机数法数学分析法等。
注意:哈希函数设计的越精妙,产生哈希冲突的可能性越低,但无法避免哈希冲突。

3.哈希冲突

对于两个数据元素的关键字Ki和Kj(i != j),有Ki != Kj,但有:
HashFunc(Ki)==HashFunc(Kj)
即不同关键字通过相同哈希函数计算出相同哈希地址,该种现象称为哈希冲突或哈希碰撞。把具有不同关键字而具有相同哈希地址的数据元素称为“同义词”。
那么,发生哈希冲突该如何处理呢?

4.处理哈希冲突

常见的两种方法是:闭散列和开散列
本篇博客主要介绍闭散列。闭散列,也叫开放地址法,当发生哈希冲突时,如果哈希表未被填满,说明哈希表中必然有空位置,那么可以把key放到表中“下一个”空位值中。基本原理如图所示:

哈希

具体实现代码:
①hash_table.h

#pragma once 

#include <stddef.h>

#define HashMaxSize 1000 

typedef enum Stat {
    Empty,
    Valid,
    Invalid // 当前元素被删除了 
} Stat;

typedef int KeyType;
typedef int ValType;

typedef size_t(*HashFunc)(KeyType key);

typedef struct HashElem {
    KeyType key;
    ValType value;
    Stat stat; // 引入一个 stat 标记来作为是否有效的标记 
} HashElem;

typedef struct HashTable {
    HashElem data[HashMaxSize];
    size_t size;
    HashFunc hash_func;
} HashTable;

void HashInit(HashTable* ht, HashFunc hash_func);

int HashInsert(HashTable* ht, KeyType key, ValType value);

// 输入key, 查找对应key的value. 
int HashFind(HashTable* ht, KeyType key, ValType* 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"

void HashInit(HashTable* ht, HashFunc 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, ValType value)
{
    if (ht == NULL)
    {
        return 0;          //非法输入
    }
    //设定负载因子为0.8
    if (ht->size >= HashMaxSize*0.8)
    {
        return 0;         //hash表已满,插入失败
    }
    //先找到key对应的下标
    size_t offset = ht->hash_func(key);
    while (1)
    {
        if (ht->data[offset].stat == Valid)
        {
            if (ht->data[offset].key == key)
            {
                return 0;     //存在相同元素,插入失败
            }
            //出现hash冲突,使用线性探测的方式查找下一个元素
            ++offset;
            if (offset >= HashMaxSize)
            {
                offset -= HashMaxSize;
            }
        }
        else
        {
            //由于负载因子是80%,一定能够找到一个位置插入
            ht->data[offset].key = key;
            ht->data[offset].value = value;
            ht->data[offset].stat = Valid;
            ++ht->size;
            return 1;
        }
    }
    return 0;
}

int HashFind(HashTable* ht, KeyType key, ValType* value)
{
    if (ht == NULL || value == NULL)
    {
        return 0;    //非法输入
    }
    //1.先通过hash函数计算数组下标
    size_t offset = ht->hash_func(key);
    //2.从这个数组下标开始,线性的向后查找
    while (1)
    {
        //   a)如果找到某个元素的key是想要的,并且状态stat为Vaild,就认为是找到了,将value赋值给输出参数
        if (ht->data[offset].key == key && ht->data[offset].stat == Valid)
        {
            *value = ht->data[offset].value;
            return 1;
        }
        //   b)如果当前位置状态为Empty,就认为没找到
        else if (ht->data[offset].stat == Empty)
        {
            return 0;
        }
        //   c)继续尝试查找下一个位置
        ++offset;
        if (offset >= HashMaxSize)
        {
            offset -= HashMaxSize;
        }
    }
    return 0;
}

void HashRemove(HashTable* ht, KeyType key)
{
    if (ht == NULL)
    {
        return;
    }
    //1.先通过hash函数计算数组下标
    size_t offset = ht->hash_func(key);
    //2.从这个数组下标开始,线性的向后查找
    while (1)
    {
        //   a)如果找到某个元素的key是想要的,并且状态stat为Vaild,就认为是找到了,将状态置为InVaild,并--size
        if (ht->data[offset].key == key && ht->data[offset].stat == Valid)
        {
            ht->data[offset].stat = Invalid;
            --ht->size;
            return;
        }
        //   b)如果当前位置状态为Empty,就认为没找到
        else if (ht->data[offset].stat == Empty)
        {
            return;
        }
        //   c)继续尝试查找下一个位置
        ++offset;
        if (offset >= HashMaxSize)
        {
            offset -= HashMaxSize;
        }
    }
    return;
}

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)
{
    size_t i = 0;
    for (; i < HashMaxSize; ++i)
    {
        ht->data[i].stat = Empty;
    }
    ht->size = 0;
    ht->hash_func = NULL;
    return;
}

③test.c

#include "hash_table.h"

#include <stdio.h>
#include <windows.h>

#define TEST_HEADER printf("\n=======================%s======================\n",__FUNCTION__)

size_t HashFuncDefault(KeyType key)
{
    return key % HashMaxSize;
}

void HashPrintInt(HashTable* ht, const char* msg)
{
    printf("[%s]:\n", msg);
    size_t i = 0;
    for (; i < HashMaxSize; ++i)
    {
        if (ht->data[i].stat != Empty)
        {
            printf("[%lu]: key=%d, value=%d\n", i, ht->data[i].key, ht->data[i].value);
        }
    }
}

void CountNum()
{
    TEST_HEADER;
    int array[] = { 1, 1, 1, 2, 2, 3,101,1001};
    HashTable ht;
    HashInit(&ht, HashFuncDefault);
    size_t i = 0;
    for (; i < sizeof(array) / sizeof(array[0]); ++i)
    {
        int value = 0;
        int ret = HashFind(&ht, array[i], &value);
        if (ret == 0)
        {
            //没找到,直接插入
            HashInsert(&ht, array[i], 1);
        }
        else
        {
            //该元素存在,先删除该元素,然后再插入
            HashRemove(&ht, array[i]);
            HashInsert(&ht, array[i], value + 1);
        }
    }
    HashPrintInt(&ht, "最终结果为");
}

void TestInit()
{
    TEST_HEADER;
    HashTable ht;
    HashInit(&ht, HashFuncDefault);
    printf("ht->size expect 0,actual %lu\n", ht.size);
    printf("ht->hash_func expect %p,actual %p\n", HashFuncDefault, ht.hash_func);
    size_t i = 0;
    for (; i < HashMaxSize; ++i)
    {
        if (ht.data[i].stat != Empty)
        {
            printf("pos[%lu] elem error!\n", i);
        }
    }
}

void TestInsert()
{
    TEST_HEADER;
    HashTable ht;
    HashInit(&ht, HashFuncDefault);
    HashInsert(&ht, 1, 100);
    HashInsert(&ht, 2, 200);
    HashInsert(&ht, 1001, 300);
    HashInsert(&ht, 1002, 400);
    HashInsert(&ht, 1, 500);
    HashPrintInt(&ht, "插入五个元素");
}

void TestFind()
{
    TEST_HEADER;
    HashTable ht;
    HashInit(&ht, HashFuncDefault);
    HashInsert(&ht, 1, 100);
    HashInsert(&ht, 2, 200);
    HashInsert(&ht, 1001, 300);
    HashInsert(&ht, 1002, 400);
    HashInsert(&ht, 1, 500);
    HashPrintInt(&ht, "插入五个元素");

    int value = 0;
    int ret = HashFind(&ht, 1, &value);
    printf("[查找1]:\n");
    printf("ret expect 1,actual %d\n", ret);
    printf("value expect 100,actual %d\n\n", value);

    ret = HashFind(&ht, 1001, &value);
    printf("[查找1001]:\n");
    printf("ret expect 1,actual %d\n", ret);
    printf("value expect 300,actual %d\n\n", value);

    ret = HashFind(&ht, 1003, &value);
    printf("[查找1003]:\n");
    printf("ret expect 0,actual %d\n", ret);
}

void TestRemove()
{
    TEST_HEADER;
    HashTable ht;
    HashInit(&ht, HashFuncDefault);
    HashInsert(&ht, 1, 100);
    HashInsert(&ht, 2, 200);
    HashInsert(&ht, 1001, 300);
    HashInsert(&ht, 1002, 400);
    HashInsert(&ht, 1, 500);
    HashPrintInt(&ht, "插入五个元素");

    HashRemove(&ht, 1);
    int value = 0;
    int ret = HashFind(&ht, 1, &value);
    printf("[删除1之后]:\n");
    printf("ret expect 0,actual %d\n\n", ret);

    HashRemove(&ht, 1001);
    ret = HashFind(&ht, 1001, &value);
    printf("[删除1001之后]:\n");
    printf("ret expect 0,actual %d\n\n", ret);

    HashRemove(&ht, 1003);
    ret = HashFind(&ht, 1003, &value);
    printf("[删除1003之后]:\n");
    printf("ret expect 0,actual %d\n", ret);
}

void TestEmpty()
{
    TEST_HEADER;
    HashTable ht;
    HashInit(&ht, HashFuncDefault);
    int ret = HashEmpty(&ht);
    printf("ret expect 1, actual %d\n", ret);

    HashInsert(&ht, 1, 100);
    HashInsert(&ht, 2, 200);
    HashInsert(&ht, 1001, 300);
    HashInsert(&ht, 1002, 400);
    HashInsert(&ht, 1, 500);
    HashPrintInt(&ht, "插入五个元素");
    ret = HashEmpty(&ht);
    printf("ret expect 0, actual %d\n", ret);

    HashRemove(&ht, 1);
    HashRemove(&ht, 2);
    HashRemove(&ht, 1001);
    HashRemove(&ht, 1002);
    ret = HashEmpty(&ht);
    printf("ret expect 1, actual %d\n", ret);
}

void TestSize()
{
    TEST_HEADER;
    HashTable ht;
    HashInit(&ht, HashFuncDefault);
    size_t ret = HashSize(&ht);
    printf("ret expect 0, actual %d\n", ret);

    HashInsert(&ht, 1, 100);
    HashInsert(&ht, 2, 200);
    HashInsert(&ht, 1001, 300);
    HashInsert(&ht, 1002, 400);
    HashInsert(&ht, 1, 500);
    HashPrintInt(&ht, "插入五个元素");
    ret = HashSize(&ht);
    printf("ret expect 4, actual %d\n", ret);

    HashRemove(&ht, 1);
    HashRemove(&ht, 2);
    HashRemove(&ht, 1001);
    HashRemove(&ht, 1002);
    ret = HashSize(&ht);
    printf("ret expect 0, actual %d\n", ret);

}

void TestDestroy()
{
    TEST_HEADER;
    HashTable ht;
    HashInit(&ht, HashFuncDefault);
    HashInsert(&ht, 1, 100);
    HashInsert(&ht, 2, 200);
    HashInsert(&ht, 1001, 300);
    HashInsert(&ht, 1002, 400);
    HashInsert(&ht, 1, 500);
    HashPrintInt(&ht, "插入五个元素");
    HashDestroy(&ht);
    printf("销毁哈希表后:\n");
    printf("ht->size expect 0,actual %lu\n", ht.size);
    printf("ht->hash_func expect NULL,actual %p\n", ht.hash_func);
    size_t i = 0;
    for (; i < HashMaxSize; ++i)
    {
        if (ht.data[i].stat != Empty)
        {
            printf("pos[%lu] elem error!\n", i);
        }
    }
}

int main()
{
    TestInit();
    TestInsert();
    TestFind();
    TestRemove();
    TestEmpty();
    TestSize();
    TestDestroy();
    CountNum();
    system("pause");
    return 0;
}

对应结果截图:

结果
结果
结果

未完待续……

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值