散列
Posted xxMYxx
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了散列相关的知识,希望对你有一定的参考价值。
第 5章 散列
散列表的实现常常叫做散列(hashing),散列是一种用于以常数平均时间执行插入、删除和查找的技术,但是,那些需要元素见任何排序信息的树操作将不会得到有效的支持
5.1 一般想法
理想的散列表数据结构只不过时包含一些项的具有固定大小的数组。
5.2 散列函数
如果输入的关键字是整数,则一般合理的方法就是直接返回key mod tablesize ,除非key具有某些不合乎需要的性质,
5.3 分离链接法
解决冲突的第一种方法叫做分离链接法 其做法是将散列到同一个值的所有元素保存到一个表中,我们可以使用标准库表的实现方法,如果空间很紧,则更可取的方法是避免使用他们因为(这些表是双向链表并且浪费空间)
填充因子 为散列表中元素个数对表的大小的比
链表的平均长度为λ
执行一次查找所需要的工作就是计算散列函数值所需要的常数时间加上遍历表所用的时间,
一次成功的查找需要遍历大约 1+(λ/2)个链
5.4 不用链表的散列表
分离连接散列算法的缺点是使用一些链表,由于给新单元分配地址需要时间(特别是在其他语言中),因此这就导致算法的速度有些减慢,同时算法实际上还要求第二种数据结构才能实现 另一种不用链表的解决冲突的方法,尝试另外的一些单元,知道找出空的单元为之,
这种解决问题的方式导致所需要的表比分离链接散列的表大,其填充因子应该低于0.5
5.4.1 线性探测法
在线性探索法中,函数f是i的线性函数,典型情形是 f(i) = i
只要表足够大,总能找到一个自由的单元,但是如此花费的事件相当的多,更糟糕的是,即使表相对较空,这样占据的单元也会开始形成一些区块,其结果成为一次聚集
5.4.2 平方探测法
平方探测法是消除线性探测中一次聚集问题的冲突解决方法,平方探测就是冲突函数为2次的探测方法
但表的大小超过一半时,当表的大小不是素数时,甚至在表被填充一半之前就不能保证一次就找到空的单元了
表的大小是素数非常重要
在探测散列表中标准的删除操作不能执行,因为相应的单元可能已经引起冲突,所有探测散列表中只能惰性删除
二次聚集 虽然平方探测排除了一次聚集,但是散列到同一个位置上的那些元素将探测相同的备选单元,所以这叫做二次聚集。
5.4.3 双散列
f(i) = i* hash2(x)我们将在第二个散列函数应用到x并距离hash2(x),2hash2(x)
5.5 再散列
为了觉得平方探索,操作时间过长和插入失败的问题,提供了一种解决方法 建立另外一个大约两倍大的表(而且使用一个相关的新散列函数),扫描整个原始散列表,并计算每个(未删除)元素的新散列值并将其插入到新表中
再散列可以用平方探测以多种方法实现 一种做法是表满装到一半 2 极端做法 插入失败时再散列,第三种是 到达某一个装填因子就进行散列
5.6 标准库中的散列表
因为散列表操作中最费时间的部分就是计算hashCode方法,所有String类对hashCode方法进行了优化, 每一个String对象都在存储它的hashCode值,该默认值是0 但若hashCode被调用,那么这个值就会被记住。因此当可以避免hashCode对String对象进行两次计算,因此避免 昂贵的重新计算 这个技巧叫做闪存散列代码
闪存散列代码之所以有效,是因为String类时一个不可变类
5.7 最坏情况下O(1)访问散列表
当有合理的装填因子时和合理的散列函数时,可以期望插入、删除和查找的平均花销都是O(1)
5.7.1 完美散列
每一个二级散列表将用一个不同的散列函数进行构造,知道没有冲入妹纸,如果产生的冲突次数高于要求值,主散列表也可以被构建多次,这种方法称为完美散列
5.7.2 布谷鸟散列
在布谷鸟散列中,假设有N个项,我们维护两个分别超过一半空的表,而且有两个独立的散列函数,可以把每一项分配到每一个表中的位置,布谷鸟是散列中总是会被存储在这两个位置之一
于是焦点问题就在于,存在阻碍插入成功的循环概率有多大,以及成功插入需要替换次数的期望是多少,装填因子小于0.5的时候替换次数的期望是一个常数。
因此在若干次替换被检测到后,我们就可以简单的用新的散列函数重新建表,更N次插入以重新建表,即便如此 也意味着这种花费很,然而表的装填因子达到0.5或者更高,循环的概率就更高,这种方法就不太好用了
5.7.3 跳房子散列
跳房子散列的思路是 用事先确定的 对计算机底层体系结构而言是最优秀的一个常数 给探测序列的最大长度加一个上届。这样做可以给常数的最坏查询时间,并且与布谷鸟散列一样,查询并优化,以同时检查可用位置的有限集
如果某次插入要把一个新的项放到距离它的散列位置太远的地方,我们会很有效的掉头想散列位置走,替换掉潜在项,如果足够谨慎,那么替换可以很快完成,并且保证那些被替换的想都不会放到距离它们的散列位置很远的地方
5.8 通用散列表
尽管散列表很有效,并且子啊装填因子适当的情况下,假设每个操作都有固定的平均花销,但是其表现和分析却取决于散列函数具有以下两种性质
1. 散列函数必须可在常数时间内计算
2. 散列函数必须将各项均匀分布在数组单元中
以你散列函数不好,一切都是徒劳。每个操作的花销都可能是线性的
5.9 可扩散列
本节讨论数据量太大,以至于装不进主存的情况,此时主要考虑是检索数据所需的磁盘存取次数
可扩散列 它使得用两次磁盘访问执行一次查找,插入操作也需要很少的磁盘访问
小结
1. 散列表可以用来以常数平均时间实现insert 和查找操作,当使用散列表时应该注意 注入装填因子 否则时间界将不再有效。仔细选择散列函数也很重要
2. 对于分离链接散列法,虽然装填因子不是很大时性能并不明显降低,但装填因子还是应该接近1 。对于探测散列算法,除非完全不可避免,否则装填因子不应该超过0.5 如果使用线性探测,那么线性随着装填因子接近1将急速下降。
二叉树也可以用来实现insert和contains运算,虽然平均时间界为0(log N)但是二叉树也支持哪些需要序从功能更大的例程,使用散列表不能找出最小元素。
另一方面散列的最坏情况一般来自于实现的错误。而有序的输入却可能使二叉树运行的很差。平衡查找树实现代价很高,因此在不需要使用有序的信息以及对输入是否被排序存在有怀疑,都应该选择散列这种数据结构
散列有丰富的应用,编译器使用散列表追踪代码中声明的变量,这种数据结构叫做符号表
对于任何带有实际名字而非数字节点的图论问题,散列表都是有用的
以上是关于散列的主要内容,如果未能解决你的问题,请参考以下文章