当前位置: 首页 > news >正文

C语言 哈希表的简单实现

简介

   哈希表:也叫做散列表。是根据关键字和值(Key-Value)直接进行访问的数据结构。也就是说,它通过关键字 key 和一个映射函数 Hash(key) 计算出对应的值 value,然后把键值对映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做哈希函数(散列函数),用于存放记录的数组叫做 哈希表(散列表)。 哈希表的关键思想是使用哈希函数,将键 key 和值 value 映射到对应表的某个区块中。

哈希冲突

  根据上述内容不难发现会出现特殊情况,就是通过不同的 Key,可能访问到同一个地址,这种现象叫作碰撞(Collision)。而通过某个 Key 一定会得到唯一的 Value 地址。
冲突的处理方式也有很多,下面介绍几种。

  • 开放地址法(也叫开放寻址法):实际上就是当需要存储值时,对Key哈希之后,发现这个地址已经有值了,这时该怎么办?不能放在这个地址,不然之前的映射会被覆盖。这时对计算出来的地址进行一个探测再哈希,比如往后移动一个地址,如果没人占用,就用这个地址。如果超过最大长度,则可以对总长度取余。这里移动的地址是产生冲突时的增列序量。
  • 再哈希法(rehash):在产生冲突之后,使用关键字的其他部分继续计算地址,如果还是有冲突,则继续使用其他部分再计算地址。这种方式的缺点是时间增加了。
  • 链地址法:链地址法其实就是对Key通过哈希之后落在同一个地址上的值,后续通过一个链表将数据衔接在其后面。其实在很多高级语言的实现当中,也是使用这种方式处理冲突的。
  • 建立一个公共溢出区:这种方式是建立一个公共溢出区,当地址存在冲突时,把新的地址放在公共溢出区里。

链地址法示例【后续代码采用链地址法解决冲突】:
在这里插入图片描述

哈希函数的构建

  • 直接寻址法:取关键字或关键字的某个线性函数值为散列地址,如 add = a(key) + b。
  • 数字分析法:通过对数据的分析,发现数据中冲突较少的部分,并构造散列地址。
  • 平方取中法:当无法确定关键字里哪几位的分布相对比较均匀时,可以先求出关键字的平方值,然后按需要取平方值的中间几位作为散列地址。这是因为:计算平方之后的中间几位和关键字中的每一位都相关,所以不同的关键字会以较高的概率产生不同的散列地址。
  • 取随机数法:使用一个随机函数,取关键字的随机值作为散列地址,这种方式通常用于关键字长度不同的场合。
  • 除留取余法:取关键字被某个不大于散列表的表长 n 的数 m 除后所得的余数 p 为散列地址。这种方式也可以在用过其他方法后再使用。该函数对 m 的选择很重要,一般取素数或者直接用 n。

散列表的特点

散列表有两种用法:一种是 Key 的值与 Value 的值一样,一般我们称这种情况的结构为 Set(集合);而如果 Key 和 Value 所对应的内容不一样时,那么我们称这种情况为 Map,也就是人们俗称的键值对集合。

根据散列表的存储结构,我们可以得出散列表的以下特点。

  1. 访问速度很快
    由于散列表有散列函数,可以将指定的 Key 都映射到一个地址上,所以在访问一个 Key(键)对应的 Value(值)时,根本不需要一个一个地进行查找,可以直接跳到那个地址。所以我们在对散列表进行添加、删除、修改、查找等任何操作时,速度都很快。

  2. 需要额外的空间
    首先,散列表实际上是存不满的,如果一个散列表刚好能够存满,那么肯定是个巧合。而且当散列表中元素的使用率越来越高时,性能会下降,所以一般会选择扩容来解决这个问题。另外,如果有冲突的话,则也是需要额外的空间去存储的,比如链地址法,不但需要额外的空间,甚至需要使用其他数据结构。

  3. 无序
    散列表还有一个非常明显的特点,那就是无序。为了能够更快地访问元素,散列表是根据散列函数直接找到存储地址的,这样我们的访问速度就能够更快,但是对于有序访问却没有办法应对。

  4. 可能会产生碰撞
    没有完美的散列函数,无论如何总会产生冲突,这时就需要采用冲突解决方案,这也使散列表更加复杂。通常在不同的高级语言的实现中,对于冲突的解决方案不一定一样。

