算法--查找--散列表查找
来源:互联网 发布:乐视1s移动数据开关 编辑:程序博客网 时间:2024/04/30 02:23
查找除了线性表的查找(顺序、二分、分块),树上查找(BST、B-树),还有一种散列查找。
相比于前两种查找,散列表的查找效率惊人的高O(1),它采用的直接寻址技术,基本上就是实现了精准打击,达到一击而中的效果。
所谓的直接寻址技术,说白了,就是存储记录时,通过散列函数计算出一个散列地址;读取记录时,通过同样的散列函数计算出地址,然后按照地址访问记录。整个过程有点类似于数学中的映射,一个或者多个key通过映射对应到一个地址,同样也不会存在多个地址对应一个key的情况。
这里提到了一个关键:散列函数
散列函数,其实就是一种映射关系。它的选取,一般会遵循两个原则:从实现上说要求简单,从效果上说要求均匀。认真想一下就不难明白,我们的散列查找本来就要求高效,如果在这个计算地址的过程山一重水一重,那岂不是南辕北辙了?所以越简单越好。那为什么要求均匀呢?这就涉及到另外一个问题,冲突。不妨先假想一下,如果采用的散列函数计算出来的地址个个都一样,再不做特殊处理的话,取值访问的时候,那不是目瞪口呆无从下手了。
那到底该如何选取所谓的散列函数呢,在前辈们的探索中,总结出来一些具有代表性的方法,这里就直接贴出来了。
散列函数的构造
直接定址
名字听起来很高大上,其实是最简单的。
举个例子来说,现在要统计一下班级同学的年龄分布,我们看一下数据,1990年的有2人,1991年的有8人,1992年的有10人…,1999年的有1人。不难发现,所有的时间(key)都是199开头的,我们存储的时候,完全可以用0,1,2…来表示相应的年份,即f(key) = key -1990。特点:实现简单,但存储前必须了解数据分布。
数字分析
既然能分析的,必然是有规律的,不然再分析也没什么卵用。
看个例子,比如要登记公司的员工信息,用手机号做关键字。我们发现公司员工竟然用的都是形如135XXXX1234的手机号,顺便普及一下,135代表号段,XXXX代表地区识别号,1234才是真正的用户编号。存储的时候,何不用直接用最后四位来表示用户的散列地址呢?特点:实现简单,存储前必须了解数据分布,数据必须有规律。
平方取中
通俗易懂,如名所述。将关键字平方之后,取中间的若干位最为散列地址特点:可以没规律,适合关键字较小的数据
折叠
这个比较特别。
当关键字比较大的时候,平方运算就比较吃力了。这个时候可以考虑折叠。折叠的核心在于将关键字从左到右平均拆成若干份,位数不够的补0,然后把这几部分求和,最后的结果作为散列地址。特点:可以没规律,适合关键字较大的数据
随机数法
比较简单,如名所述。
把关键字当成种子,生成随机数作为散列地址。值得说明的是,这里的随机只是一种伪随机,不然访问数据的时候找不到地址,可就尴尬了。特点:适合关键字长度不等
除数留余
这个应该是用的比较多的。
核心思想一般是选取表长m作为除数,用关键字key除以m,所得的余数作为散列地址。特点:除数的选取至关重要
冲突处理
前面已经说过,不管采用哪种方法构造出来的散列函数,最终生成的散列地址都有可能重复,也就是所谓的冲突,只是概率的大小问题。
当冲突不可避免时,就不得不考虑解决方案了。这里介绍常见的四种。
开放定址法
不知道谁起的名字,简直无语之极。说穿了,就是一旦发生了冲突,就去寻找下一个地址,只要散列表足够大,总能找到适合的地址。这模式,跟丑旦我找女朋友一毛一样,好不容易喜欢上一个姑娘,可人家不愿意,我又不想等,那怎么办,赶紧换下一家呗。用数学的语言描述一下这个过程,就是
fi(key) = (f(key)+di) MOD m (di=1,2,…,m-1)di的选取,可以线性,也可以选择平方或者随机数,来提升均匀性。
链地址法
把它俩放一块说,可以比较着来看。还说前面的找女朋友吧,人家不愿意,但是丑旦我脑子被偶像剧安利多了,决定要等等看,说不定女神有一天就能够回眸一笑回心转意,我等啊等,女神没有垂青,我擦嘞反倒是等来了两个情敌…最让我气愤的是,这两个情敌竟然也是个痴情的种子,那咋办嘞?不如组成个联盟,一块等吧。我先来的我是资深备胎,你后来的你是一般备胎,他最后来的他是实习备胎…
假设关键字集合为{12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34},使用除留余数法求散列表。
图片比较丑,但能说明情况,其中*表示初始化的默认值,一般是key不可能取到的值。再哈希
再哈希的核心思想就是,如果一个哈希函数不能保证不重复,那就用两个。经过两个不同的哈希函数计算出来的结果重复的概率就大大降低,所以再哈希的性能是比较好的。用数学语言描述一下就是:
hi=( h(key)+i*h1(key) )%m 0≤i≤m-1 结果显然是由h(key)和h1(key)共同决定的。
公共溢出区
这个就更直白了,开辟两张表:基本表和溢出表,将所有冲突的“情敌”单独存放到溢出表中,可以参考下面的例子。例:假设关键字集合为{12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34},同样使用除留余数法求散列表。
依然比较丑,*表示初始化的默认值,一般是key不可能取到的值。
代码实现
下面使用“除数留余”和“开放地址”来模拟一下哈希查找。
要插入的数据是{ 12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34 };
老规矩,能上代码就不多说话。
public class HashSearch { public static int defaultValue = -1111; /** * <p>name: main</p> * <p>description: </p> * <p>return: void</p> */ public static void main(String[] args) { // TODO Auto-generated method stub Hash hash = initTable(12); int[] array = { 12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34 }; for (int i = 0; i < array.length; i++) { insert(array[i], hash); } for (int i = 0; i < array.length; i++) { System.out.println("" + search(array[i], hash)); } } public static class Hash { int length; int[] address; public Hash(int length) { this.length = length; } } //初始化 public static Hash initTable(int size) { Hash hash = new Hash(size); hash.address = new int[size]; for (int i = 0; i < hash.address.length; i++) { hash.address[i] = defaultValue; } return hash; } //计算哈希值 public static int computeHash(int key,Hash hash) { if (hash.length > 0) { return key % hash.length; } return defaultValue; } //插入 public static void insert(int key, Hash hash) { int index = computeHash(key, hash); while (hash.address[index] != defaultValue) {// 不是默认值,说明冲突 index = (index + 1) % hash.length;// 开放定址法的线性探测,每次加1,往后寻找 } hash.address[index] = key; } //查找 public static boolean search(int key, Hash hash) { int index = computeHash(key, hash); while (hash.address[index] != key) { index = (index + 1) % hash.length; /** * di取值是(0,length-1]. * index==computeHash(key,hash),说明已经加够一个周期了,依然不存在,即可返回; * hash.address[index] == defaultValue,说明出现了空位依然没有找到key,即可返回. */ if (hash.address[index] == defaultValue || index == computeHash(key, hash)) { return false; } } System.out.println("找到" + key + "了,它在表中的第" + index + "个位置"); return true; }}
程序的输出:
找到12了,它在表中的第0个位置true找到67了,它在表中的第7个位置true找到56了,它在表中的第8个位置true找到16了,它在表中的第4个位置true找到25了,它在表中的第1个位置true找到37了,它在表中的第2个位置true找到22了,它在表中的第10个位置true找到29了,它在表中的第5个位置true找到15了,它在表中的第3个位置true找到47了,它在表中的第11个位置true找到48了,它在表中的第6个位置true找到34了,它在表中的第9个位置true
手动计算的结果:
good,两下吻合。
哈希查找就先研究到这里,欢迎留言拍砖嘞。
- 算法--查找--散列表查找
- Java查找算法(五): 散列表查找
- 查找算法—散列表
- 几种查找算法总结与比较—顺序查找、有序查找、散列表查找
- 散列表(哈希表)查找算法
- 散列表(哈希表)查找算法
- 散列表(哈希表)查找算法
- 算法——查找之散列表
- 【Data_Structure笔记10】查找算法之【哈希查找或散列表查找法】
- 散列表查找
- 散列表查找概述
- 散列表的查找
- 散列表查找
- 散列表查找实现
- 查找 之 散列表查找(哈希表)
- 开散列表及其查找算法的实现
- 闭散列表及其查找算法的实现
- 查找算法中的概念(排序树和散列表)
- ACdream 1210 Chinese Girls' Amusement 大数+思维
- 关于字符串指针的一些问题及字符串的左旋转代码
- 文字无限无缝滚动效果——和派孔明
- 在Jenkins中使用Git托管项目的源码创建Build Job(托管在GitHub上面)
- HALF<水题>
- 算法--查找--散列表查找
- 静态成员函数和友元
- hdu 1102 Constructing Roads(最小生成树,prim)
- 旅游
- cvCreateVideoWriter 创建视频文件写入器 用法
- Spring对css、img、js等静态文件拦截的解决办法
- UML建模工具—UMlet使用总结
- Java(数据类型转换)
- const