散列表查找为何如此之快
一、散列函数
散列是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。建立了关键字与存储位置的映射关系,公式如下:
存储位置 =f(关键字)
设所有可能出现的关键字集合记为U(简称全集),实际发生(即实际存储)的关键字集合记为K(|K|比|U|小得多)。
散列方法是使用函数f将U映射到表T[0..m-1]的下标上(m=O(|U|))。这样以U中关键字为自变量,以f为函数的运算结果就是相应结点的存储地址。从而达到在O(1)时间内就可完成查找。
其中:
- f:U(可能出现的全集)→{0,1,2,…,m-1} ,通常称f为散列函数(Hash Function)。散列函数f的作用是压缩待处理的下标范围,使待处理的|U|个值减少到m个值,从而降低空间开销。
- T为散列表(Hash Table)。
- f(Ki)(Ki∈U)是关键字为Ki结点存储地址(亦称散列值或散列地址)。
- 将结点按其关键字的散列地址存储到散列表中的过程称为散列(Hashing)
二、直接寻址法
散列的概念属于查找,它不以关键字的比较为基本操作,采用直接寻址技术。
在最坏情况下,散列表的查找和删除的最坏时间代价为O(n),但是在实际中,散列表的性能是很好的,在一些合理的假设下,散列表的查找和删除操作的平均时间代价为O(1)。
在说直接寻址法之前,先举个例子:
现在有10个门,并且从1到10标好了号码,你手中有5号门的钥匙,那如何打开第五号门呢?肯定不是一个一个的去试,而是直接去开第5号门。
python的列表中也有通过key找到数据的思想,列表是可以直接访问的,通过列表的起始地址加上一个偏移量得到目标数据的存储地址。
list_a = [1,2,3,4]
list_a[2]
>>>3
这种方法是很快的,可以在O(1)时间内完成。直接寻址表就借助了列表这种可直接访问的优势。
如果我们现在要对0-100岁的人口数字统计表,那么我们对年龄这个关键字就可以直接用年龄的数字作为地址。此时f(key) = key。
地址 | 年龄 | 人数 |
00 | 0 | 500万 |
01 | 1 | 600万 |
02 | 2 | 450万 |
... | ... | ... |
20 | 20 | 1500万 |
... | ... | ... |
这个时候,我们可以得出这么个哈希函数:
f(0) = 0,f(1) = 1,……,f(20) = 20。
这个是根据我们自己设定的直接定址来的。人数我们可以不管,我们关心的是如何通过关键字找到地址。
如果我们现在要统计的是80后出生年份的人口数,那么我们对出生年份这个关键字可以用年份减去1980来作为地址。此时f (key) = key-1980。
地址 | 出生年份 | 人数 |
00 | 1980 | 500万 |
01 | 1981 | 600万 |
02 | 1982 | 450万 |
... | ... | ... |
20 | 2000 | 1500万 |
... | ... | ... |
假如今年是2000年,那么1980年出生的人就是20岁了,此时 f(2000) = 2000 - 1980,可以找得到地址20,地址20里保存了数据“人数500万”。
也就是说,我们可以取关键字的某个线性函数值为散列地址,即:
f(key) = a × key + b
这样的散列函数优点就是简单、均匀,也不会产生冲突,但问题是这需要事先知道关键字的分布情况,适合査找表较小且连续的情况。由于这样的限制,在现实应用中,直接寻址法虽然简单,但却并不常用。
三、除留余数法
除留余数法此方法为最常用的构造散列函数方法。对于散列表长为m的散列函数公式为:
f( key ) = key mod p ( p ≤ m )
mod是取模(求余数)的意思。事实上,这方法不仅可以对关键字直接取模,也可在折叠、平方取中后再取模。
一个例子
很显然,本方法的关键就在于选择合适的p, p如果选得不好,就可能会容易产生同义词。下面我们来举个例子看看:
有一个关键字,它有12个记录,现在我们要针对它设计一个散列表。如果采用除留余数法,那么可以先尝试将散列函数设计为如下方法:
f(key) = key mod 12
比如29 mod 12 = 5,所以它存储在下标为5的位置。
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
关键字 | 12 | 25 | 38 | 15 | 16 | 29 | 78 | 67 | 56 | 21 | 22 | 47 |
不过这也是存在冲突的可能的,因为12 = 2×6 = 3×4。如果关键字中有像18(3×6)、30(5×6)、42(7×6)等数字,它们的余数都为6,这就和78所对应的下标位置冲突了。
甚至极端一些,对于下图的关键字,如果我们让p为12的话,就可能出现下面的情况,所有的关键字都得到了0这个地址数,这未免也太糟糕了点。
下标 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
关键字 | 12 | 24 | 36 | 48 | 60 | 72 | 84 | 96 | 108 | 120 | 132 | 144 |
但是我们如果不选用p=12来做除留余数法,而选用p=11,则结果如下:
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 0 | 1 |
关键字 | 12 | 24 | 36 | 48 | 60 | 72 | 84 | 96 | 108 | 120 | 132 | 144 |
这个时候就只有12和144有冲突,相对来说,就要好很多了。
如何合理选取p值
使用除留余数法的一个经验是,若散列表表长为m,通常p为小于或等于表长(最好接近m)的最小质数或不包含小于20质因子的合数。
这句话怎么理解呢?再举个例子:某散列表的长度为100,散列函数H(k)=k%P,则P通常情况下最好选择哪个呢?
A、91 B、93 C、97 D、99
实践证明,当P取小于哈希表长的最大质数时,产生的哈希函数较好。所以选97,因为它是离长度值最近的最大质数。
散列表查找为何如此之快相关推荐
- 查找 之 散列表查找(哈希表)
基础概念 散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key).这里对应关系f称为散列函数,又称为哈希(Hash)函数. 采用散列技术将 ...
- 散列表查找——线性探测法
//散列表查找--线性探测法 #include<iostream> #include<stdlib.h> using namespace std; void print(int ...
- 数据结构与算法之hashmap散列表查找
哈希表:关键字.地址与查找 哈希查找是一种常用的查找方式,通常通过自定义函数F(关键字)来实现对于元素的查找,并返回关键字的存储地址(查找成功)或"查找失败"讯息. 散列表不同于线 ...
- 《数据结构与算法》(二十)- 散列表查找
目录 前言 1. 散列表查找(哈希表)概述 1.1 散列表查找定义 1.2 散列表查找步骤 2. 散列函数的构造方法 2.1 直接定址法 2.2 数字分析法 2.3 平方取中法 2.4 折叠法 2.5 ...
- 散列表查找失败平均查找长度
如果你看了很多其他博客然后都看不懂看到了这篇,你一定可以容易懂的!我佛了,这么简单的东西死板地讲题目不讲原理鬼看得懂啊,这种风气真的不行,我忍不住想骂一声垃圾,啥玩意儿,误人子弟!原理懂了啥题不会做? ...
- 计算散列表查找成功和查找不成功的平均查找长度(利用线性探测法处理冲突)
散列表 哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构.也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.映射函数叫做 ...
- 散列表查找(哈希表)
散列函数构造经常要考虑: 1.散列表的长度 2.关键字的长度 3.关键字的分布情况 4.计算散列函数所需的时间 5.记录的查找频率 一个"好"的散列函数应遵循一下两条原则 (1)函 ...
- 散列表查找的一个实例
这里解决冲突的方法是开放地址法:"开放地址指的是表中尚未被占用的地址,开放地址法就是当冲突发生时候,形成一个地址序列,沿着这个序列逐个进行探测,直到找到一个空的开放地址,将发生冲突的关键字存 ...
- 散列表查找失败平均查找长度_Python数据结构与算法56:排序与查找:冲突解决方案...
注:本文如涉及到代码,均经过Python 3.7实际运行检验,保证其严谨性. 本文阅读时间约为6分钟. 前面说过,如果两个数据项被散列映射到同一个槽,需要一个系统化的方法在散列表中保存第二个数据项,这 ...
最新文章
- memcached 常用命令及使用说明
- Android 使用Listview的优化
- 左值和左值引用、右值和右值引用
- uniapp返回上一页_一例万级写入并发,百亿级数据,毫秒级返回架构分享
- 马云:首批助力欧洲防疫的物资今天到达比利时
- Nginx+keepalived高可用配置实战(内附彩蛋)
- 手把手教你用WPE“修改”各种魔兽SF
- 统计通话次数和时间的软件_手机通话时间统计软件下载
- 清华大数据,365天我们持续在发声——数据院四周年系列报道之传播篇
- 目标追踪:FCNT、GOTURN、SiamFC、SiamRPN、SiamRPN++
- 基于PHP和YII框架技术的班级管理系统 | 饭饭博客
- 解决Maven下载速度缓慢问题
- iOS 内购最新讲解
- 老毛桃安装Linux系统ISO镜像,用U盘快速安装Ubuntu的方法
- 飞机大战实现--c++
- addEventListener( ) 方法 -- 事件监听
- ip iq 谐波检测matlab仿真,谐波检测技术在配电项目中的应用
- C语言学习笔记:C语言的指针函数与函数指针??
- python调用perl_perl和python的相互调用
- 李html画布,李雪松的绘画