源代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAX_KEY_LEN 100
#define MAX_TABLE_SIZE 100				// 哈希表大小

typedef struct HashNode{
    char *key;
    int value;							// 哈希表元素 key + value 
    struct HashNode *nextNode;
}HashNode;


typedef struct HashTable{
    HashNode * hashNode[MAX_TABLE_SIZE];
    int currentIndex;
}HashTable;


unsigned long HashFun(const char *key)					// 哈希函数
{
    unsigned long hash = 0;
    int len = strlen(key);
    for (int i = 0; i < len; i++) {
        hash = hash * 33 + key[i];
    }
    return hash;
}
//初始化
void InitHashTable(HashTable *hashTable)
{
    memset(hashTable->hashNode, 0, sizeof(HashNode *) * MAX_TABLE_SIZE);
    hashTable->currentIndex = 0;
}

//插入key value
void Insert(HashTable *hashTable, char *key, int value)
{
    int pos = HashFun(key) % MAX_TABLE_SIZE;
    HashNode * newNode = (HashNode *)malloc(sizeof(HashNode));
    newNode->nextNode = NULL;
    newNode->key = (char *)malloc(sizeof(char) * (strlen(key) + 1));
    strcpy(newNode->key, key);
    newNode->key[strlen(key)] = '\0';
    newNode->value = value;
    HashNode *p = hashTable->hashNode[pos];
    if (p == NULL) {  //如果头结点为空,说明没有冲突
        hashTable->hashNode[pos] = newNode;
        hashTable->currentIndex++;
        return;
    }
    //头结点不为空,同时头结点的key和输入的key相同,覆盖value

    
    if (strcmp(p->key, key) == 0) {
        return;
    }

    else 
    {
        while(p->nextNode)
        {
            if (strcmp(p->nextNode->key, key) == 0) 
            {
                return;
            }
            p = p->nextNode;
        }
        
        p->nextNode = newNode;
        return ;
    }
}

int * Get(HashTable *hashTable, char *key)
{
    int pos = HashFun(key) % MAX_TABLE_SIZE;
    HashNode *p = hashTable->hashNode[pos];
    if (p == NULL) {  //如果头结点为空,说明不存在这样的key
        return NULL;
    } else {
        HashNode *q = p;
        while (q != NULL) {
            if(strcmp(q->key, key) == 0) {
                return &(q->value);
            }
            q = q->nextNode;
        }
        return NULL;
    }
}

int Drop(HashTable *hashTable, char *key)
{
    int pos = HashFun(key) % MAX_TABLE_SIZE;
    HashNode *p = hashTable->hashNode[pos];
    if (p == NULL) {  //如果头结点为空,说明不存在这样的key
        return 0;
    }
    else {
        if(strcmp(p->key, key) == 0) {  //删除的如果是头结点
            hashTable->hashNode[pos] = p->nextNode;
            free(p->key);
            free(p);
            return 1;
        }
        //删除的不是头结点的情况
        HashNode *q = p->nextNode;
        HashNode * last = p;
        while (q != NULL) {
            if(strcmp(q->key, key) == 0) {
                last->nextNode = q->nextNode;
                free(q->key);
                free(q);
                return 1;
            }
            last = q;
            q = q->nextNode;
        }
        return 0;
    }
}


void ClearHashTable(HashTable* hashTable)
{
    for(int i = 0; i < MAX_TABLE_SIZE; i++) {
        HashNode *head = hashTable->hashNode[i];
        while(head) {
            HashNode *temp = head->nextNode;
            free(head->key);
            free(head);
            head = temp;
        }
    }
}

void PrintHashTable(HashTable *hashTable)
{
    for(int i = 0; i < MAX_TABLE_SIZE; i++) {
        HashNode *head = hashTable->hashNode[i];
        if (head == NULL)
            continue;
        printf("\nindex:%d======>", i);
        printf("(%s:%d)", head->key, head->value);
        head = head->nextNode;
        while(head) {
            printf("-->(%s:%d)", head->key, head->value);
            head = head->nextNode;
        }
    }
}

