Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >散列查找和哈希查找_散列检索

散列查找和哈希查找_散列检索

作者头像
全栈程序员站长
发布于 2022-11-15 02:35:08
发布于 2022-11-15 02:35:08
1K00
代码可运行
举报
运行总次数:0
代码可运行

散列表相关概念

散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。建立了关键字与存储位置的映射关系,公式如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
                存储位置 = f(关键字)

这里把这种对应关系f称为散列函数,又称为哈希(Hash)函数。 采用散列技术将记录存在在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表。那么,关键字对应的记录存储位置称为散列地址。   散列技术既是一种存储方法也是一种查找方法。散列技术的记录之间不存在什么逻辑关系,它只与关键字有关,因此,散列主要是面向查找的存储结构。

散列函数的构造方法

2.1 直接定址法

所谓直接定址法就是说,取关键字的某个线性函数值为散列地址,即

优点:简单、均匀,也不会产生冲突。 缺点:需要事先知道关键字的分布情况,适合查找表较小且连续的情况。 由于这样的限制,在现实应用中,此方法虽然简单,但却并不常用。

2.2 数字分析法

如果关键字时位数较多的数字,比如11位的手机号”130****1234”,其中前三位是接入号;中间四位是HLR识别号,表示用户号的归属地;后四为才是真正的用户号。如下图所示。

如果现在要存储某家公司的登记表,若用手机号作为关键字,极有可能前7位都是相同的,选择后四位成为散列地址就是不错的选择。若容易出现冲突,对抽取出来 的数字再进行反转、右环位移等。总的目的就是为了提供一个散列函数,能够合理地将关键字分配到散列表的各个位置。

数字分析法通过适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布比较均匀,就可以考虑用这个方法。

2.3 平方取中法 这个方法计算很简单,假设关键字是1234,那么它的平方就是1522756,再抽取中间的3位就是227,用做散列地址。 平方取中法比较适合不知道关键字的分布,而位数又不是很大的情况。

