哈希表

简要

通俗来讲,哈希表是通过函数 (映射关系) 来直接寻找表中存储的关键字,哈希表也是一种数据结构,它是表结构的一种升级拓展,哈希就是一种函数映射,表是一种数据结构,那么合起来就叫哈希表

那么那一张图来描述一下它们的关系:

查找表

那么这里就简单说一下什么是查找表,查找表也是一种常见的数据结构,但是它的结构非常简单,是由同一种类型数据组成的集合,就是里面只有一个数组,来存放同一类型的值(可能是 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}

上面就是对哈希表的操作

数据结构-----------------------哈希表(最通俗易懂的文章)相关推荐

  1. 算法笔记(三)特殊数据结构——哈希表、有序表、并查集、KMP、Manacher、单调栈、位图、大数据类题

    layout: post title: 算法笔记(三)特殊数据结构--哈希表.有序表.并查集.KMP.Manacher.单调栈.位图.大数据类题 description: 算法笔记(三)特殊数据结构- ...

  2. java hashtable 数据结构_数据结构--哈希表(Java)

    数据结构--哈希表(Java) 介绍 哈希表 底层是 数组加链表 或者是 数组加二叉树 ,一个数组里面有多个链表,通过散列函数来提高效率 代码 package cn.guizimo.hashtab; ...

  3. Python中常用的数据结构---哈希表(字典)

    Python中常用的数据结构-哈希表(字典) 常用的数据结构有数组.链表(一对一).栈和队列.哈希表.树(一对多).图(多对多)等结构. 在本目录下我们将讲解,通过python语言实现常用的数据结构. ...

  4. 数据结构——哈希表的详解与实现

    数据结构--哈希表(HashTable) 1.前言 ​ 当我们频繁的查找数据中的某个元素时,我们通常会选择数组来存放数据,因为数组的的内存是连续的,可以直接通过下标访问数据,但是它添加和删除数据比较麻 ...

  5. 数据结构哈希表的实现与设计

    数据结构哈希表查找姓名的课程设计 有没有大神能帮忙写一下这道题,课设的题目.用C++语言 问题描述:针对某公司中花名设计哈希表,并完成相应的建表和查表程序,基本要求: (1)假设花名为汉字拼音形式.名 ...

  6. 数据结构 — 哈希表

    目录 文章目录 目录 哈希表 哈希表 哈希表,又称为散列表,是根据键值对(Key/Value)进行访问的数据结构,它让 Value 经过哈希函数的转换映射到哈希表对应的位置上,查找效率非常高.哈希索引 ...

  7. 浅谈算法和数据结构: 哈希表

    作者: yangecnu(yangecnu's Blog on 博客园) 出处:http://www.cnblogs.com/yangecnu/  http://www.cnblogs.com/yan ...

  8. 哈希表数据结构_Java数据结构哈希表如何避免冲突

    前言 一.哈希表是what? 这是百度上给出的回答: 简而言之,为什么要有这种数据结构呢? 因为我们想不经过任何比较,一次从表中得到想要搜索的元素.所以就构造出来了哈希表,通过某种函数(哈希函数)使元 ...

  9. openssl lhash 数据结构哈希表

    哈希表是一种数据结构,通过在记录的存储位置和它的关键字之间建立确定的对应关系,来快速查询表中的数据: openssl lhash.h 为我们提供了哈希表OPENSSL_LHASH 的相关接口,我们可以 ...

最新文章

  1. UIButton设置 textAlignment 属性的方法
  2. php桶排序,PHP实现桶排序算法
  3. 使用iPhone相机和OpenCV来完成3D重建(第一部分)
  4. 机器学习:利用核函数进行非线性分类
  5. TensorFlow 2 Object Detection API 教程: 安装
  6. bestcoder#22NPY and girls
  7. 网站视频很卡怎么办?有没有免费的视频平台?使用阿里云OSS对象云存储+下行流量包解决网站文件/视频访问慢问题
  8. 怎样把本地文档共享至服务器上,利用云服务器共享本地文件
  9. 使用Java编写爬虫,下载百度图片
  10. Hybrid Dilated Convolution学习笔记
  11. jenkins下载与简单使用
  12. Gradle may disable incremental compilation as the following annotation processors are not ......
  13. input输入框设置输入值
  14. 【树莓派4B】如何烧录新的系统
  15. tag untag_Vlan中的 PVID vid tag untag 常识理论
  16. 计算机控制菜单在哪里,电脑控制面板在哪里打开(控制面板怎么设置邮件)
  17. 压力测试工具WCAT
  18. Word 使用小技巧(转)
  19. 苹果CMS,苹果CMS网站搭建,苹果CMS采集资源
  20. 五、Git 与 SVN 区别

热门文章

  1. Android Studio配置优化最全详解
  2. vscode配置c语言并优化
  3. 三年级语文计算机之父教学反思,三年级语文教学反思15篇
  4. 睡眠排序算法c语言实现,Linux 进程必知必会
  5. Oralce数据库断电之ORA-00600: 内部错误代码, 参数: [kcratr_nab_less_than_odr], [1], [37]
  6. 用phpcms切换中英文网页的方法(不用解析二级域名)、phpcms完成pc和手机端切换(同一域名)...
  7. 一位厦门大学CV硕士毕业生文言文致谢!畅聊三年求学路
  8. html 鼠标悬停效果,30个帅气的鼠标悬停效果
  9. java 获取最近12个月(包含当月)
  10. 关于表的创建(第二次作业)