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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
散列表
是根据键 (Key) 而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。
周三不加班
2019/09/03
7330
散列表
数据结构之哈希表(HASH)
   当我们在编程过程中,往往需要对线性表进行查找操作。在顺序表中查找时,需要从表头开始,依次遍历比较a[i]与key的值是否相等,直到相等才返回索引i;在有序表中查找时,我们经常使用的是二分查找,通过比较key与a[i]的大小来折半查找,直到相等时才返回索引i。最终通过索引找到我们要找的元素。    但是,这两种方法的效率都依赖于查找中比较的次数。我们有一种想法,能不能不经过比较,而是直接通过关键字key一次得到所要的结果呢?这时,就有了散列表查找(哈希表)。
全栈程序员站长
2022/07/21
6080
数据结构之哈希表(HASH)
哈希表基本概念介绍及哈希冲突的处理方法(附源码)
  哈希表(散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做哈希(散列)函数,存放记录的数组叫做哈希(散列)表。
嵌入式与Linux那些事
2021/05/20
9440
哈希表基本概念介绍及哈希冲突的处理方法(附源码)
程序员必读:教你摸清哈希表的脾气
在哈希表中,记录的存储位置 = f (关键字),通过查找关键字的存储位置即可,不用进行比较。散列技术是在记录的存储位置和它的关键字之间建立一个明确的对应关系f 函数,使得每个关键字 key 对应一个存储位置 f(key) 且这个位置是唯一的。这里我们将这种对应关系 f 称为散列函数,又称为哈希(Hash)函数。采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)。
谭庆波
2018/08/10
3970
程序员必读:教你摸清哈希表的脾气
散列表
http://blog.csdn.net/yyxaf/article/details/7527878 搜索关键词:散列函数、散列表、哈希函数、哈希表、Hash函数、Hash表 散列方法不同于顺序查找、二分查找、二叉排序树及B-树上的查找。它不以关键字的比较为基本操作,采用直接寻址技术。在理想情况下,无须任何比较就可以找到待查关键字,查找的期望时间为O(1)。 散列表的概念 1、散列表 设所有可能出现的关键字集合记为U(简称全集)。实际发生(即实际存储)的关键字集合记为K(|K|比|U|小得多)。 散列方
用户1624346
2018/04/17
1K0
散列表
总结:这上面三种方法都是在同一个数组中进行处理,没有超过数组的范畴,改变的都是d的取值方式
大忽悠爱学习
2021/11/15
6520
《大话数据结构》 查找 以及一个简单的哈希表例子
第八章 查找 定义:查找就是根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)。 8.2 查找概论 查找表(Search table):是由同一类型的数据元素构成的集合。 关键字(key):是数据元素中某个数据项的值,又称为键值。 若此关键字可以唯一的标识一个记录,则称此关键字为主关键字(Primary key)。 对于那些可以识别多个数据元素的关键字,我们称为次关键字(Secondary key)。 查找表按照操作方式来分有两大种:静态查找表和动态查找表 静态查找表(Static
xcywt
2018/03/28
2.4K0
《大话数据结构》 查找 以及一个简单的哈希表例子
数据结构基础详解:哈希表【理论计算篇】开放地址法_线性探测法_拉链法详解
散列表,又称哈希表。是一种数据结构,特点是:数据元素的关键字与其存储地址直接相关。
小徐在进步
2024/10/08
6560
数据结构基础详解:哈希表【理论计算篇】开放地址法_线性探测法_拉链法详解
【408&数据结构】散列 (哈希)知识点集合复习&考点题目
散列查找是一种高效的查找方法,它通过散列函数将关键字映射到数组的一个位置,从而实现快速查找。这种方法的时间复杂度平均为(O(1)),但最坏情况下可能会退化到(O(n))。
苏泽
2024/09/09
3210
【408&数据结构】散列 (哈希)知识点集合复习&考点题目
重温数据结构:哈希 哈希函数 哈希表
该文介绍了计算机科学中的哈希表(Hash Table)及其在编程中的应用。哈希表是一种数据结构,可以高效地完成查找、插入、删除等操作。文章还介绍了哈希函数、哈希冲突、拉链法等概念。
张拭心 shixinzhang
2018/01/05
2.7K1
重温数据结构:哈希 哈希函数 哈希表
查找-散列查找
散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。查找时,根据这个确定的对应关系找到给定值key的映射f(key),若查找集合中存在这个记录,则必定在f(key)的位置上。
全栈程序员站长
2022/08/28
1.6K0
查找-散列查找
散列表(哈希表)
版权声明:本文为博主原创文章,转载请注明博客地址: https://blog.csdn.net/zy010101/article/details/83998492
zy010101
2019/05/25
7640
C语言实现散列查找的编程
Integrated Development Environment:Viusal Studio 2022
timerring
2025/05/24
1040
C语言实现散列查找的编程
哈希查找
这是一种简单/最常用的方法,嘉定哈希表表长为m,取一个不大于m但最接近或等于m的质数p,利用一下公式把关键字转化成哈希地址:
Autooooooo
2020/11/09
4670
哈希查找
数据结构:查找
衡量标准:查找过程中对关键字的平均比较次数——平均查找长度ASL。设查找到第i个元素的概率为p,比较次数为c,则查找成功的ASL_{succ}=\sum^n_{i=1}p_ic_i
ttony0
2022/12/26
1K0
数据结构:查找
【数据结构】哈希表
顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为 $O(N)$ ,平衡树中为树的高度,即 $O(logN)$ ,搜索的效率取决于搜索过程中元素的比较次数。
椰椰椰耶
2024/09/20
1220
【数据结构】哈希表
学生物的女朋友都能看懂的哈希表总结!
之前给大家介绍了链表,栈和队列今天我们来说一种新的数据结构散列(哈希)表,散列是应用非常广泛的数据结构,在我们的刷题过程中,散列表的出场率特别高。所以我们快来一起把散列表的内些事给整明白吧,文章框架如下。
公众号袁厨的算法小屋
2020/11/25
8900
学生物的女朋友都能看懂的哈希表总结!
C++ —— 哈希详解 - 开散列与闭散列
1. 从发⽣冲突的位置开始,依次线性向后探测,直到寻找到下⼀个没有存储数据的位置为⽌,如果⾛到哈希表尾,则回绕到哈希表头的位置(回绕方法就是进行取模)
迷迭所归处
2024/11/19
1380
C++ —— 哈希详解 - 开散列与闭散列
解析hash(散列)数据结构
在学习完map、set这两个由红黑树构成的容器后,我们来到了这里hash,首先我们要有一个基础的认知——哈希和map与set的仅在使用时的差别区别:前者内部的元素没有序,而后者有序,其它的都相同,这里我们可以通过STL标准库对应的unordered_map和unordered_set的两个名字就能看出,那hash存在的意义在哪里?底层的数据结构又是如何实现的呢?
比特大冒险
2023/04/16
8330
解析hash(散列)数据结构
散列表(哈希表)
序言: 如果将一系列的记录按照关键字的某种函数存储,那么在查找某个数据的时候就可以直接通过关键字计算出来了,而不在需要“比较”,这样会非常高效,这就是散列技术。 所以散列技术就是:     存储位置=f(关键字)        不管是记录的存储还是查找,都用这种方法 散列技术具有很高的效率,但是使用起来有一些限制。如1个关键字对应多个记录的情况(比如在一个学校的学生中按性别查找,则对应太多的记录),此外散列技术同样不适合于范围查找和排序等操作。 一、散列函数的构造 在设计散了函数的时候主要考虑两个原则: (
用户1215536
2018/02/05
7410
相关推荐
散列表
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验