【数据结构篇C++实现】- 哈希表

文章目录

  • 🚀一、哈希表的原理精讲
    • 🚢(一)概念
    • 🚢(二)常见哈希函数的构造方法
      • 1.直接定址法
      • 2.数字分析法
      • 3.平方取中法
      • 4.除留余数法
      • 5.随机数法
    • 🚢(三)哈希冲突与处理方法
      • 1.开放地址法
      • 2.链地址法
      • 3.公共溢出区法
  • 🚀二、哈希表的算法实现
    • 1.哈希链表的结构体定义
    • 2.哈希函数
    • 3.初始化哈希表
    • 4.哈希表插入元素
    • 5.哈希表查找元素
    • 6.哈希表删除元素


🚀一、哈希表的原理精讲

🚢(一)概念

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

哈希表是基于快速存取的角度设计的,也是一种典型的“空间换时间”的做法,本质上是由数组衍生而来,其核心就是哈希函数

几个核心概念:

  • 键(key): 组员的编号 如, 1 、 5 、 19 。 。 。
  • 值(value): 组员的其它信息(包含 性别、年龄和战斗力等)
  • 索引:数组的下标(0,1,2,3,4) ,用以快速定位和检索数据,同时可以把该数组称为索引数组
  • 哈希函数:将组员编号映射到索引上,一般采用求余法

在这里插入图片描述

图中,Key为28,通过哈希函数计算存放在哈希表中的位置,得到索引为:28 % 5 = 3,但是如此的话就会造成一个问题:如果还有个学生的学号为33,通过哈希函数计算后得到的数组存放位置下标也为3,这种情况就是待会要讲的哈希冲突

 

🚢(二)常见哈希函数的构造方法

1.直接定址法

如果我们现在要对0~100岁的人口数字统计表,如表8-10-1所示,那么我们对年龄这个关键字就可以直接用年龄的数字作为地址。此时f (key) =key。

在这里插入图片描述

如果我们现在要统计的是80后出生年份的人口数,如表8-10-2所示,那么我们对出生年份这个关键字可以用年份减去1980来作为地址。此时f (key)=key-1980。

在这里插入图片描述

也就是说,我们可以取关键字的某个线性函数值为散列地址,即f (key) =axkey+b (a、b为常数)
这样的散列函数优点就是简单、均匀,也不会产生冲突,但问题是这需要事先知道关键字的分布情况,适合查找表较小且连续的情况。由于这样的限制,在现实应用中,此方法虽然简单,但却并不常用。

2.数字分析法

例如当手机号码为关键字时,其11位数字是有规则的,此时是无需把11位数值全部当做散列地址,这时我们给关键词抽取, 抽取方法是使用关键字的一部分来计算散列存储位置的方法,这在散列函数中是常常用到的手段。
数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀,就可以考虑用这个方法。这种方法适合于已知的关键字集合,若更换了关键字,则需要重新构造新的散列函数。

3.平方取中法

这个方法计算很简单,假设关键字是1234,那么它的平方就是1522756,再抽取中间的3位就是227,用做散列地址。再比如关键字是4321,那么它的平方就是18671041,抽取中间的3位就可以是671,也可以是710,用做散列地址。平方取中法比较适合于不知道关键字的分布,而位数又不是很大的情况。

4.除留余数法

这是一种最简单、最常用的方法,假定散列表表长为m mm,取一个不大于m mm但最接近或等于m mm的质数p pp,利用以下公式把关键字转换成散列地址。散列函数为H ( k e y ) = k e y % p ( p < = m ) 事实上,这方法不仅可以对关键字直接取模,也可在折叠、平方取中后再取模。
除留余数法的关键是选好p pp,使得每个关键字通过该函数转换后等概率地映射到散列空间上的任一地址,从而尽可能减少冲突的可能性。

5.随机数法

选择一个随机数,取关键字的随机函数值为它的散列地址。也就是H ( k e y ) = r a n d o m ( k e y ) 这里random是随机函数。当关键字的长度不等时,采用这个方法构造散列函数是比较合适的。
 

🚢(三)哈希冲突与处理方法

哈希函数可能会把两个或两个以上的不同关键字映射到同一地址,称这种情况为冲突,这些发生碰撞的不同关键字称为同义词。一方面,设计的好的哈希函数应尽量减少这样的冲突;另一方面,由于这样的冲突总是不可避免的,所以还要设计好处理冲突的方法。

1.开放地址法

所谓的开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。

