哈希表----数据结构

引入

如果你是一个队伍的队长,现在有 24 个队员,需要将他们分成 6 组,你会怎么分?其实有一种方法是让所有人排成一排,然后从队头开始报数,报的数字就是编号。当所有人都报完数后,这 24 人也被分为了 6 组,看下方。

(你可能会让 1~4 号为第一组,5~8 号为第二组……但是这样有新成员加入时,就很难决定新成员的去向)

编号除以 6 能被整除的为第一组: 6        12        18        24

编号除以 6 余数是 1 的为第二组:1         7         13        19

编号除以 6 余数是 2 的为第三组:2         8         14        20

编号除以 6 余数是 3 的为第四组:3         9         15        21

编号除以 6 余数是 4 的为第五组:4         10       16        22

编号除以 6 余数是 5 的为第六组:5         11       17        23

OK呀,也是分好了。通过这种方式划分小组,无论是往小组中添加成员,还是快速确定成员的小组都非常方便,例如新加一个队员编号为 25 号,就能够很从容地让他加入到第二组。这种编号方式就是高效的散列,或者称为“哈希”,所以我们经常听说的哈希表也叫做散列表。(哈希就是Hash英文的音译,而Hash的意思是散列)

以上的过程是通过把关键码值 key (编号) 映射到表中的一个位置(数组的下标)来访问记录,从而加快了查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

上面的例子中每个人的编号都是一个关键码值 key ,例如 17 通过映射函数(编号%6 )就能得到一个位置 5 ,就能在数组里下标为 5 的位置找到第六组的所有成员,从而快速找到 17 所在的位置,如下图。

到这里你可以想象一下,一个 key 经过了加工得到了所在的地址,知道地址就可以快速访问所在的地方,就比如你知道一个学生的学号,你通过一系列操作你可以得知那个学生所在的宿舍楼,甚至知道所在的宿舍,从而去那个宿舍交流一下。

哈希表概念

散列表-哈希表 它是基于快速存取的角度设计的,“以空间换时间”。

为什么哈希表很快呢?例如在上面的例子中,问你队伍中存在编号为 17 的队员吗?如果你一个一个遍历你要遍历 24次,不能排除任何一个数据。假设数组有序,你使用二分查找一次也只能排除一半的数据,但是你使用哈希表你可以一次排除六分之五的数据,只需要到第六组中去遍历了,假如小组数量变多是不是效率更高了。

键(key):组员的编号,如:1、15、36……每个编号都是独一无二的,具有唯一性,为了快速访问到某一个组员。

值(value):组员存储的信息,可以是一个整型,可以是一个结构体、也可以是一个类。

索引:用 key 映射到数组的下标(0,1,2,3,4,5)用来快速定位并检索数据

哈希桶:用来保存索引的数组(或链表)存放的成员为索引值相同的组员

映射函数:将文件编号映射到索引上,采用求余法。如文件编号 17 % 5,得到索引 2 

哈希表的实现

哈希表的数据结构定义

我的哈希桶的实现方式是链表。

#define DEFAULT_SIZE 16 //默认的哈希表大小typedef struct _ListNode //哈希链表
{void* date;		//值,指向保存的数据int key;		//键struct _ListNode* next;
}ListNode;typedef ListNode* List;
typedef ListNode* Element; typedef struct _HashTable
{int TableSize;List* lists;    //二级指针,指向指针数组,指针数组里的元素是一级指针,指向了哈希桶
}HashTable;

 

我们是用指向HashTable的指针,访问lists二级指针,lists[i]【(*lists + i)】都是指向了ListNode的指针,用lists[i]去访问哈希桶,如果不太明白可以看看哈希表的初始化。

哈希函数

其实哈希函数的参数不仅仅只有整型,例如参数可以是一个字符串,将字符串的首字符的ASCII码返回也是一个函数。只需要对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址就可以了。

如图:

int Hash(int key, int TableSize)
{//根据 key 得到索引,也就得到了哈希桶的位置return (key % TableSize);
}

初始化哈希表

HashTable* initHash(int TableSize)
{if (TableSize <= 0) //哈希桶的数量不能小于 1{TableSize = DEFAULT_SIZE; //使用默认的大小}HashTable* Hash = NULL; //指向哈希表结构体Hash = (HashTable * ) malloc(sizeof(HashTable));if (Hash == NULL) //防御性编程{cout << "哈希表分配内存失败" << endl;return NULL;}//为二级指针分配内存,指向指针数组,每一个指针指向一个哈希桶Hash->lists = (List*)malloc(sizeof(List) * TableSize);if (Hash->lists == NULL) //防御性编程{cout << "哈希表分配内存失败" << endl;free(Hash); // 运行到这一步,说明Hash不为空,别忘记释放return NULL;}for (int i = 0; i < TableSize; i++){Hash->lists[i] = (ListNode*)malloc(sizeof(ListNode));if (Hash->lists[i] == NULL) //防御性编程{for (int j = 0; j < i; j++){free(Hash->lists[j]);}free(Hash->lists);free(Hash);return NULL;}else{memset(Hash->lists[i], 0, sizeof(ListNode)); //将指向的节点初始化,全部变成0}}return Hash;
}