2.4 折叠法 折叠法是将关键字从左到右分割成位数相等的几部分(注意最后一部分位数不够时可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。 比如关键字是9876543210,散列表表长为三位,将它分为四组,987|654|321|0,然后将它们叠加求和987 + 654 + 321 + 0 = 1962,再求后3位得到散列地址962。 折叠法事先不需要知道关键字的分布,适合关键字位数较多的情况。

2.5 除留余数法 此方法为最常用的构造散列函数方法。对于散列表长为m的散列函数公式为:

mod是取模(求余数)的意思。事实上,这方法不仅可以对关键字直接取模,也可以再折叠、平方取中后再取模。 很显然,本方法的关键在于选择合适的p,p如果选不好,就可能会容易产生冲突。 根据前辈们的经验,若散列表的表长为m,通常p为小于或等于表长(最好接近m)的最小质数或不包含小于20质因子的合数。

2.6 随机数法 选择一个随机数,取关键字的随机函数值为它的散列地址。也就是f(key) = random(key)。这里random是随机函数。当关键字的长度不等时,采用这个方法构造散列函数是比较合适的。 总之,现实中,应该视不同的情况采用不同的散列函数,这里只能给出一些考虑的因素来提供参考: (1)计算散列地址所需的时间 (2)关键字的长度; (3)散列表的长度; (4)关键字的分布情况; (5)记录查找的频率。 综合以上等因素,才能决策选择哪种散列函数更合适。

处理散列冲突的方法   在理想的情况下,每一个关键字,通过散列函数计算出来的地址都是不一样的,可现实中,这只是一个理想。市场会碰到两个关键字key1 != key2,但是却有f(key1) = f(key2),这种现象称为冲突。出现冲突将会造成查找错误,因此可以通过精心设计散列函数让冲突尽可能的少,但是不能完全避免。 3.1 开放定址法 所谓的开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。 它的公式为:

比如说,关键字集合为{12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34},表长为12。散列函数f(key) = key mod 12。 当计算前5个数{12, 67, 56, 16, 25}时,都是没有冲突的散列地址,直接存入,如下表所示。

计算key = 37时,发现f(37) = 1,此时就与25所在的位置冲突。于是应用上面的公式f(37) = (f(37) + 1) mod 12 =2,。于是将37存入下标为2的位置。如下表所示。

接下来22,29,15,47都没有冲突,正常的存入,如下标所示。

到了48,计算得到f(48) = 0,与12所在的0位置冲突了,不要紧,我们f(48) = (f(48) + 1) mod 12 = 1,此时又与25所在的位置冲突。于是f(48) = (f(48) + 2) mod 12 = 2,还是冲突……一直到f(48) = (f(48) + 6) mod 12 = 6时,才有空位,如下表所示。

把这种解决冲突的开放定址法称为线性探测法。

考虑深一步,如果发生这样的情况,当最后一个key = 34,f(key) = 10,与22所在的位置冲突,可是22后面没有空位置了,反而它的前面有一个空位置,尽管可以不断地求余后得到结果,但效率很差。因此可以改进di=12, -12, 22, -22………q2, -q2(q<= m/2),这样就等于是可以双向寻找到可能的空位置。对于34来说,取di = -1即可找到空位置了。另外,增加平方运算的目的是为了不让关键字都聚集在某一块区域。称这种方法为二次探测法。

还有一种方法,在冲突时,对于位移量di采用随机函数计算得到,称之为随机探测法。 既然是随机,那么查找的时候不也随机生成di 吗?如何取得相同的地址呢?这里的随机其实是伪随机数。伪随机数就是说,如果设置随机种子相同,则不断调用随机函数可以生成不会重复的数列,在查找时,用同样的随机种子,它每次得到的数列是想通的,相同的di 当然可以得到相同的散列地址。

总之,开放定址法只要在散列表未填满时,总是能找到不发生冲突的地址,是常用的解决冲突的方法。 3.2 再散列函数法 对于散列表来说,可以事先准备多个散列函数。

这里RHi 就是不同的散列函数,可以把前面说的除留余数、折叠、平方取中全部用上。每当发生散列地址冲突时,就换一个散列函数计算。 这种方法能够使得关键字不产生聚集,但相应地也增加了计算的时间。

3.3 链地址法 将所有关键字为同义词的记录存储在一个单链表中,称这种表为同义词子表,在散列表中只存储所有同义词子表前面的指针。对于关键字集合{12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34},用前面同样的12为余数,进行除留余数法,可以得到下图结构。

此时,已经不存在什么冲突换地址的问题,无论有多少个冲突,都只是在当前位置给单链表增加结点的问题。 链地址法对于可能会造成很多冲突的散列函数来说,提供了绝不会出现找不到地址的保证。当然,这也就带来了查找时需要遍历单链表的性能损耗

3.4 公共溢出区法 这个方法其实更好理解,你冲突是吧?那重新给你找个地址。为所有冲突的关键字建立一个公共的溢出区来存放。

在查找时,对给定值通过散列函数计算出散列地址后,先与基本表的相应位置进行比对,如果相等,则查找成功;如果不相等,则到溢出表中进行顺序查找。如果相对于基本表而言,有冲突的数据很少的情况下,公共溢出区的结构对查找性能来说还是非常高的。

  1. 散列表查找实现
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include<stdio.h>
#include<stdlib.h>
typedef struct hash{
int *elem;  //数据元素存储基地址,动态分配数组 
int count;
}HashTable;
//初始化散列表
void Init_HashTable(HashTable *h,int m)
{
int i;
h->elem=(int *)malloc(sizeof(int)*m);
if(!h->elem)
{
printf("散列表长度不能为0\n"); 
}
for(i=0;i<m;i++)
{
h->elem[i]=NULL;
}
}
//散列函数(采用的是除留余数法)
int Hash(int key,int m)
{
return key%m;
} 
//将数组插入到散列表
void Insert_HashTable(HashTable *h,int key,int m)
{
int addr = Hash(key,m);
//开放地址法的线性探测 
int i;
for(i=1;h->elem[addr];i++)
{
if(!i%2)
{
i=i/2;
}
addr=(key+((-1)^i)*i^2)%m;
}
h->elem[addr]=key; 
}
//散列表查找关键字
void Search_HashTable(HashTable *h,int key,int m)
{
int addr = Hash(key,m);
int i=1;
for(;h->elem[addr]&&h->elem[addr]!=key;i++) //哈希表位置为addr的值不为空,且不等于key,则线性探测 
{
if(!i%2)
{
i=i/2;
}
addr=(key+((-1)^i)*i^2)%m;
}
if(h->elem[addr]==key)                                          
{
printf("the %d is at the %d of the HashTable\n",key,addr);
}else{
printf("no found\n");
}
} 
int main(void)
{
int m = 10,key,i;
HashTable *h;
int a[8]={
10,15,29,36,59,46,68,58};
printf("请输入要查找的数");
scanf("%d",&key);
Init_HashTable(h,m);
for(i=0;i<8;i++)
{
Insert_HashTable(h,a[i],m);
}
Search_HashTable(h,key,m);
for(i=0;i<m;i++)
{
printf("%5d",h->elem[i]);
}
return 0;
} 

5.散列表的性能分析 如果没有冲突,散列查找是所介绍过的查找中效率最高的。因为它的时间复杂度为O(1)。但是,没有冲突的散列只是一种理想,在实际应用中,冲突是不可避免的。 那散列查找的平均查找长度取决于哪些因素呢? (1)散列函数是否均匀 散列函数的好坏直接影响着出现冲突的频繁程度,但是,不同的散列函数对同一组随机的关键字,产生冲突的可能性是相同的(为什么??),因此,可以不考虑它对平均查找长度的影响。 (2)处理冲突的方法 相同的关键字、相同的散列函数,但处理冲突的方法不同,会使得平均查找长度不同。如线性探测处理冲突可能会产生堆积,显然就没有二次探测好,而链地址法处理冲突不会产生任何堆积,因而具有更好的平均查找性能。 (3)散列表的装填因子 所谓的装填因子a = 填入表中的记录个数/散列表长度。a标志着散列表的装满的程度。当填入的记录越多,a就越大,产生冲突的可能性就越大。也就说,散列表的平均查找长度取决于装填因子,而不是取决于查找集合中的记录个数。 不管记录个数n有多大,总可以选择一个合适的装填因子以便将平均查找长度限定在一个范围之内,此时散列表的查找时间复杂度就是O(1)了。为了这个目标,通常将散列表的空间设置的比查找表集合大。

6.散列表的适应范围 散列技术最适合的求解问题是查找与给定值相等的记录。对于查找来说,简化了比较过程,效率会大大提高。   但是,散列技术不具备很多常规数据结构的能力,比如     同样的关键字,对应很多记录的情况,不适合用散列技术;     散列表也不适合范围查找等等。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/234257.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年11月1日 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
机器学习与深度学习中的数学知识点汇总
在机器学习与深度学习中需要大量使用数学知识,这是给很多初学带来困难的主要原因之一。此前SIGAI的公众号已经写过“学好机器学习需要哪些数学知识”的文章,由于时间仓促,还不够完整。今天重新整理了机器学习与深度学习中的主要知识点,做到精准覆盖,内容最小化,以减轻学习的负担同时又保证学习的效果。这些知识点是笔者长期摸索总结出来的,相信弄懂了这些数学知识,数学将不再成为你学好机器学习和深度学习的障碍。
SIGAI学习与实践平台
2019/09/11
1.3K0
机器学习与深度学习中的数学知识点汇总
学好机器学习需要哪些数学知识?
“ 随机过程,实分析。机器学习往深里做肯定需要用这种,高级的数学语言去对问题进行描述。我本人对随机和实分析,其实目前也还只是略懂,很难说,真正的彻底掌握这两门十分强大的数学工具。”
SIGAI学习与实践平台
2018/08/07
1.6K0
学好机器学习需要哪些数学知识?
机器学习5大数学知识(附详细课程资源)
机器学习理论是一个涵盖统计、概率、计算机科学和算法方面的领域,该理论的初衷是以迭代方式从数据中学习,找到可用于构建智能应用程序的隐藏洞察。
Ai学习的老章
2019/08/01
1.4K0
机器学习中的数学基础
导语:现在出现了很多易于使用的机器学习和深度学习的软件包,例如 scikit-learn, Weka, Tensorflow 等等。机器学习理论是统计学、概率学、计算机科学以及算法的交叉领域,是通过从
IT派
2018/03/28
1.2K0
机器学习中的数学基础
练功 | 机器学习应补充哪些数学基础?
? 编者按:很多同学开始学习机器学习时候遇到的最大障碍就是数学基础,机器学习到底需要学习哪些数据知识?要掌握到什么程度呢?希望这篇文章对于大家学习大数据和机器学习有所帮助。 机器学习理论是统计学、概率
小莹莹
2018/04/23
9270
练功 | 机器学习应补充哪些数学基础?
入门AI的数学图谱 | 机器学习涉及的数学知识 | 入门AI系列
在过去几个月里,有几个人联系过我,说他们渴望进军数据科学领域,使用机器学习 (ML) 技术探索统计规律,并打造数据驱动的完美产品。但是,据我观察,一些人缺乏必要的数学直觉和框架,无法获得有用的结果。这是我决定写这篇博客文章的主要原因。最近,易用的机器学习和深度学习工具包急剧增加,比如scikit-learn、Weka、Tensorflow、R-caret等。机器学习理论是一个涵盖统计、概率、计算机科学和算法方面的领域,该理论的初衷是以迭代方式从数据中学习,找到可用于构建智能应用程序的隐藏洞察。尽管机器学习和深度学习有巨大的发展潜力,但要深入掌握算法的内部工作原理并获得良好的结果,就必须透彻地了解许多技术的数学原理。
用户7623498
2020/08/04
6100
不知道如何开始机器学习?这有份初学者指南!
这份指南是为了那些对机器学习感兴趣,但不知如何开始的朋友们准备的。我想大多厌倦在网上搜索大量资料的人都会有挫败感,也放弃了有人能指引他们如何入门的希望。
AI研习社
2018/07/26
4180
不知道如何开始机器学习?这有份初学者指南!
机器学习与深度学习习题集(上)
本文是SIGAI公众号文章作者编写的机器学习和深度学习习题集(上),是《机器学习-原理、算法与应用》一书的配套产品。此习题集课用于高校的机器学习与深度学习教学,以及在职人员面试准备时使用。为了帮助高校更好的教学,我们将会对习题集进行扩充与优化,并免费提供给高校教师使用。对此感兴趣的在校教师和学生可以通过向SIGAI微信公众号发消息获取。习题集的下半部分、所有题目的答案将在后续的公众号文章中持续给出。
SIGAI学习与实践平台
2019/10/14
2.7K0
机器学习与深度学习习题集(上)
人工智能的数学基础 | AI基础
但“数学”二字所包含的内涵与外延太广,到底其中的哪些内容和当前的人工智能技术直接相关呢?
叶锦鲤
2020/02/13
3.2K0
人工智能的数学基础 | AI基础
112页数学知识整理!机器学习-数学基础回顾.pptx
机器学习的基础是数学,数学基础决定了机器学习从业人员的上限,想要学好机器学习,就必须学好数学。
黄博的机器学习圈子
2022/02/23
7100
112页数学知识整理!机器学习-数学基础回顾.pptx
机器学习算法地图2021版
为了帮助大家理清机器学习的知识脉络,建立整体的知识结构,2018年SIGAI推出过机器学习算法地图,纸质版和电子版的阅读量超过10万。两年之后,我们对算法地图进行了优化升级,使得它的结构更为合理清晰,内容更为简洁。下面先看算法地图2021版的整图
SIGAI学习与实践平台
2021/03/22
1.1K0
机器学习算法地图2021版
机器学习中的目标函数总结
几乎所有的机器学习算法都归结为求解最优化问题。有监督学习算法在训练时通过优化一个目标函数而得到模型,然后用模型进行预测。无监督学习算法通常通过优化一个目标函数完成数据降维或聚类。强化学习算法在训练时通过最大化奖励值得到策略函数,然后用策略函数确定每种状态下要执行的动作。多任务学习、半监督学习的核心步骤之一也是构造目标函数。一旦目标函数确定,剩下的是求解最优化问题,这在数学上通常有成熟的解决方案。因此目标函数的构造是机器学习中的中心任务。
SIGAI学习与实践平台
2021/01/05
1.6K0
机器学习中的目标函数总结
怎样成为一名优秀的算法工程师
同时在本微信公众号中,回复“SIGAI”+日期,如“SIGAI0515”,即可获取本期文章的全文下载地址(仅供个人学习使用,未经允许,不得用于商业目的)。
SIGAI学习与实践平台
2018/08/07
6220
怎样成为一名优秀的算法工程师
怎样成为一名优秀的算法工程师
原创声明:本文为 SIGAI 原创文章,仅供个人学习使用,未经允许,不得转载,不能用于商业目的。
SIGAI学习与实践平台
2018/07/12
7340
学机器学习有必要懂数学吗?深入浅出机器学习与数学的关系
小黑,Datawhale团队成员,秦时明月十年铁粉,本科就读于山西大学,保研至天津大学并硕博连读,现为2018级博士,研究方向:脑机接口。
用户1564362
2019/07/04
1.5K1
机器学习的数学基础
我们知道,机器学习的特点就是:以计算机为工具和平台,以数据为研究对象,以学习方法为中心;是概率论、线性代数、数值计算、信息论、最优化理论和计算机科学等多个领域的交叉学科。所以本文就先介绍一下机器学习涉及到的一些最常用的的数学知识。
Ai学习的老章
2019/04/24
9130
机器学习的数学基础
【知识】人工智能数学基础知识
数学是打开科学大门的钥匙。——培根 数学基础知识蕴含着处理智能问题的基本思想与方法,也是理解复杂算法的必备要素。今天的种种人工智能技术归根到底都建立在数学模型之上,要了解人工智能,首先要掌握必备的数学基础知识,具体来说包括: 线性代数:如何将研究对象形式化? 概率论:如何描述统计规律? 数理统计:如何以小见大? 最优化理论: 如何找到最优解? 信息论:如何定量度量不确定性? 形式逻辑:如何实现抽象推理? 线性代数:如何将研究对象形式化 事实上,线性代数不仅仅是人工智能的基础,更是现代数学和以现代数学作为主
陆勤_数据人网
2018/02/26
1.2K0
想入门机器学习?机器之心为你准备了一份中文资源合集
机器之心整理 参与:机器之心编辑部 机器学习日益广为人知,越来越多的计算机科学家和工程师投身其中。不幸的是,理论、算法、应用、论文、书籍、视频等信息如此之多,很容易让初学者迷失其中,不清楚如何才能提升技能。本文作者依据自身经验给出了一套快速上手的可行方法及学习资源的分类汇总,机器之心在其基础上做了增益,希望对读者有所帮助。 先决条件 机器学习的基础是数学。数学并非是一个可选可不选的理论方法,而是不可或缺的支柱。如果你是一名计算机工程师,每天使用 UML、ORM、设计模式及其他软件工程工具/技术,那么请闭
机器之心
2018/05/09
1.2K0
人工智能-数学基础总结
九层之台,起于累土:线性代数 ---- 必备的数学知识是理解人工智能不可或缺的要素,今天的种种人工智能技术归根到底都建立在数学模型之上,而这些数学模型又都离不开线性代数(linear algebra)的理论框架。 在线性代数中,由单独的数 a 构成的元素被称为标量(scalar):一个标量 a 可以是整数、实数或复数。如果多个标量按一定顺序组成一个序列,这样的元素就被称为向量(vector)。显然,向量可以看作标量的扩展。原始的一个数被替代为一组数,从而带来了维度的增加,给定表示索引的下标才能唯一地确定向量
iOSDevLog
2018/06/13
2.8K0
机器学习里,数学究竟多重要?
【新智元导读】本文的主要目的是提供资源,给出有关机器学习所需的数学上面的建议。数学初学者无需沮丧,因为初学机器学习,并不需要先学好大量的数学知识才能开始。正如这篇文章提到的,最基本的需要是数据分析,然后你可以在掌握更多技术和算法的过程中继续学习数学。 过去几个月里,有不少人联系我,向我表达他们对数据科学、对利用机器学习技术探索统计规律性,开发数据驱动的产品的热情。但是,我发现他们中有些人实际上缺少为了获取有用结果的必要的数学直觉和框架。这是我写这篇文章的主要原因。 最近,许多好用的机器和深度学习软件变得十分
新智元
2018/03/23
7740
推荐阅读
相关推荐
机器学习与深度学习中的数学知识点汇总
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验