它的公式是:
f i ( k e y ) = ( f ( k e y ) + d i ) M O D m ( d i = 1 , 2 , 3 , . . . . . . , m − 1 ) f_{i}(key)=(f(key)+d_{i})MODm(d_{i}=1,2,3,......,m-1) fi(key)=(f(key)+di)MODm(di=1,2,3,......,m1)

在这里插入图片描述

2.链地址法

将所有关键字为同义词的记录存储在一个单链表中,我们称这种表为同义词子表,在散列表中只存储所有同义词子表的头指针。

例如,关键字序列为{ 12 , 67 , 56 , 16 , 25 , 37 , 22 , 29 , 15 , 47 , 48 , 34 } {12, 67, 56, 16, 25, 37, 22, 29,15, 47,48, 34}{12,67,56,16,25,37,22,29,15,47,48,34},我们用除留余数法构造散列函数H ( k e y ) = k e y % 12 ,用拉链法处理冲突,建立的表如下图所示。

我们将每一条链表称为哈希桶,数组成员为每一个索引值相同的多个元素

在这里插入图片描述

3.公共溢出区法

这个方法其实就更加好理解,就是把凡是冲突的家伙额外找个公共场所待着。我们为所有冲突的关键字建立了一个公共的溢出区来存放

就前面的例子而言,我们共有三个关键字37 , 48 , 34 {37,48,34}37,48,34与之前的关键字位置有冲突,那么就将它们存储到溢出表中,如下图所示。

在这里插入图片描述

如果相对于基本表而言,有冲突的数据很少的情况下,公共溢出区的结构对查找性能来说还是非常高的。

🚀二、哈希表的算法实现

为了减少哈希冲突的现象出现,这里我们就讲解一下链地址这种处理方法的实现,一般我们称为哈希链表

1.哈希链表的结构体定义

#define DEFAULT_SIZE 16 //哈希桶的数量

//结点结构
typedef struct _ListNode {
    struct _ListNode *next;
    int key;
    void *data;
}ListNode;

typedef ListNode *List;
typedef ListNode *Element;

//哈希链表结构
typedef struct _HashTable {
    int TableSize;
    List *Thelists;
}HashTable;

2.哈希函数

/*根据 key 计算索引,定位 Hash 桶的位置*/
int Hash(int key, int TableSize)
{
	return (key%TableSize);
}

3.初始化哈希表

/*初始化哈希表*/
HashTable *InitHash(int TableSize)
{
    int i = 0;
    HashTable *hTable = NULL;
    
    if (TableSize <= 0) {
    	TableSize = DEFAULT_SIZE;
    }
    
    hTable = (HashTable *)malloc(sizeof(HashTable));
    if (NULL == hTable)
    {
        printf("HashTable malloc error.\n");
        return NULL;
    }
    hTable->TableSize = TableSize;
    
    //为 Hash 桶分配内存空间,其为一个指针数组
    hTable->Thelists = (List *)malloc(sizeof(List)*TableSize);
    if (NULL == hTable->Thelists)
    {
        printf("HashTable malloc error\n");
        free(hTable);
        return NULL;
    }
    
    //为 Hash 桶对应的指针数组初始化链表节点
    for (i = 0; i < TableSize; i++)
    {
        hTable->Thelists[i] = (ListNode *)malloc(sizeof(ListNode));
        if (NULL == hTable->Thelists[i])
        {
        printf("HashTable malloc error\n");
        free(hTable->Thelists);
        free(hTable);
        return NULL;
        }
    	else
        {
        	memset(hTable->Thelists[i], 0, sizeof(ListNode));
        }
    }
    
    return hTable;
}

4.哈希表插入元素

/*哈希表插入元素,元素为键值对*/
void Insert(HashTable *HashTable, int key, void *value )
{
    Element e=NULL, tmp=NULL;
    List L=NULL;
    e = Find(HashTable, key);
    
    if (NULL == e)
    {
        tmp = (Element)malloc(sizeof(ListNode));
        if (NULL == tmp)
        {
            printf("malloc error\n");
            return;
        }
        L = HashTable->Thelists[Hash(key, HashTable->TableSize)];
        tmp->data = value;
        tmp->key = key;
        tmp->next = L->next;
        L->next = tmp;
    }
    else
    	printf("the key already exist\n");
}

5.哈希表查找元素

/*从哈希表中根据键值查找元素*/
Element Find(HashTable *HashTable, int key)
{
    int i = 0;
    List L = NULL;
    Element e = NULL;
    i = Hash(key, HashTable->TableSize);
    L = HashTable->Thelists[i];
    e = L->next;
    while (e != NULL && e->key != key)
    	e = e->next;
   
    return e;
}