查找这个键在哈希表中是否存在

Element Find(HashTable* hash, int key)
{int i = 0;List list = NULL;Element e = NULL; //Element 和 List 本质一样i = Hash(key, hash->TableSize); //确定哈希桶位置list = hash->lists[i];			//指向哈希桶e = list->next;		while (e != NULL && e->key != key){e = e->next;}return e; //存在就返回这个节点,不存在就返回 NULL
}

哈希表插入元素

//哈希表插入元素,键key和值(*date)
void insertHash(HashTable* hash, int key, void* date) 
{Element e = NULL , temp = NULL; //temp为指向新加节点List lists = NULL;e = Find(hash, key);if (e == NULL){temp = (ListNode*)malloc(sizeof(ListNode));if (temp == NULL) //防御性编程{cout << "为新加节点分配内存失败" << endl;return;}lists = hash->lists[Hash(key, hash->TableSize)]; //指向新加节点所在的哈希桶//采用前插法,插入节点temp->date = date;temp->key = key;temp->next = lists->next;lists->next = temp;}else{cout << "此键已经存在于哈希表" << endl;}
}

哈希表删除元素

void deleteHash(HashTable* hash, int key)
{int i = 0;i = Hash(key, hash->TableSize);Element e = NULL,last = NULL;List  l = NULL;l = hash->lists[i];last = l;e = l->next;while (e != NULL && e->key != key){last = e; //last保存着要删除的节点的上一个节点e = e->next;}if (e != NULL) //存在这个元素{last->next = e->next;free(e);}else{;}
}

哈希表元素中提取数据

void* getDate(Element e)
{return e ? e->date : NULL;
}

销毁哈希表

void destoryHash(HashTable* hash)
{int i = 0;List l = NULL;Element tmp = NULL,next = NULL;for (int i = 0; i < hash->TableSize; i++){l = hash->lists[i];tmp = l->next; while (tmp != NULL){next = tmp->next;free(tmp);tmp = next;}free(l);}free(hash->lists);free(hash);
}

全部代码

#define _CRT_SECURE_NO_WARNINGS 1#include <iostream>
using namespace std;#define DEFAULT_SIZE 16 //默认的哈希表大小typedef struct _ListNode //哈希链表
{void* date;		//值,指向保存的数据int key;		//键struct _ListNode* next;
}ListNode;typedef ListNode* List;
typedef ListNode* Element;typedef struct _HashTable
{int TableSize;List* lists;
}HashTable;//哈希函数
int Hash(int key, int TableSize)
{//根据 key 得到索引,也就得到了哈希桶的位置return (key % TableSize);
}//初始化哈希表
HashTable* initHash(int TableSize)
{if (TableSize <= 0) //哈希桶的数量不能小于 1{TableSize = DEFAULT_SIZE; //使用默认的大小}HashTable* Hash = NULL; //指向哈希表Hash = (HashTable * ) malloc(sizeof(HashTable));if (Hash == NULL) {cout << "哈希表分配内存失败" << endl;return NULL;}//为哈希桶分配内存,为指针数组,每一个指针指向一个哈希桶Hash->lists = (List*)malloc(sizeof(ListNode*) * TableSize);if (Hash->lists == NULL) //防御性编程{cout << "哈希表分配内存失败" << endl;free(Hash); // 运行到这一步,说明Hash不为空,别忘记释放return NULL;}for (int i = 0; i < TableSize; i++){Hash->lists[i] = (ListNode*)malloc(sizeof(ListNode));if (Hash->lists[i] == NULL) //防御性编程{for (int j = 0; j < i; j++){free(Hash->lists[j]);}free(Hash->lists);free(Hash);return NULL;}else{memset(Hash->lists[i], 0, sizeof(ListNode)); //将指向的节点初始化}}return Hash;
}//查找这个键在哈希表中是否存在
Element Find(HashTable* hash, int key)
{int i = 0;List list = NULL;Element e = NULL;i = Hash(key, hash->TableSize); //确定哈希桶位置list = hash->lists[i];			//指向哈希桶e = list->next;		while (e != NULL && e->key != key){e = e->next;}return e; //存在就返回这个节点,不存在就返回 NULL
}//哈希表插入元素,键key和值(*date)
void insertHash(HashTable* hash, int key, void* date) 
{Element e = NULL , temp = NULL; //temp为指向新加节点List lists = NULL;e = Find(hash, key);if (e == NULL){temp = (ListNode*)malloc(sizeof(ListNode));if (temp == NULL) //防御性编程{cout << "为新加节点分配内存失败" << endl;return;}lists = hash->lists[Hash(key, hash->TableSize)]; //指向新加节点所在的哈希桶//采用前插法,插入节点temp->date = date;temp->key = key;temp->next = lists->next;lists->next = temp;}else{cout << "此键已经存在于哈希表" << endl;}
}//哈希表删除元素
void deleteHash(HashTable* hash, int key)
{int i = 0;i = Hash(key, hash->TableSize);Element e = NULL,last = NULL;List  l = NULL;l = hash->lists[i];last = l;e = l->next;while (e != NULL && e->key != key){last = e; //last保存着要删除的节点的上一个节点e = e->next;}if (e != NULL) //存在这个元素{last->next = e->next;free(e);}else{;}
}//哈希表元素中提取数据
void* getDate(Element e)
{return e ? e->date : NULL;
}//销毁哈希表
void destoryHash(HashTable* hash)
{int i = 0;List l = NULL;Element tmp = NULL,next = NULL;for (int i = 0; i < hash->TableSize; i++){l = hash->lists[i];tmp = l->next; while (tmp != NULL){next = tmp->next;free(tmp);tmp = next;}free(l);}free(hash->lists);free(hash);
}
int main(void)
{const char* elems[] = { "苍老师","一花老师","天老师" };HashTable *hash = NULL;hash = initHash(31);insertHash(hash, 1, (void*)elems[0]);insertHash(hash, 2, (void*)elems[1]);insertHash(hash, 3, (void*)elems[2]);deleteHash(hash, 3);for (int i = 0; i < 3; i++){Element e = Find(hash, i + 1);if (e){cout << (const char *)getDate(e) << endl;}else{cout << "键值为" << i + 1 << "的元素不存在" << endl;}}return 0;
}

如果不太理解的话,可以多看看结构体的定义和初始化,谢谢你看到这里!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/131319.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

D-Link账号密码泄露

构造payload&#xff1a; /getcfg.php SERVICESDEVICE.ACCOUNT&attackture%0D%0AAUTHORIZED_GROUP%3D1漏洞证明&#xff1a; 文笔生疏&#xff0c;措辞浅薄&#xff0c;望各位大佬不吝赐教&#xff0c;万分感谢。 免责声明&#xff1a;由于传播或利用此文所提供的信息、技…

IDEA集成Docker插件打包服务镜像与运行【附Docker命令汇总】

Docker官网 Docker官网&#xff1a;https://www.docker.com/ Docker Hub官网&#xff1a;http://hub.docker.com/ 什么是Docker Docker 是一个开源的容器引擎&#xff0c;可以轻松的为任何应用创建一个轻量级的、可移植的、自给自足的容器。开发者和系统管理员在笔记本上编…

ELK搭建以及使用教程(多pipiline)

1、环境准备 服务器&#xff1a;Centos7 Jdk版本&#xff1a;1.8 Es版本&#xff1a;7.12.1 kibana版本&#xff1a;7.12.1 logstash版本:7.12.1 IP地址安装软件192.168.50.211Es&#xff0c;Kibana&#xff0c;logstash 2、安装docker 安装步骤参考&#xff1a;https:…

Springboot cloud概念和Nacos

Spring Boot Cloud 是一组基于 Spring Boot 开发框架的云计算组件和工具&#xff0c;旨在简化构建云原生应用程序的过程。Spring Boot Cloud 提供了许多云原生应用程序所需的基础设施&#xff0c;例如服务发现、负载均衡、配置管理等。Spring Boot Cloud 包括以下组件&#xff…

JavaScript 数组的 reduce 方法怎么用?用 5 个实际应用例子教会你

reduce 函数介绍 在 JavaScript 中&#xff0c;reduce() 是数组的一个高阶函数&#xff0c;作用是把数组中的元素逐个迭代处理&#xff0c;最终输出为一个处理结果。 reduce() 的语法如下&#xff1a; array.reduce(callback, initialValue); 这个函数接受两个参数&#xf…

在Node.js中,什么是中间件(middleware)?它们的作用是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

【音视频 | opus】opus编解码库(opus-1.4)详细介绍以及使用——附带解码示例代码

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

Three.js之PBR材质与环境贴图

参考资料 PBR材质简介…三维软件导出PBR材质属性 知识点 注&#xff1a;基于Three.jsv0.155.0 PBR材质简介PBR材质金属度和粗糙度&#xff1a;metalness、roughness环境贴图.envMap(金属效果)&#xff1a;CubeTextureLoader、envMapIntensityMeshPhysicalMaterial清漆层&am…

【已验证】解决PHP连接SQL Server时中文乱码问题23.11.04

测试环境 --php8.2 --apache2.4 --sqlserver2022 一.PHP连接SQLSERVER中文乱码-【问题分析】编码问题 1、SQL语句中有中文会导致执行失败&#xff1b; php连接sqlsever&#xff0c;输入中文&#xff0c;然后查询sqlserver中对应的数据&#xff0c;由于提交中文是UTF-8&…

第G7周:Semi-Supervised GAN 理论与实战

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客 &#x1f366; 参考文章&#xff1a;365天深度学习训练营-第G7周&#xff1a;Semi-Supervised GAN 理论与实战&#xff08;训练营内部成员可读&#xff09; &#x1f356; 原作者&#xff1a;K同学啊|接…

Centralized Feature Pyramid for Object Detection解读

Centralized Feature Pyramid for Object Detection 问题 主流的特征金字塔集中于层间特征交互&#xff0c;而忽略了层内特征规则。尽管一些方法试图在注意力机制或视觉变换器的帮助下学习紧凑的层内特征表示&#xff0c;但它们忽略了对密集预测任务非常重要的被忽略的角点区…

【论文精读】PlanT: Explainable Planning Transformers via Object-Level Representations

1 基本信息 院校&#xff1a;德国的图宾根大学 网站&#xff1a;https://www.katrinrenz.de/plant 2 论文背景 2.1 现有问题 现在的基于学习的方法使用高精地图和BEV&#xff0c;认为准确的&#xff08;达到像素级的pixel-level&#xff09;场景理解是鲁棒的输出的关键。re…

Java自学第1课:安装JDK+Eclipse

1 引言 在学习前&#xff0c;我想说一句&#xff0c;那就是为什么要学习Java。 每个人的出发点都不同&#xff0c;对于做信息化的工程技术人员来说&#xff0c;java不懂&#xff0c;就没法干项目。 尽管有c和matlab等基础&#xff0c;但java看起来与这些语言都不太一样。 做…

【从0到1设计一个网关】基于Hystrix实现熔断降级

文章目录 依赖引入服务降级效果演示上文我们已经成功实现了请求重试与请求限流,接下来我们开始实现熔断与服务降级。 熔断与服务降级,在SpringCloud中设计到的就是我们的hystrix,这里我们也将会考虑配合hystrix来实现熔断与服务降级。 如果不了解hystix的可以先进行一下了解…

LeetCode 面试题 16.16. 部分排序

文章目录 一、题目二、C# 题解 一、题目 给定一个整数数组&#xff0c;编写一个函数&#xff0c;找出索引m和n&#xff0c;只要将索引区间[m,n]的元素排好序&#xff0c;整个数组就是有序的。注意&#xff1a;n-m尽量最小&#xff0c;也就是说&#xff0c;找出符合条件的最短序…

基于单片机的智能饮水机系统

收藏和点赞&#xff0c;您的关注是我创作的动力 文章目录 概要 一、系统设计方案分析2.1 设计功能及性能分析2.2设计方案分析 二、系统的硬件设计3.1 系统设计框图系统软件设计4.1 总体介绍原理图 四、 结论 概要 现在很多学校以及家庭使用的饮水机的功能都是比较单一的&#…

【JavaScript】事件监听、事件委托和回调函数

1. 事件监听 on 方法&#xff1a;box.onclick function(){}&#xff0c;但是这种方式会被覆盖&#xff0c;直接使用null覆盖偶就可以实现事件的解绑。 addEventListener 是 DOM 对象专门用来添加事件监听的方法&#xff0c;它的前两个参数分别为【事件类型】和【事件回调】&…

MySQL InnoDB数据存储结构

1. 数据库的存储结构&#xff1a;页 索引结构给我们提供了高效的索引方式&#xff0c;不过索引信息以及数据记录都是保存在文件上的&#xff0c;确切说是存储在页结构中。另一方面&#xff0c;索引是在存储引擎中实现的&#xff0c;MySQL服务器上的存储引擎负责对表中数据的读…

第四次pta认证P测试

第一题 试题编号&#xff1a; 试题名称&#xff1a;整数排序 时间限制&#xff1a; 1.0s 内存限制&#xff1a; 128.0MB 【问题描述】 老师给定 10 个整数的序列&#xff0c;要求对其重新排序。排序要求: 1.奇数在前&#xff0c;偶数在后&#xff1b; 2.奇数按从大到小排序&am…

分享68个工作总结PPT,总有一款适合您

分享68个工作总结PPT&#xff0c;总有一款适合您 PPT下载链接&#xff1a;https://pan.baidu.com/s/1juus0gmesBFxJ-5KZgSMdQ?pwd8888 提取码&#xff1a;8888 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整理更不易。知识付…