int main(void)
{
    HashTable *hashTable = (HashTable *)malloc(sizeof(HashTable));
    InitHashTable(hashTable);
    Insert(hashTable,"1234", 1);
    int *data = Get(hashTable,"b");
    printf("\n====%d=====\n", *data);
    Drop(hashTable, "python");
    PrintHashTable(hashTable);
    Drop(hashTable, "b");
    Drop(hashTable, "c1");
    printf("\n");
    PrintHashTable(hashTable);
    Drop(hashTable, "world");
    printf("\n");
    PrintHashTable(hashTable);
    ClearHashTable(hashTable);

}

参考链接:http://data.biancheng.net/view/107.html

相关文章:

  • 学习率和BatchSize对模型的影响
  • 小代码大智慧: FilenameUtils.getName 函数分析
  • 基于php理发店管理系统
  • Linux入门之使用 firewalld 防火墙
  • 【论文阅读】SABRE: Protecting Bitcoin against Routing Attacks
  • 【设计模式3_责任链、观察者】
  • .NET MVC之AOP
  • 机器学习基础
  • 浅谈报表测试
  • 第十一:Fiddler抓包教程(11)-Fiddler设置安卓手机抓包,不会可是万万不行的!
  • ARMv8 MMU和translation stages、translation regimes和相关寄存器
  • Linux入门之配置以太网连接
  • 小时3.0报表某个型号数据比天数据多问题复盘
  • 深度学习 FairMOT多目标跟踪(PANDA)
  • 深度学习图像分割U-Net和FCN讲解
  • [PHP内核探索]PHP中的哈希表
  • classpath对获取配置文件的影响
  • create-react-app项目添加less配置
  • EventListener原理
  • Java 11 发布计划来了,已确定 3个 新特性!!
  • Java 最常见的 200+ 面试题:面试必备
  • Javascript基础之Array数组API
  • JavaScript异步流程控制的前世今生
  • LeetCode算法系列_0891_子序列宽度之和
  • MySQL用户中的%到底包不包括localhost?
  • Python进阶细节
  • Python爬虫--- 1.3 BS4库的解析器
  • python学习笔记 - ThreadLocal
  • spring boot下thymeleaf全局静态变量配置
  • UMLCHINA 首席专家潘加宇鼎力推荐
  • vue自定义指令实现v-tap插件
  • 从伪并行的 Python 多线程说起
  • 飞驰在Mesos的涡轮引擎上
  • 给github项目添加CI badge
  • 前端自动化解决方案
  • 如何编写一个可升级的智能合约
  • 如何合理的规划jvm性能调优
  • 如何优雅的使用vue+Dcloud(Hbuild)开发混合app
  • 3月27日云栖精选夜读 | 从 “城市大脑”实践,瞭望未来城市源起 ...
  • Java数据解析之JSON
  • #gStore-weekly | gStore最新版本1.0之三角形计数函数的使用
  • (02)Cartographer源码无死角解析-(03) 新数据运行与地图保存、加载地图启动仅定位模式
  • (2022版)一套教程搞定k8s安装到实战 | RBAC
  • (Matlab)遗传算法优化的BP神经网络实现回归预测
  • (八)Docker网络跨主机通讯vxlan和vlan
  • (附源码)ssm高校升本考试管理系统 毕业设计 201631
  • (心得)获取一个数二进制序列中所有的偶数位和奇数位, 分别输出二进制序列。
  • (转)linux 命令大全
  • .NET 5.0正式发布,有什么功能特性(翻译)
  • .NET CF命令行调试器MDbg入门(三) 进程控制
  • .net core webapi 部署iis_一键部署VS插件:让.NET开发者更幸福
  • .NET/C# 使窗口永不获得焦点
  • .so文件(linux系统)
  • [ C++ ] STL priority_queue(优先级队列)使用及其底层模拟实现,容器适配器,deque(双端队列)原理了解
  • [ linux ] linux 命令英文全称及解释