哈希表(C语言实现)

在计算机世界中,哈希表如同一位聪慧的图书管理员。他知道如何计算索书号,从而可以快速找到目标图书。

1.哈希表的概念

哈希表(hash table),又称散列表,它通过建立键key 与值value 之间的映射,实现高效的元素查询。具体而言,我们向哈希表中输入一个键key ,则可以在𝑂(1) 时间内获取对应的值value 。

1.1哈希表的基本操作

  • 添加元素
  • 查询元素
  • 删除元素
    在哈希表中进行增删查改的时间复杂度都是𝑂(1),非常高效。

1.2哈希表的常用操作

哈希表的常见操作包括:初始化、查询操作、添加键值对和删除键值对等。

2.基于数实现哈希表

2.1哈希表的结构体定义

首先,我们将key 和value 封装成一个类Pair ,以表示键值对。

//键值对
typedef struct{
    int key;
    int *val;
}Pair;

其次,我们来定义哈希表的结构体

// 基于数组实现哈希表
typedef struct{
    Pair* buckes[Max_Size];
}ArrayHashMap;

2.2哈希表的初始化

首先,为哈希表动态分配内存空间,其次,将哈希表中的每个槽(bucket)初始化为空或NULL,表示没有元素存储在这些槽中,如果初始化成功,返回哈希表指针.

// 哈希表的初始化
ArrayHashMap *InitHashMap(){
    //为哈希表分配内存
    ArrayHashMap *hmap = malloc(sizeof(ArrayHashMap));
    if(hmap == NULL){
        printf("内存分配失败!\n");
        return -1;
    }
    //将每一个键值对置空
    for (int i = 0; i < Max_Size; i++){
        hmap->buckets[i] = NULL;
    }
    return hmap; 
}

2.3删除哈希表

首先,遍历一遍哈希表,将哈希表的键值对释放,再将哈希表的槽释放,最后将哈希表释放。

// 哈希表的删除
void DesttoryHashMap(ArrayHashMap *hmap){
    for (int i = 0; i < Max_Size; i++)
    {
        if(hmap->buckets[i] != NULL){
            free(hmap->buckets[i]->val);
            free(hmap->buckets[i]);
        }
    }
    free(hmap);
}

2.4哈希函数

哈希函数的作用是将一个较大的输入空间映射到一个较小的输出空间。在哈希表中,输入空间是所有key ,输出空间是所有桶(数组索引)。换句话说,输入一个key ,我们可以通过哈希函数得到该key 对应的键值对在数组中的存储位置。

//哈希函数
unsigned int HashFunction(int key){
    return abs(key) % Max_Size;
}

2.5查找哈希表中的元素

在哈希表中查找给定键的值。如果找到,返回对应的值;否则返回 -1。

// 哈希表的查找
int *SearchInHashMap(ArrayHashMap *hamp, int key){
    unsigned int index = HashFunction(key);
    if(hamp->buckets[index]->key == key){
        return hamp->buckets[index]->val;
    }
    return -1;  //没找到
}

2.6 删除哈希表中的元素

// 删除哈希表的元素
bool DeleteInHashMap(ArrayHashMap *hmap, int key){
    unsigned int index = HashFunction(key);
    if(hmap->buckets[index]->key == key){
        hmap->buckets[index]->key = NULL;
        return true;
    }
    return false;
}

2.7添加哈希表元素

将新的键值对添加到哈希表中。如果槽位已被占用,这里我不解决哈希冲突,则返回 false,表示添加失败

// 添加哈希表函数
bool AddHashMap(ArrayHashMap *hmap, int key, int val){
    unsigned int index = HashFunction(key);
    if(hmap->buckets[index]->key == key){
        //这里我们不解决哈希冲突
        printf("槽位已经被占用!\n");
        return false;
    }
    hmap->buckets[index]->key = key;
    hmap->buckets[index]->val = val;
    return true;
}

3.哈希冲突与扩容

什么是哈希冲突?
从本质上看,哈希函数的作用是将所有key 构成的输入空间映射到数组所有索引构成的输出空间,而输入空间往往远大于输出空间。因此,理论上一定存在 “多个输入对应相同输出” 的情况。我们将这称之为哈希冲突。
容易想到,哈希表容量𝑛 越大,多个key 被分配到同一个桶中的概率就越低,冲突就越少。因此,我们可以通过扩容哈希表来减少哈希冲突。
类似于数组扩容,哈希表扩容需将所有键值对从原哈希表迁移至新哈希表,非常耗时;并且由于哈希表容量capacity 改变,我们需要通过哈希函数来重新计算所有键值对的存储位置,这进一步增加了扩容过程的计算开销。为此,编程语言通常会预留足够大的哈希表容量,防止频繁扩容。

4.链式地址改良哈希表

哈希冲突会导致查询结果错误,严重影响哈希表的可用性。为了解决该问题,每当遇到哈希冲突时,我们就进行哈希表扩容,直至冲突消失为止。此方法简单粗暴且有效,但效率太低,因为哈希表扩容需要进行大量的数据搬运与哈希值计算。为了提升效率,我们可以通过链式存储结构来改良哈希表。
基于链式存储结构哈希表发生了以下变化:

  • 查询元素:输入key ,经过哈希函数得到桶索引,即可访问链表头节点,然后遍历链表并对比key 以查找目标键值对。
  • 添加元素:首先通过哈希函数访问链表头节点,然后将节点(键值对)添加到链表中。
  • 删除元素:根据哈希函数的结果访问链表头部,接着遍历链表以查找目标节点并将其删除。

