数据结构-----------------------哈希表(最通俗易懂的文章)
哈希表
简要
通俗来讲,哈希表是通过函数 (映射关系) 来直接寻找表中存储的关键字,哈希表也是一种数据结构,它是表结构的一种升级拓展,哈希就是一种函数映射,表是一种数据结构,那么合起来就叫哈希表
那么那一张图来描述一下它们的关系:
查找表
那么这里就简单说一下什么是查找表,查找表也是一种常见的数据结构,但是它的结构非常简单,是由同一种类型数据组成的集合,就是里面只有一个数组,来存放同一类型的值(可能是 int,char,或者是一个结构体),主要还是放 关键字,用来查找所用,然后再加上查找的函数,就是你怎么查找,用顺序还是二分查找这个关键字,合起来就是查找表
就是 一个数组(存放关键字) + 查找关键字的方法(函数) == 查找表
因此该结构产生的目的主要是方便去快速查找一个值,例如在手机中,电话簿里面存储一堆人的电话号码,那么这个电话簿就是一个查找表,为了方便去查找,那么你设置的姓名就是该表的关键字,你去寻找王五的电话,那么直接去按照它的开头字母即可,那么这就是表的作用。
总而言之,就是为了更快速去查找关键字设置的,表里面存关键字。当然查找表还分静态和动态,静态查找表是先往数组中存储一堆值,只能查找,不能增删。动态查找表是边查找边插值,如果有就可以查找到,如果没有,那么就插入值,还能删除。
具体的这里就不细说,主要说一下哈希表
哈希表
既然哈希表也属于查找表,那么哈希表里面有一个数组来存储关键字,它和普通的表就是它一般不用去比较关键字,不用去用 > < =比较,而哈希表只需要用 = 去比较,并且如果你哈希表设计的比较好的话,有时候可以直接就可以找到关键字,它也是属于动态查找表,边查找边插值。
学习哈希表建议去先看 静态表和二叉排序树,那么你就可以知道哈希表的强大,可以直接通过函数换算直接去找关键字。可能需要多次比较值才能找到你想要的,比较次数多了说明你设计的这个哈希函数不好。
例子
例如之前的最简单的静态查找表,就是表里面有一个数组,然后查找方法是顺序查找,就是从第一个开始然后一个接一个比较,直到找到你要的关键值,但是哈希表是通过函数计算直接找到,例如你电话簿存的五个人的电话,张三,李四,王五,赵六四个人,先存到表中:
那么就往数组按顺序存到里面了(查找表结构就是里面有一个数组,就和之前的线性表一样,只不过它多了一个查找的函数)
你要去找赵六的电话,那么他就开始从数组开头来找,先张三,发现不对,然后下一个,还不对,依次类推,比较了四次(最后一个虽然已经找到了,但是也是经过了比较才知道是相等的,所以也算一次比较),计算机最忌讳的就是比较,因为耗费的时间和空间资源比较大,但是哈希表做了很好的改进。
哈希是一个函数(也可以叫成一种映射关系,大学高中里面都学过的,其实就是一个函数,最简单的理解),将关键字通过函数计算后,可以根据函数计算值快速找到关键字,若数组中有你查找的关键字,就返回,没有那么就插入(这就是动态表的特点)。因此它创建的时候其实也是一个一个根据哈希函数换算插入的,然后查找的时候再根据函数计算直接找关键字。那么还是以上面那个例子:
首先你先在查找表创建一个容量为30的数组 C [30] ,然后设置哈希函数 f 为 :将关键字首字母换算成数字。(哈希函数)
那么将张三李四王五进行哈希函数计算:
关键字通过哈希函数计算下来的就是 该关键字在数组中的位置(也可以说函数计算后返回的是该关键字的地址),这就是哈希函数的精髓,这三个人通过函数计算后那么依次放到数组中的位置:
所以你每次查找一个关键字,先进行哈希函数计算后便可以直接找到值,你要找张三,那么函数计算 f(张三)= 26 ,直接在表里的 数组 C[26] 找关键字,比较一下你输入的张三,如果相同,那么直接返回。所以不用像顺序查找那样,比较多次,这里直接函数计算,返回一个地址,根据这个地址去找。
当然你创建查找表必须拿哈希函数去创建,并不是你先随便插入一堆值,然后用哈希函数去查找,那显然脑子chou了,因为它是动态的,所以创建的时候其实是从空表一个一个插入的,和静态最大的区别,静态先一次性插入一堆值,再查找
这个哈希函数当然可以随便定义,例如定义成 f (key) = 关键字首字的笔画,还是张三李四王五,那么在数组中呈现的又是另外一种顺序,当然这里也不是让你瞎定义,哈希函数也有好坏,那么好坏是如何定义的?
冲突
什么是冲突?还是上面的例子
现在你又要存一个赵六的电话,通过哈希函数计算后 f (赵六) = 26,返回一个地址,根据地址去数组中插值,发现 26 位置的已经被占了啊,被张三的电话占了,拿你不可能一块挤挤,都放26吧?这就叫冲突,就是你函数计算后的地址发现已经有关键字了,被占了,那只能换一个位置,比如下一个地址 27
那么这种方法就叫解决冲突,你要是再存一个赵四,函数计算后还是 26 ,26有值,27也有值了,那就往后推存28,这种方法叫线性探测再散列,其实是比较简单的,但是也反应了一个问题,你这种存值到了后面好像在计算后都对不上它的地址了,都需要一直往后推的存,比如上面的,你再查找赵六的话,计算出来了的地址26,发现关键字值对不上往后挪,再看是否相等,如果需要一直往后挪呢,又比较了很多次了,那不是返璞归真?那设置的就没有意义了,所以此时说明你哈希函数设置比较 烂了,需要换一个。
当然为了解决这种冲突现象,要设置一个完美的哈希函数,需要根据你插入的关键字的特点去设置函数,有很多方法,直接定址,数字分析,平方取中,除留取余等等,这里就不去解释了,可以网上找资料,哈希函数怎么设置最完美,就是让他发生冲突次数最少,
处理冲突
当然,你设置的哈希函数再完美,当你面对成千上万的值它还是避免不了有冲突的现象,这时候就有三种方法来解决这种冲突:线性探测再散列,二次探测再散列,伪随机探测再散列
线性探测再散列
这个上面的例子就是用的该方法,你发现你要插入的地址有值,那么就将地址后移 1 位,一直类推,如果一直有就一直加 1,直到空为止,同理查找的时候也是,发现你查找的地址与你的关键字对不上,那么就将地址后移 1 位,一直比对,地址一直加 1 ,直到相等了为止
那么函数实现:
collision(int p) //线性探测再散列函数,p为传入的地址,要进行冲突处理的地址
{p++; //将p地址后移 1 位return p; //返回处理冲突后的地址
}
最简单的一种处理方法
二次探测再散列
思路和上面的一样,不过它不是在原来的地址上加 1 了而是加一串的数组的值,当然这个数组中的值是有规律的,所以为什么要将该方法要起成二次探测 :
你会发现这个数列中的数是有特点的,用方程表示为 ( (-1) ^(n+1) )* [n/2]
(这里 [ n/2 ] 是超过的最小整数,比如算出0.5那么取1,算出1.5,那么就取值2,这里不好打那个符号就是 下面图片中的符号,表示取超过的最小整数)
带入 n=1,2,3,4,可以试一下,那么如何实现二次探测?放发生冲突的时候,线性探测是将地址加 1 ,而二次探测就是加上面数组中的值,n初始化为1,表示第一次冲突处理,那么先加 1,如果发现还是冲突的话(就是发现修改后的地址中仍然有关键字不能往进存关键字的时候),那么再冲突处理,n=2,此时加的值就为 -1 ,如果还是发生冲突,那么继续冲突处理,n=3,地址所加的值就是 2 ,依次类推,直到找到空,同理查找也是这样,线性探测中就讲解了,这里不说了。
那么它的函数实现就是:
collision(int p,int n) //p表示要冲突处理的地址,n表是二次探测的次数,应该是个全局变量否则无法记住你已经进行了几次二次探测
{p = p + pow(-1,n+1)*ceil(n/2); //pow函数表示求n次方函数,后面表示求超过的最小整数return p;
}
伪随机探测再散列
这个和前面的两个一样,如果发生冲突,还是在地址上加一个数,只不过和第一二个不同,它加的是一个随机数,你需要用伪随机数生成算法生成一个数组,例如:{ 1,5,2,3,9,12,4}
然后如果发生冲突的话从第一个开始加,如果地址还是冲突那么加第二个,依次类推,算法设计这里就不说了,可以网上查,前两种方法其实差不多就够了,这种伪随机数可以尽量避免多次发生冲突,因为它冲突处理后的地址是随机的,所以可能大概率避免再次冲突。
链地址法
这种方法也是处理冲突比较高效的,不用去地址迁移存储了,只需要将表(表中的数组)的每个空设置成一个链表即可,如果没有关键字,就设置为空,如果有了就存放到当前地址的链表中,如果发生冲突了,接到当前地址链表的后面。还是举上面的例子,张三李四王五赵六的电话存储:
每个空都是一个链表 ,三角表示空,没有存入任何关键值,先将关键值哈希计算后,得到地址,就往该地址的链表中存关键值,如上图
然后再插入赵六的关键值,那么哈希计算后得26,此时不用作地址推移,直接放到该链表末尾。
那么如何查找?
还是和之前一样,先将关键字进行哈希计算后得到地址,根据地址去数组中查找,只不过链式表的每一项都成了一个链表,这里就涉及到了链表的查找,在上面的文章中都有。比如你要查王五的关键字,那么进行哈希计算:f(王五)=23,那么就去23位置找,按顺序来查找链表,第一个不是那么就到下一个。
然后查赵六 ,哈希计算后地址为26,然后根据地址找,比对第一个,发现 张三 !=赵六,说明第一个关键字不是,那么就根据指针找到下一个,比对关键字,发现正确,那么就返回关键字,其余都是这么查找,直到指针指向空为止,说明链表已经空了。返回NULL,或者插入该关键字。
哈希表实现例子
那么现在就写一个简单的哈希表创建,用简单的冲突处理,线性探测再散列,假设哈希函数为除以11求余,得到余数为几,那么就往数组中的对应位置中存储。
哈希函数为:f (x) = x mod 11
那么开始存一堆数,这里只是例子,不需要大量的数来做示范,所以数组创建的小点,C[15]。这里需要一个哈希表结构的创建,就是普通的线性表,可以看下面文章,里面有线性表如何创建还有原理
线性表的创建
(当然你想使用哈希表,肯定得先学会表数据结构,如果你只是想简单的了解哈希表的原理,那就不需要了,数据结构是相互之间有联系的,前面的表不会,那么哈希表的创建也不懂了)
typedef struct //创建哈希表
{int *elem; //创建表里面的数组(线性表其实就是里面有个数组存值)int count; //设置当前的元素数量
}HashTable;
因为之前说的哈希表是一个动态表,它是边查边插入的,一开始应该是个空表,然后一次一次查找,发现没有,那么就插入的过程,逐渐建立起来的表。那么上面说的文章中有线性表的初始化,所以哈希表的初始化代码:
void init_Hash(HashTable &table)
{table.elem=(int*)malloc(15*sizeof(int)); //给数组分配空间table.count=0; //因为表一开始为空的,所以设置当前元素数量为0for(int i=0;i<15;i++) //对表中数组每一项初始化为0,好方便后面比较,设置为0表示该空还没有存关键字{table.elem[i]=0; //将数组中的每一项值设置为0,表示为空}
}
上面简单的创建好了哈希表框架,但是只是存储的结构,还需要查找和插入函数,先搞查找函数,为什么呢?因为插入函数是建立在查找函数上的,你插入的前提是先去表中查找该关键字,发现没有才去选择插入的(当然是发现插入的位置为空时候,如果不为空,并且不相等,进行冲突检测,直到找到空位置),所以一开始明知道是空表还是用查找函数。具体流程为下图
哈希表查找函数:
//设置全局变量
int p; //设置地址p为全局变量,这里的原因是方便后面查找插入操作,对p进行处理后的地址直接可以使用//首先得有哈希函数,否则你查找插入操作都没有依据
int Hash(int K) //K表示要进行哈希计算的关键字
{return K%11; //哈希函数设置,求余得到的值就是数组中的位置
} //查找函数
int SerchHash(int K,HashTable &table) //K表示要查找的关键字
{p=Hash(K); //得到关键字经过哈希函数计算后的位置while(table.elem[p]!=0&&table.elem[p]!=K) //可以看上图,先进行判断,首先看该位置是否为空,或者一下就找到该关键字,直接进行到下面,若是不空并且与查找的关键字不相等,说明该空已经被占,那么进行冲突处理{p++; //该处为冲突处理,这里用了简单的线性探测再散列,原理上面已经给了,直接往后一直挪单位为1 的,如果挪了还是不符合,那么继续while循环,当然这里也可以将冲突处理封装成一个函数}
//上面这个循环只是用来得到一个 地址 ,这个地址只有两种情况,找到了和找到了空的位置if(table.elem[p]==K) //判断下该位置是否是该关键字return 1; //若是返回1表示找到了else return 0; //那么0自然是没有找到,找到了一个空,那么就是后面插入函数的前提,找到一个空位置才能插入} //插入函数
int InsertHash(int K,HashTable &table)
{if(SerchHash(K,table)) //看上面的serch函数,如果返回1,说明该关键字已经存在了,不需要插入了,返回0,说明没有该关键字开始下面的插入操作,那么这里就是设置全局变量的好处,因为你serch函数已经将 地址p处理完了,最后的p是指向空的位置或者是找到关键字的位置,然后下面对地址p直接可以插入就可以了,调用serch函数中的p不太方便,在函数外就不能对p进行调用,所以要将p设置为全局变量return 0;else //进行到这,说明没有找到关键字,而是找到了一个空位置,那么这时候的p已经是处理完的地址,就是p指向的已经是空位置table.elem[p]=K; //插入值
}
实现操作:
int main()
{HashTable table; //创建哈希表init_Hash(table); //初始化表InsertHash(14,table); //插入操作,进行哈希计算后,该位置应该存到 3 的位置printf("%d",table.elem[3]); //然后对表中数组 3 位置的值进行输出可得值为 14}
上面就是对哈希表的操作
数据结构-----------------------哈希表(最通俗易懂的文章)相关推荐
- 算法笔记(三)特殊数据结构——哈希表、有序表、并查集、KMP、Manacher、单调栈、位图、大数据类题
layout: post title: 算法笔记(三)特殊数据结构--哈希表.有序表.并查集.KMP.Manacher.单调栈.位图.大数据类题 description: 算法笔记(三)特殊数据结构- ...
- java hashtable 数据结构_数据结构--哈希表(Java)
数据结构--哈希表(Java) 介绍 哈希表 底层是 数组加链表 或者是 数组加二叉树 ,一个数组里面有多个链表,通过散列函数来提高效率 代码 package cn.guizimo.hashtab; ...
- Python中常用的数据结构---哈希表(字典)
Python中常用的数据结构-哈希表(字典) 常用的数据结构有数组.链表(一对一).栈和队列.哈希表.树(一对多).图(多对多)等结构. 在本目录下我们将讲解,通过python语言实现常用的数据结构. ...
- 数据结构——哈希表的详解与实现
数据结构--哈希表(HashTable) 1.前言 当我们频繁的查找数据中的某个元素时,我们通常会选择数组来存放数据,因为数组的的内存是连续的,可以直接通过下标访问数据,但是它添加和删除数据比较麻 ...
- 数据结构哈希表的实现与设计
数据结构哈希表查找姓名的课程设计 有没有大神能帮忙写一下这道题,课设的题目.用C++语言 问题描述:针对某公司中花名设计哈希表,并完成相应的建表和查表程序,基本要求: (1)假设花名为汉字拼音形式.名 ...
- 数据结构 — 哈希表
目录 文章目录 目录 哈希表 哈希表 哈希表,又称为散列表,是根据键值对(Key/Value)进行访问的数据结构,它让 Value 经过哈希函数的转换映射到哈希表对应的位置上,查找效率非常高.哈希索引 ...
- 浅谈算法和数据结构: 哈希表
作者: yangecnu(yangecnu's Blog on 博客园) 出处:http://www.cnblogs.com/yangecnu/ http://www.cnblogs.com/yan ...
- 哈希表数据结构_Java数据结构哈希表如何避免冲突
前言 一.哈希表是what? 这是百度上给出的回答: 简而言之,为什么要有这种数据结构呢? 因为我们想不经过任何比较,一次从表中得到想要搜索的元素.所以就构造出来了哈希表,通过某种函数(哈希函数)使元 ...
- openssl lhash 数据结构哈希表
哈希表是一种数据结构,通过在记录的存储位置和它的关键字之间建立确定的对应关系,来快速查询表中的数据: openssl lhash.h 为我们提供了哈希表OPENSSL_LHASH 的相关接口,我们可以 ...
最新文章
- UIButton设置 textAlignment 属性的方法
- php桶排序,PHP实现桶排序算法
- 使用iPhone相机和OpenCV来完成3D重建(第一部分)
- 机器学习:利用核函数进行非线性分类
- TensorFlow 2 Object Detection API 教程: 安装
- bestcoder#22NPY and girls
- 网站视频很卡怎么办?有没有免费的视频平台?使用阿里云OSS对象云存储+下行流量包解决网站文件/视频访问慢问题
- 怎样把本地文档共享至服务器上,利用云服务器共享本地文件
- 使用Java编写爬虫,下载百度图片
- Hybrid Dilated Convolution学习笔记
- jenkins下载与简单使用
- Gradle may disable incremental compilation as the following annotation processors are not ......
- input输入框设置输入值
- 【树莓派4B】如何烧录新的系统
- tag untag_Vlan中的 PVID vid tag untag 常识理论
- 计算机控制菜单在哪里,电脑控制面板在哪里打开(控制面板怎么设置邮件)
- 压力测试工具WCAT
- Word 使用小技巧(转)
- 苹果CMS,苹果CMS网站搭建,苹果CMS采集资源
- 五、Git 与 SVN 区别
热门文章
- Android Studio配置优化最全详解
- vscode配置c语言并优化
- 三年级语文计算机之父教学反思,三年级语文教学反思15篇
- 睡眠排序算法c语言实现,Linux 进程必知必会
- Oralce数据库断电之ORA-00600: 内部错误代码, 参数: [kcratr_nab_less_than_odr], [1], [37]
- 用phpcms切换中英文网页的方法(不用解析二级域名)、phpcms完成pc和手机端切换(同一域名)...
- 一位厦门大学CV硕士毕业生文言文致谢!畅聊三年求学路
- html 鼠标悬停效果,30个帅气的鼠标悬停效果
- java 获取最近12个月(包含当月)
- 关于表的创建(第二次作业)