哈希表 学习顺序 及 个人心得

简介:
	Hash表也称散列表,也有直接译作哈希表,Hash表是一种特殊的数据结构,根据关键码值(Key value)而直接进行访问的数据结构,它同数组、链表以及二叉排序树等相比较有很明显的区别,它能够快速定位到想要查找的记录,而不是与表中存在的记录的关键字进行比较来进行查找。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度,存放记录的数组叫做哈希表。

理解:
哈希表(hash table)是从一个集合A到另一个集合B的映射(mapping)。
	映射是一种对应关系,而且集合A的某个元素只能对应集合B中的一个元素。但反过来,集合B中的一个元素可能对应多个集合A中的元素。如果B中的元素只能对应A中的一个元素,这样的映射被称为一一映射。这样的对应关系在现实生活中很常见,比如:
          A  -> B
          人 -> 身份证号
          日期 -> 星座
上面两个映射中,人 -> 身份证号是一一映射的关系。在哈希表中,上述对应过程称为hashing。A中元素a对应B中元素b,a被称为键值(key)b被称为a的hash值(hash value)。

映射在数学上相当于一个函数f(x):A->B。比如 f(x) = 3x + 2。哈希表的核心是一个哈希函数(hash function),这个函数规定了集合A中的元素如何对应到集合B中的元素。比如:

          A: 三位整数    hash(x) = x % 10    B: 一位整数

          104                               4

          876                               6

          192                               2

上述对应中,哈希函数表示为hash(x) = x % 10。也就是说,给一个三位数,我们取它的最后一位作为该三位数的hash值。

	注意,hash只要求从A到B的对应为一个映射,它并没有限定该对应关系为一一映射。因此会有这样的可能:两个不同的键值对应同一个hash值。这种情况叫做hash碰撞(hash collision)或者hash 冲突

Hash函数

Hash函数设计的好坏直接影响到对Hash表的操作效率。下面举例说明: 假如对上述的联系人信息进行存储时,采用的Hash函数为:姓名的每个字的拼音开头大写字母的ASCII码之和           address(张三)=ASCII(Z)+ASCII(S)=90+83=173;           address(李四)=ASCII(L)+ASCII(S)=76+83=159;           address(王五)=ASCII(W)+ASCII(W)=87+87=174;           address(张帅)=ASCII(Z)+ASCII(S)=90+83=173; 假如只有这4个联系人信息需要进行存储,这个Hash函数设计的很糟糕。首先,它浪费了大量的存储空间,假如采用char型数组存储联系人信息的话,则至少需要开辟174*12字节的空间,空间利用率只有4/174,不到5%;另外,根据Hash函数计算结果之后,address(张三)和address(李四)具有相同的地址,这种现象称作冲突,对于174个存储空间中只需要存储4条记录就发生了冲突,这样的Hash函数设计是很不合理的。所以在构造Hash函数时应尽量考虑关键字的分布特点来设计函数使得Hash地址随机均匀地分布在整个地址空间当中。所以我们得想办法解决,那就是在选择哈希函数的事了。
构建哈希函数的几种方法

1.直接定址法
取关键字或者关键字的某个线性函数为Hash地址,即address(key)=a*key+b;如知道学生的学号从2000开始,最大为4000,则可以将address(key)=key-2000作为Hash地址。

2.平方取中法
对关键字进行平方运算,然后取结果的中间几位作为Hash地址。假如有以下关键字序列{421,423,436},平方之后的结果为{177241,178929,190096},那么可以取{72,89,00}作为Hash地址。

3.折叠法
将关键字拆分成几部分,然后将这几部分组合在一起,以特定的方式进行转化形成Hash地址。假如知道图书的ISBN号为8903-241-23,可以将address(key)=89+03+24+12+3作为Hash地址。

4.除留取余法
如果知道Hash表的最大长度为m,可以取不大于m的最大质数p,然后对关键字进行取余运算,address(key)=key%p。在这里p的选取非常关键,p选择的好的话,能够最大程度地减少冲突,p一般取不大于m的最大质数。