6.哈希表删除元素

/*哈希表删除元素,元素为键值对*/
void Delete(HashTable *HashTable, int key )
{
    Element e=NULL, last=NULL;
    List L=NULL;
    int i = Hash(key, HashTable->TableSize);
    L = HashTable->Thelists[i];
    
    last = L;
    e = L->next;
    while (e != NULL && e->key != key){
        last = e;
        e = e->next;
    }
    
    if(e){//如果键值对存在
        last->next = e->next;
        delete(e);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

web服务器—nginx

一、nginx介绍Nginx(“engine x”)是一款是由俄罗斯的程序设计师Igor Sysoev所开发高性能的 Web和 反向代理服务器&#xff0c;也是一个 IMAP/POP3/SMTP 代理服务器。和apache一样&#xff0c;都是web服务器软件&#xff0c;因为其性能优异&#xff0c;所以被广大运维喜欢。又因…

【python】【protobuf】逆向还原protobuf结构

文章目录一、前言二、示例三、python demo一、前言 在很多场景&#xff0c;都有一个需求&#xff1a; 得到了一个编码后的protobuf数据&#xff08;比如竞品调研的的数据包&#xff09;&#xff0c;需要逆向还原其proto结构文件。 有3种方案去做这件事情&#xff1a; 从编码入…

Linux常用文件管理命令

Linux常用文件管理命令 目录Linux常用文件管理命令前言常用命令练习题创建文件夹题目代码复制题目代码移动题目代码删除题目代码系列操作题目代码前言 本文将讲解我们在使用Linux操作系统时经常需要使用的命令&#xff0c;也可以当成是一篇笔记的记录&#xff0c;当然光看这些…

Ubuntu安装交叉编译器gcc

1.创建文件并把压缩包复制到文件夹下 2.解压到文件夹下 先找到放置的目录 也可以直接找到文件夹右键-在终端打开 通过-C选项指定解压后的目标目录 tar -jxvf gcc-linaro-arm-linux-gnueabihf-4.9-2014.09_linux.tar.bz2 -C /opt 注意:输入文件名时可以Tab键自动补齐 输入…

计算机网络中端到端与点到点的区别

计算机网络中端到端与点到点的区别 数据传输的可靠性是通过数据链路层和网络层的点对点和传输层的端对端保证的。端到端与点到点是针对网络中传输的两端设备间的关系而言的。 在一个网络系统的不同分层中&#xff0c;可能用到端到端传输&#xff0c;也可能用到点到点传输。如…

限流、熔断、服务降级

在分布式系统中&#xff0c;如果某个服务节点发生故障或者网络发生异常&#xff0c;都有可能导致调用方被阻塞等待&#xff0c;如果超时时间设置很长&#xff0c;调用方资源很可能被耗尽。这又导致了调用方的上游系统发生资源耗尽的情况&#xff0c;最终导致系统雪崩。 举例&a…

[Vulfocus解题系列]Spring WebFlow 远程代码执行漏洞(CVE-2017-4971)

简介 Spring WebFlow 是一个适用于开发基于流程的应用程序的框架&#xff08;如购物逻辑&#xff09;&#xff0c;可以将流程的定义和实现流程行为的类和视图分离开来。在其 2.4.x 版本中&#xff0c;如果我们控制了数据绑定时的field&#xff0c;将导致一个SpEL表达式注入漏洞…

科大奥瑞物理实验——声速的测量

实验名称&#xff1a;声速的测量 1. 实验目的&#xff1a; &#xff08;1&#xff09;了解超声波的发射和接收方法。 &#xff08;2&#xff09;加深对振动合成、波动干涉等理论知识的理解。 &#xff08;3&#xff09;掌握用驻波法和相位法测声速。 2. 实验器材&#xff1a…

如何挖掘用户需求的真正动机?关键是4大因素

需求分析实质是挖掘用户内心真正的目标&#xff0c;并转化为产品需求的过程。而用户需求是用户基于自身角度提出的表层需求&#xff0c;这些需求往往有用户期望的产品功能指向。而在产品功能指向的背后&#xff0c;暗藏着潜在的用户动机&#xff0c;这是用户真正希望解决的核心…

【尚硅谷】Java数据结构与算法笔记13 - 图

文章目录一、图的基本介绍1.1 为什么要有图1.2 图的举例说明1.3 图的常用概念二、图的表示方式2.1 邻接矩阵2.2 邻接表三、图的快速入门案例四、图的遍历4.1 深度优先遍历 DFS4.1.1 基本思想4.1.2 算法步骤4.1.3 图示4.2 广度优先遍历 BFS4.2.1 基本思想4.2.2 算法步骤4.2.3 图…

【机器学习】P8 过拟合与欠拟合、正则化与正则化后的损失函数和梯度下降

过拟合与欠拟合、正则化与正则化后的损失函数和梯度下降过拟合与欠拟合过拟合与欠拟合直观理解线性回归中 过拟合与欠拟合逻辑回归中 过拟合与欠拟合过拟合与欠拟合的解决办法过拟合解决方案欠拟合解决方案包含正则化的损失函数正则化线性回归损失函数正则化逻辑回归损失函数包…

java爬虫利器Jsoup的使用

对于长期使用java做编程的程序猿应该知道&#xff0c;java支持的爬虫框架还是有很多的&#xff0c;如&#xff1a;ebMagic、Spider、Jsoup等。今天我们就用Jsoup来实现一个小小的爬虫程序&#xff0c;Jsoup作为kava的HTML解析器&#xff0c;可以直接对某个URL地址、HTML文本内容…

焦虑真的好吗 过度的焦虑存在哪些影响

日常常见的焦虑情绪真的好吗&#xff1f;焦虑是我们七情中的一种正常情绪表现&#xff0c;我们生活当中很多因素都可能会导致我们产生焦虑的情绪表现&#xff0c;如一场考试、一次挑战、一个活动等等。这种焦虑情绪的产生并不是一件坏事&#xff0c;相反&#xff0c;焦虑情绪的…

ROS学习笔记(零):ROS与机器人概述

ROS学习笔记&#xff08;零&#xff09;&#xff1a;ROS与机器人概述ROSROS的起源ROS的特点ROS架构设计机器人机器人的定义机器人的组成执行机构驱动系统传感系统控制系统ROS ROS的起源 ROS&#xff08;Robot Operating System&#xff09;是一个广泛使用的机器人操作系统&…

Python图片相册批处理器的设计与实现批量添加图片水印、批量命名等功能

课题研究使用Python语言开发一个包含批量添加图片水印、批量命名等功能的图片批处理程序&#xff0c;功能模块大概包含以下模块&#xff1a; &#xff08;1&#xff09;首页模块&#xff1a;首页是整个软件的初始页面&#xff0c;包含用户登录、注册、关于本软件等功能&#xf…

红日(vulnstack)5 内网渗透ATTCK实战

环境配置 链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;l8r7 攻击机&#xff1a;kali2022.03 192.168.135.128(NET模式) win7 192.168.138.136 (仅主机模式) 192.168.135.150 (NET模式) win2008 192.168.138.138 (仅主机模式) web渗透 1.nmap探测目标靶机开…

Qt学习笔记之SQLITE数据库

1. SQLite数据库介绍 SQLite&#xff0c;是一款轻型的数据库&#xff0c;是遵守ACID的关系型数据库管理系统&#xff0c;它包含在一个相对小的C库中。它是D.RichardHipp建立的公有领域项目。它的设计目标是嵌入式的&#xff0c;而且已经在很多嵌入式产品中使用了它&#xff0c;…

SpringBoot(1)基础入门

SpringBoot基础入门SpringBoot项目创建方式Idea创建SpringBoot官网创建基于阿里云创建项目手工搭建SpringBoot启动parentstarter引导类内嵌tomcat基础配置属性配置配置文件分类yaml文件yaml数据读取整合第三方技术整合JUnit整合MyBatis整合Mybatis-Plus整合DruidSpringBoot是由…

运动健康路线导入,助力用户轻松导航

华为HMS Core运动健康服务支持通过REST API&#xff0c;以GPX文件格式写入用户路线数据&#xff0c;支持导入轨迹&#xff08;Track&#xff09;或路程&#xff08;Route&#xff09;类型的数据&#xff0c;实现用户路线数据在华为运动健康App中的展示效果。 假若与华为运动健…

​selenium+python做web端自动化测试框架与实例详解教程​

下面有详细的代码介绍&#xff0c;如果不是很明白的话&#xff0c;可以看看这套视频&#xff0c;在哔站学习人数超过数万人&#xff01; 在华为工作了10年的大佬出的Web自动化测试教程&#xff0c;华为现用技术教程&#xff01;_哔哩哔哩_bilibili在华为工作了10年的大佬出的W…