哈希表

将一个大范围空间压缩到一个较小的空间

前面用的方式是离散化(其实是一种特别的哈希 要求保序)

这里介绍一般的哈希

思路就是 将一个大区间内所有的数

image-f27422c2

通过一个哈希函数映射到一个小区间中

image-25594a7c

那就势必要考虑两个问题

哈希函数怎么设

既然是从大区间映射到小区间 就一定会出现冲突问题 怎么解决冲突

对于第一个问题:

最简单的 要把一堆数映射到某特定范围 的方法

就是取模

n%7 的取值为[0,6]

那么很简单的 要把原区间的数都映射到一个新区间

就把原区间的所有数 都与新区间的大小相近的一个数去取模

这样就能保证每个数都能在新区域有个投射

引入两个子问题:

这个数到底取多少

将取模结果怎么映射成下标

这个数取多少

模的这个数最好要是质数 且要离2的整数次幂尽可能远 (这样冲突概率最小)

那这里就直接选 大于新数组大小的第一个质数

可以使用

找到大于区间的第一个质数

for(int i=100000;;i++)
{
    bool flag=true;
    for(int j=2;j*j<=i;j++)
    {
        if(i%j==0)
        {
            flag=false;
            break;
        }
    }
    if(flag)
    {
        cout<<i<<endl;
        break;
    }
}

找到大于区间的第一个质数(10000->100003)

将取模结果怎么映射成下标

若将-10^9~10^9的数映射到0~10^5

就要考虑一个问题

因为c++中 负数取模的结果为负数

而显然负数是不能作为下标的 所以得想办法把它变成正数

可以采用 先取模 再加上N 再取模N的方法

image-4a35c7f9

\(k=(x mod N+N)%N\)

那么得到的要插入的地方就是h[k]

对于第二个问题:

秉承一个萝卜一个坑的原则 如果两个数经过处理后 要在一个坑 这就冲突了

该怎么解决? 要么换一个坑(开放寻址法) 要么就排队(拉链法) 比较推荐拉链法 可以加深对数组模拟链表的理解 以及后面的图中邻接表也是类似存储 y总推荐开放寻址 看自己吧

开放寻址法

只用一个数组去存储映射后的关系

如果冲突了 就继续往后找 直到找到一个坑没被占

那这样的话 数组长度就应该比理想情况要大了 大多少 最好开两到三倍

对于这么一个结构来说 核心就是查找了

如果得到一个k 从h[k]开始去找

如果当前位置有数 并且就为x 那就找到了x

如果当前位置有数 但不是x 那就往后找

如果当前位置没数 那就x不存在

(执行删除还是返回没找到看操作要求 核心就是一个find)

怎么表示该某位置没元素? 全初始化成无穷大

→ 两个相关小专题见 04-刷题小贴士:无穷大(为什么取 0x3f3f3f3f)、memset(按字节赋值的原理)

拉链法

就是把一个数处理后 得到一个下标位置

但如果这个下标位置已经有元素了

那就把它链在后面

image-ce8010f0

但是这里我们还是用数组模拟的链表

嗯 一个e放数据本身 一个ne放下一个位置的下标

那就是这样咯?

image-cbe7f3ae

在一维的每一个结点的后面都接两个数组

其实没必要

因为一维中存的不过是一个下标 作用就是单链表中的head

各个域中的head并不冲突 它只需要根据下标找到e数组中的第一个数

再链着ne找下一个数 直到所有

所以 完全可以把所有的数都存放在一个e、ne中

不同域去就相当于不同的head 去进行一个索引

嗯 把一个数组拆成多个链

image-1d6127e2

把一维的每一个位置都当做head即可 不过是对一个数组有多个head访问罢了

那么既然是当head用

所以初始化就是要把所有的值都赋为-1表示空

memset(h,-1,sizeof h);

插入一个点 就是头插

e[idx]=x;

ne[idx]=h[k];

h[k]=idx++;

遍历也很简单 从头开始 i=h[k]

直到结束i!=-1 每次往后寻 i=ne[i]

for(int i=h[k];i!=-1;i=ne[i])

模拟散列表

字符串哈希


⬅️ 堆排序 🏠 00-听课板子 ➡️ 模拟散列表