以下笔记为jjyaoao本人制作,欢迎借鉴学习和提出相关建议,转载需要标明出处www.jjyaoao.space

词典

散列

引例

image-20220306111525046

image-20220306111633552

原理

image-20220306111849792

实例

image-20220306111920343

image-20220306112110080

冲突

image-20220306112330480

image-20220306112354193

散列函数

冲突难免

image-20220306112530933

image-20220306112746370

何谓优劣

image-20220306112905613

几种散列函数

除余法

image-20220306112954822

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "Bitmap/Bitmap.h" //引入Bitmap结构
0002
0003 /******************************************************************************************
0004 * 筛法求素数:找出小于n的所有素数
0005 * 内循环每趟迭代O(n/i)步,外循环由素数定理至多n/ln(n)趟,累计耗时不过
0006 * n/2 + n/3 + n/5 + n/7 + n/11 + ...
0007 * < n/2 + n/3 + n/4 + n/6 + n/7 + ... + n/(n/ln(n))
0008 * = O(n(ln(n/ln(n)) - 1))
0009 * = O(nln(n) - nln(ln(n)) - 1)
0010 * = O(nlog(n))
0011 * 如下实现中,内循环的起点、外循环的终点都了优化
0012 ******************************************************************************************/
0013
0014 void Eratosthenes ( int n, char* file ) {
0015 Bitmap B( n ); B.set( 0 ); B.set( 1 ); //0和1都不是素数
0016 for ( int i = 2; i*i < n; i++ ) //逐个地
0017 if ( !B.test ( i ) ) //确认下一个素数i
0018 for ( int j = i * i; j < n; j += i ) //并将一系列能被i整除的
0019 B.set ( j ); //j标记为合数(小于i*i的合数,必在此之前已被标记)
0020 B.dump ( file ); //将筛选标记统一存入指定文件,以便日后直接导入
0021 }

image-20220306113038065

平方取中

image-20220306113135073

折叠/异或

image-20220306113202092

(伪)随机数法

image-20220306113231475

image-20220306113250302

多项式法

image-20220306113314662

image-20220306113327967

排解冲突

多槽位

image-20220306113417143

类似于一山不容二虎,将每个虎用铁丝网封锁在不同的山

每个桶不同的槽位就用来存储多出来的重复的散列表

image-20220306113603101

只要槽位控制在常数,整体的速度不会太降低,不过缺陷是显而易见的。

​ 每个桶需要分多少个槽位是无法预估的,如果分的过细,浪费空间,分的太少,可能装不下,反过来,无论分的多细,仍有可能面对大规模的冲突,那么我们如何破解呢?

独立链

image-20220306113506490

image-20220306131512891

虽然这种方式良好的解决了空间上的问题,但是引入了时间上巨大迟钝

系统的缓存几乎失效

image-20220306131632740

​ 那么为了有效的激活缓存功能,我们又应该如何改进呢?

公共溢出区

​ 为了保证空间上彼此毗邻,我们可能应该放弃这种策略,并且反其道而行之,采用开放定址

image-20220306131928840

image-20220306132059604

查找链约定

线性试探

紧邻的试探,使得越后面进来的,需要的时间(冲突)越来越多

image-20220306132245775

image-20220306132503655

前后对比,可以发现,后一种插入所发生的的多次冲突(紫色圈起来的)的确是可以避免的。[如果对应桶的位置有人占了,则以此向后遍历,直到找到一个空桶]

【对7而言,对应的桶就应该是0】

懒惰删除

image-20220306132924670

实战代码

image-20220306133046310

1
2
3
4
5
6
7
8
9
/******************************************************************************************
0002 * 沿关键码k的试探链,找到与之匹配的桶;实践中有多种试探策略可选,这里仅以线性试探为例 搜索!
0003 ******************************************************************************************/
0004 template <typename K, typename V> int Hashtable<K, V>::probe4Hit ( const K& k ) {
0005 int r = hashCode( k ) % M; //按除余法确定试探链起点
0006 while ( ( ht[r] && ( k != ht[r]->key ) ) || removed->test(r) )
0007 r = ( r + 1 ) % M; //线性试探(跳过带懒惰删除标记的桶)
0008 return r; //调用者根据ht[r]是否为空及其内容,即可判断查找是否成功
0009 }
1
2
3
4
5
6
7
8
/******************************************************************************************
0002 * 沿关键码k的试探链,找到首个可用空桶;实践中有多种试探策略可选,这里仅以线性试探为例 插入!
0003 ******************************************************************************************/
0004 template <typename K, typename V> int Hashtable<K, V>::probe4Free ( const K& k ) {
0005 int r = hashCode ( k ) % M; //按除余法确定试探链起点
0006 while ( ht[r] ) r = ( r + 1 ) % M; //线性试探,直到首个空桶(无论是否带有懒惰删除标记)
0007 return r; //只要有空桶,线性试探迟早能找到
0008 }

平方试探

image-20220306133232803

一利一弊

image-20220306133323543

至多半载

我们装填的东西必须少于这个查找桶的一半

image-20220306133643886

image-20220306133823341

双向平方试探

image-20220306133950985

image-20220306134205143

4k+3

image-20220306134257652

双平方定理

image-20220306134522541

反证法

假设取表长为M, 里面a,b是发生冲突的,那么冲突是什么呢?

​ 在mod M 的情况下 ,a^2 -b^2取余后相等,我们把a方b方之和看做为n

所以n可以整除m平方(根据费马双平方定理推论,不仅能被m,还能被m方),证明a方b方大于m方,然而就a,b的取值范围而言,这一关系是断乎不可能成立的。

image-20220306135026907

桶排序/计数排序

待排序序列的长度–n 待排序序列的取值范围–M

M越小,算法优势越明显

image-20220306135730059

image-20220306135557823

而若果真如此,这个算法有望在线性时间完成排序

image-20220306135913537

实例

image-20220306135902696

image-20220306135959587

我们只需要遍历一次数据集,在此当中,每遇到一个数据,就对他的计数器做累加,形象的说,这样的作用就像把它对应的元素都扔到桶单元中,因此这一步骤也称为 分配 (distribution)

count:每个字符所出现的次数

accum:暗示着是累计值,更精确的说,红色取现任何一个点,都对应在这个点以及之前所有蓝色的点对应的积分

没错,这个反应的就是它在有序序列中的位置,也就是,出现的区间

image-20220306140454297

以这个为例,对应这比G小的总共有6个,因此作为这些字母的最大者F应该编号为5的位置,G的位置也同理得到

image-20220306140600326

具体的算法

从头到尾线性扫描一遍即可

image-20220306140717540

前一项的累计值,加上这一项的count值

image-20220306140811279

累计,更新,累计,更新……