快接近蓝桥杯省赛了,现在是冲刺阶段,更多的是复习而不再是刷题了,刷过的题多看,多理解考点是什么,记住考过的内容,其实大部分内容都是同一个考点考来考去,只是变换了一个说法而已,所以冲刺阶段,和车神哥一起复习复习基础知识吧!!!加油~
冲刺省赛
比赛提要
数据结构基础——散列表(Hash)
主要目标是学会散列表(hash 算法)的原理与实现,学会灵活的运用,能够不依赖于模板根据题目独立写出各类散列表。数据结构 Hash 属于查找算法中的一部分,在比赛中通常也会占据一定的比例,相对较难也比较重要.
散列表(Hash)知识点:
- Hash 的概念
- 构造方法
- 冲突处理
哈希表的定义
上面所提到的查找算法,简单来说,就是判断现有数据集合中是否有这个元素,或者是否有满足条件的元素。
其中的 Hash 算法则可以帮助我们判断是否有这个元素,虽然功能简单,但是其 O(1) 时间复杂度是具有高性能的。通过在记录的存储地址和它的关键码之间建立一个确定的对应关系。这样,不经过比较,一次读取就能得到所查元素的查找方法。相比普通的查找算法来说,仅仅在比较的环节,就会大大减少查找或映射所需要的时间。
我们采用散列技术将记录存储在一块连续的存储空间中,这块连续的存储空间即称为散列表。下面用一张图给大家展示一下散列表的实现过程:
如果还是不太明白的话,我们可以理解为数学函数,Y=F(X),X 为自变量也就是这里的 Key, F( ) 对应图中的 H( ),也就是一个映射关系,Y 因变量也就是对应的值的存放位置。
散列表实战
下面我们就来学习一下关于散列表的使用方式,下面我们用一个题目来引入。
弗里的语言
输入、输出如下面示例所示:
- 输入
第 1 行,输入 N,代表共计创造了多少个单词
第 2 行至第 N+1 行,输入 N 个单词
格式如下:
fjsdfgdfsg
fdfsgsdfg
bcvxbxfyres
- 输出
例1:
输入:
6
1fagas
dsafa32j
lkiuopybncv
hfgdjytr
cncxfg
sdhrest
输出:
NO
例2:
输入:
5
sdfggfds
fgsdhsdf
dsfhsdhr
sdfhdfh
sdfggfds
输出:
sdfggfds
解题思路
第一步
- 需要创建一个散列表和一个公共溢出区
散列表
公共溢出区
第二步
- 需要定义插入散列表函数
- 按照散列表的映射方式设计即可
- 需要传入一个参数来表示放什么数据
in(Name)
{
1. 无冲突
2. 冲突处理
}
第三步
- 定义查询函数
isAt()
{
1. 如果散列表查询成功返回 True
2. 不为成功返回 False
}
第四步
- 定义散列表映射函数,此处我们采用除留余数法即可
int out(string s)
{
处理字符串 s 生成对应的 Key 值
}
第五步
- 编写主函数代码
输入 N
循环 N 次://
输入 word;
先查询,有相同的单词有就设置 flag 为 1,ans = word
没有的话,就执行插入操作
根据 flag 决定输出什么。
完整Python代码(必备模板)
总结给大家一个小窍门,在解题过程中可以使用:
- Python 中是有 Hash 函数的,在这里我们直接使用它进行解题。
h = 999983
Value = ['']*h
UpValue = ['']*h
UpValueCount = 0
def isAt (s):
n = int (hash(s)+h)%h
# print(n)
if Value[n] == '':
return False
elif Value[n]==s:
return True
else:
for i in range(0,UpValueCount):
if UpValue[i] == s:
return True
return False
def ins (s):
global UpValueCount
n =int (hash(s)+h)%h
if Value[n] == '':
Value[n]=s
return True
elif Value[n]==s:
return False
else:
for i in range(0,UpValueCount):
if UpValue[i] == s:
return False
UpValue[UpValueCount] = s
UpValueCount=UpValueCount+1
return True
if __name__=='__main__':
N=int (input())
ans = 'NO'
flag = False
while N>0:
N-=1
word=input()
# print(word)
if(not(flag)) :
if(isAt(word)):
flag=True
ans=word
else:
ins(word)
print(ans)
散列表的缺陷
散列表并不是适用于所有的需求场景,那么哪些情况下不适合使用呢?
-
散列技术一般不适合在允许多个记录有同样关键码的情况下使用。
因为这种情况下,通常会有冲突存在,将会降低查找效率,体现不出散列表查找效率高的优点。
并且如果一定要在这个情况下使用的话,还需要想办法消除冲突,这将花费大量时间,那么就失去了 O(1) 时间复杂度的优势,所以在存在大量的冲突情况下,我们就要弃用散列表。
-
散列方法也不适用于范围查找,比如以下两个情况。
-
查找最大值或者最小值
因为散列表的值是类似函数的,映射函数一个变量只能对应一个值,不知道其他值,也不能查找最大值、最小值,RMQ(区间最值问题)可以采用 ST 算法、树状数组和线段树解决。
-
也不可能找到在某一范围内的记录
比如查找小于 N 的数有多少个,是不能实现的,原因也是映射函数一个变量只能对应一个值,不知道其他值。
散列技术的关键问题
在使用散列表的时候,我们有两个关键的技术问题需要解决:
- 散列函数的设计,如何设计一个简单、均匀、存储利用率高的散列函数?
- 冲突的处理,如何采取合适的处理冲突方法来解决冲突。
如何设计实现散列函数
- 在构建散列函数时,我们需要秉持两个原则:
-
简单
- 散列函数不应该有很大的计算量,否则会降低查找效率。
-
均匀:
- 函数值要尽量均匀散布在地址空间,这样才能保证存储空间的有效利用并减少冲突。
散列函数实现三种方法
1. 直接定址法
散列函数是关键码(Key)的映射的线性函数,形如:
H
(
k
e
y
)
=
a
∗
k
e
y
+
b
H(key)=a∗key+b
H(key)=a∗key+b
来看一个小案例:
H
(
k
e
y
)
=
1
11
∗
k
e
y
+
0
H
(
k
e
y
)
=
111
∗
k
e
y
+
0
H(key) = \frac{1}{11} * key + 0H(key)= 11 1 ∗key+0
H(key)=111∗key+0H(key)=111∗key+0
如图:
缺点:
- 我们是看到了这个集合,然后想到他们都是 11 的倍数才想到这 Hash 函数。我们在平常的使用中一般不会提前知道 Key 值集合,所以使用较少。
适用范围:
- 事先知道关键码,关键码集合不大且较为连续而不离散。
2. 除留余数法
H
(
k
e
y
)
=
k
e
y
m
o
d
p
H(key)=key mod p
H(key)=keymodp
来个小例子:
这种方法是最常用的方法,这个方法的关键在于如何选取 P,使得利用率较高并且冲突率较低,一般情况下,我们会选取最接近表长且小于等于表长的最大素数。
缺点:
- P 选取不当,会导致冲突率上升。
适用范围:
- 除留余数法是一种最简单、也是最常用的构造散列函数的方法,并且不要求事先知道关键码的分布。
3. 数字分析法
比如我将我的集合全部转化为 16 进制数,根据关键码在各个位上的分布情况,选取分布比较均匀的若干位组成散列地址。或者将 N 位 10 进制数,观察各各位的数字分布,选取分布均匀的散列地址。
举例:
首先我们考虑一位作为散列函数,发现都是很多冲突,选取两位时,百位和十位组合最适宜,分布均匀且没有冲突。
当然,我们说的是这一方法的一个具体实列,既然叫做数字分析法,那么只有对于不同数据的不同分析,才能写出更是适配的 H(x)。
另外还有两种平时使用极少的方法,分别是平方取中法和折叠法,就不再做过多的讲解。
写在最后
车神哥也是第一次参赛——研究生组。不知道难度如何,所以尽力准备就好啦!重要的不是结果,而是那段你孤勇奋战的日子,这样的日子,我相信每个人在人生中都很珍贵。或许是高考,或许是考研,或许是为了仅有的一次升职加薪机会等等。
最近疫情也严重起来了,希望大家保护好自己,安全第一,比赛第二。
有梦想,每个人都很了不起!
往期回顾
- 十天冲刺省赛,保送国赛基础知识常考点复习+必背Python代码模板 | Day01 | 数据结构基础之链表
- 十天冲刺省赛,保送国赛基础知识常考点复习+必背Python代码模板 | Day02 | 数据结构基础之队列
- 十天冲刺省赛,保送国赛基础知识常考点复习+必背Python代码模板 | Day03 | 数据结构基础之栈
官方刷题练习系统:http://lx.lanqiao.cn/
ღ( ´・ᴗ・` )
❤