4.1链式地址哈希表结构体定义

//键值对
typedef struct
{
    int key;
    int *val;
} Pair;

//链表节点 
typedef struct Node{
    Pair* pair;
    struct Node* next;
}Node;

//哈希表节点定义
typedef struct {
    int size;   //键值对的数量
    int capcity;    //哈希表的容量
    double loadThres;   //触发扩容的负载因子阈值
    int extendRatio;    //扩容倍数
    Node **buckets;     //桶数组
}HashMapChaining;

4.2哈希表的初始化

//哈希表初始化
HashMapChaining *InitHashMap(){
    HashMapChaining* hashmap = (HashMapChaining*)malloc(sizeof(HashMapChaining));
    hashmap->size = 0;
    hashmap->capcity = 4;
    hashmap->loadThres = 2.0 / 3.0;
    hashmap->extendRatio = 2;
    hashmap->buckets = (Node**)malloc(sizeof(Node*)*hashmap->capcity);
    for (int i = 0; i < hashmap->capcity; i++)
    {
        hashmap->buckets[i] = NULL;
    }
    return hashmap;
    
}

4.3 哈希表的销毁

//哈希表的销毁
void DesrtroyHashMap(HashMapChaining *hashmap){
    for (int  i = 0; i < hashmap->capcity; i++){
        Node* cur = hashmap->buckets[i];
        while (cur){
            Node* temp = cur;
            cur = cur->next;
            free(temp->pair);
            free(temp);
        }
    }
    free(hashmap->buckets);
    free(hashmap);
}

4.4哈希函数

//哈希函数
int HashFuntion(HashMapChaining *hashmap, int key){
    return key % hashmap->capcity;
}

4.5 负载因子

double loadFactor(HashMapChaining *hashMap){
    return (double)hashMap->size / (double)hashMap->capcity;
}

4.6哈希表的扩容

/* 扩容哈希表*/
void extend(HashMapChaining *hashMap)
{
    // 暂存原哈希表
    int oldCapacity = hashMap->capcity;
    Node **oldBuckets = hashMap->buckets;
    // 初始化扩容后的新哈希表
    hashMap->capcity *= hashMap->extendRatio;
    hashMap->buckets = (Node **)malloc(hashMap->capcity * sizeof(Node *));
    for (int i = 0; i < hashMap->capcity; i++)
    {
        hashMap->buckets[i] = NULL;
    }
    hashMap->size = 0;
    // 将键值对从原哈希表搬运至新哈希表
    for (int i = 0; i < oldCapacity; i++)
    {
        Node *cur = oldBuckets[i];
        while (cur)
        {
            put(hashMap, cur->pair->key, cur->pair->val);
            Node *temp = cur;
            cur = cur->next;
            // 释放内存
            free(temp->pair);
            free(temp);
        }
}
以下是一个简单的哈希表C语言实现代码,使用链地址法解决哈希冲突: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #define HASHSIZE 12 #define NULLKEY -32768 typedef struct { int *elem; // 数据元素存储基址,动态分配数组 int count; // 当前数据元素个数 } HashTable; int InitHashTable(HashTable *H) { int i; H->count = HASHSIZE; H->elem = (int *)malloc(HASHSIZE * sizeof(int)); for (i = 0; i < HASHSIZE; i++) { H->elem[i] = NULLKEY; } return 1; } int Hash(int key) { return key % HASHSIZE; // 散列函数采用除留余数法 } void InsertHash(HashTable *H, int key) { int addr = Hash(key); // 求哈希地址 while (H->elem[addr] != NULLKEY) { // 如果不为空,则冲突 addr = (addr + 1) % HASHSIZE; // 开放定址法的线性探测 } H->elem[addr] = key; // 直到有空位后插入关键字 } int SearchHash(HashTable H, int key) { int addr = Hash(key); // 求哈希地址 while (H.elem[addr] != key) { // 如果不相等,则冲突 addr = (addr + 1) % HASHSIZE; // 开放定址法的线性探测 if (H.elem[addr] == NULLKEY || addr == Hash(key)) { // 如果循环回到原点或者遇到空位,则说明关键字不存在 return -1; } } return addr; // 找到关键字,返回其地址 } int main() { HashTable H; int i, key, result; InitHashTable(&H); printf("请输入%d个数:\n", HASHSIZE); for (i = 0; i < HASHSIZE; i++) { scanf("%d", &key); InsertHash(&H, key); } printf("哈希表中的数据为:\n"); for (i = 0; i < HASHSIZE; i++) { printf("%d ", H.elem[i]); } printf("\n请输入要查找的数:\n"); scanf("%d", &key); result = SearchHash(H, key); if (result == -1) { printf("没有找到%d\n", key); } else { printf("%d的位置是%d\n", key, result); } return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

写代码的大学生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值