5.数字分析法
假设关键字是以r为基的数,并且哈希表中可能出现的关键字都是事先知道的,则可取关键字的若干数位组成哈希地址。       

例如有某些人的生日数据如下:

          年. 月. 日

          75.10.03
          85.11.23
          86.03.02
          86.07.12
          85.04.21
          96.02.15

经分析,第一位,第二位,第三位重复的可能性大,取这三位造成冲突的机会增加,所以尽量不取前三位,取后三位比较好

6.随机数法
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即

H(key)=random(key) ,其中random为随机函数。通常用于关键字长度不等时采用此法。


哈希冲突:
哈希表处理冲突主要有开放定址法再散列法链地址法(拉链法)建立一个公共溢出区四种方法。

通过构造性能良好的哈希函数,可以减少冲突,但一般不可能完全避免冲突,因此解决冲突是哈希法的另一个关键问题。创建哈希表和查找哈希表都会遇到冲突,两种情况下解决冲突的方法应该一致。下面以创建哈希表为例,说明解决冲突的方法。常用的解决冲突方法有以下四种:

1.开放定址法

这种方法也称再散列法,其基本思想是:当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中。这种方法有一个通用的再散列函数形式:Hi=(H(key)+di)%m   i=1,2,…,n,其中H(key)为哈希函数,m 为表长,di称为增量序列。增量序列的取值方式不同,相应的再散列方式也不同。主要有以下三种:

(1) 线性探测再散列

          di=1,2,3,…,m-1

这种方法的特点是:冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。

(2) 二次探测再散列

          di=12,-12,22,-22,…,k2,-k2    ( k<=m/2)

这种方法的特点是:冲突发生时,在表的左右进行跳跃式探测,比较灵活。

(3) 伪随机探测再散列

          di=伪随机数序列。

具体实现时,应建立一个伪随机数发生器,(如i=(i+p) % m),并给定一个随机数做起点。

线性探测再散列的优点是:只要哈希表不满,就一定能找到一个不冲突的哈希地址,而二次探测再散列和伪随机探测再散列则不一定。

2.再哈希法

这种方法是同时构造多个不同的哈希函数:

          Hi=RH1(key),i=1,2,3,…,n.

当哈希地址Hi=RH1(key)发生冲突时,再计算Hi=RH2(key)……,直到冲突不再产生。这种方法不易产生聚集,但增加了计算时间。

3. 拉链法

这种方法的基本思想是将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数组T[0..m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。链地址法适用于经常进行插入和删除的情况。

拉链法的优点
与开放定址法相比,拉链法有如下几个优点:

(1)拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
(2)由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
(3)开放定址法为减少冲突,要求装填因子α(装填因子=表中的记录数/哈希表的长度)较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
(4)在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。而对开放地址法构造的散列表,删除结点不能简单地将被删结点的空间置为空,否则将截断在它之后填入散列表的同义词结点的查找路径。这是因为各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。 因此在用开放地址法处理冲突的散列表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点。

拉链法的缺点是:
	指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度。

4.建立公共溢出区

这种方法的基本思想是:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表.(注意:在这个方法里面是把元素分开两个表来存储)

	冲突太多了怎么办?
		当冲突太多的时候,我们一般采用的方法时拉链法,采用拉链法的原因是动态申请空间,至于优点在上面已经阐述了.冲突太多的时候会产生堆积状态,我们将H(key)相同的关键字都统一放到一个链里,当出现冲突的时候我们就把该元素接在链表后面,这样可以避免产生堆积现象,缩短平均查找长度.


当数据表太小,而数据太多的时候怎么办?
	当数据表太小数据太多可以通过建立一个溢出表,专门用来存放哈希表中放不下的记录.


参考:
http://blog.csdn.net/liufei_learning/article/details/19220391
	http://blog.csdn.net/liangbopirates/article/details/9753599
	http://www.cnblogs.com/dolphin0520/archive/2012/09/28/2700000.html
	http://blog.csdn.net/jirongzi_cs2011/article/details/9377779
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值