神机百炼1.24-开放寻址法哈希

神机百炼1.24-开放寻址法哈希_第1张图片

开放寻址法哈希表

    • 食用指南:
    • 题目描述:
    • 题目分析:
    • 算法原理:
      • 1. 哈希冲突:
      • 2. 哈希表表长:
      • 3. 开放寻址法:
        • 空表/空元素的设置:
        • 插入元素时对哈希冲突的处理:
        • 查询元素x:
    • 代码实现:
      • 1. 哈希表初始化:
      • 2. 查询操作:
      • 3. 插入操作:
    • 代码误区:
      • 1. 开放寻址法的时间复杂度是多少?
      • 2. 开放寻址法的空节点值是多少?
      • 3. 开放寻址法的关键函数:
      • 4. x一定存在于Hash[k~N-1]吗?
      • 5. 何时在Hash[0~k-1]寻找x?
      • 6. 开放寻址法和拉链法哈希表空节点表示的差异?
  • 本篇感想:

食用指南:

对该算法程序编写以及踩坑点很熟悉的同学可以直接跳转到代码模板查看完整代码
只有基础算法的题目会有关于该算法的原理,实现步骤,代码注意点,代码模板,代码误区的讲解
非基础算法的题目侧重题目分析,代码实现,以及必要的代码理解误区

题目描述:

  • 维护一个集合,支持如下几种操作:

    I x,插入一个数 x;
    Q x,询问数 x 是否在集合中出现过;
    现在要进行 N 次操作,对于每个询问操作输出对应的结果。

    输入格式
    第一行包含整数 N,表示操作数量。

    接下来 N 行,每行包含一个操作指令,操作指令为 I x,Q x 中的一种。

    输出格式
    对于每个询问指令 Q x,输出一个询问结果,如果 x 在集合中出现过,则输出 Yes,否则输出 No。

    每个结果占一行。

    数据范围
    1≤N≤105
    −109≤x≤109
    输入样例:
    5
    I 1
    I 2
    I 3
    Q 2
    Q 5
    输出样例:
    Yes
    No

  • 题目来源:https://www.acwing.com/problem/content/842/

题目分析:

  • 若为统计26个字母是否出现,则开一个数组arr[26];即可
  • 本题数字最大到109,不可通过开数组的方式进行模拟
  • 大数映射到小数范围 - 典型哈希
    本篇博客将介绍哈希表的第二种实现方式-开放寻址法
    想要查看拉链法实现哈希表的同学看这里:传送门

算法原理:

1. 哈希冲突:

  • 哈希映射函数:数x%N之后落到(-N, N)范围内,+N后落到(0, 2N)内,再%N落到(0, N)内
  • 含义:多个不同的x映射到的值相同,如1 & 105+1都映射到了1
  • 根据解决哈希冲突的方式不同,将哈希表分为:
    1. 开放寻址法哈希表
    2. 拉链法哈希表

2. 哈希表表长:

  • 由于拉链法借助静态单链表存储元素,故其表长固定为大于数据总个数的一个质数即可
  • 开放寻址法将所有元素都存储在哈希表数组Hash[N];中,其表长为数据总个数的2~3倍
  • 表长的选取也就一个目的:减少哈希冲突

3. 开放寻址法:

空表/空元素的设置:
  • 由于-1 / 0都可能是被查询或插入的数,所以不能用他们作为空的标志
  • 由于最大的数也就一个亿,而Int表示可以十个亿
  • 所以将大于一亿,小于十亿的数作为空标志即可
插入元素时对哈希冲突的处理:
  • 若数x与数y产生哈希冲突,共同映射到小数k

    则将一者存放在Hash[k]中,另一者顺延到暂无存储元素的哈希表Hash[i]中
    神机百炼1.24-开放寻址法哈希_第2张图片

  • 顺延操作: 对于Hash[i]来说,当i++到达N-1时
    并不意味着整个Hash[]存储满了
    而是Hash[k]之后存储满了,k之前可能还有存储空间
    所以每次i++到达N时,i=0,再重头找

  • 顺延次数:
    由于Hash[]本身数组长度是所有个数的2~3倍
    所以%N后一般只需要顺延2~5个哈希表格子
    耗时为O(1)

查询元素x:
  • 先到达Hash[(x%N+N)%N]

    若Hash[(x%N+N)%N] == x,则查询到x确实存在
    若Hash[(x%N+N)%N] != x,则向后顺延查表,i++, i%N;

    若在查询到x之前,先遇到了Hash[i] == 0;则说明x不存在于哈希表中

代码实现:

1. 哈希表初始化:

//先找一个2~3倍的质数作为数组长度
int N = 0;
for(int i=200000; i++){
	int flag = 1;
    for(int j=2; j*j<i; j++){
		if(i%j == 0){
			flag = 0;
            break;
        }
    }
    if(flag){
		N = i;
    }
}
int Hash[N];
//空表置为无穷:
memset(Hash,0x3f,sizeof(h));
//无穷表示空点:
int ept = 0x3f3f3f;

2. 查询操作:

  • 若x存在则返回x的Hash[]索引,反之返回遇到的第一个空哈希索引
int find(int x){
	int k = (x%N + N)%N;
    while(h[k] != ept && h[k] != x){
        k++;
        //重头查找,肯定会停止,因为n个数插入2*n个格子,必然有空,或者找到自己
        if(k == N) k = 0;
    }
    //返回的可能是空格子,也可能是自己在格子中
    return k;
}
if (Hash[find(x)] == ept) 
	cout<<x<<"不存在"<<endl;
else 
	cout<<x<<"存在"<<endl;

3. 插入操作:

  • 不可重复插入同一个数的插入
void insert(int x){
	//k可能是第一个空,也可能是第一个x
	int k = find(x);
	if (Hash[k] == ept) Hash[k] = x;
}

代码误区:

1. 开放寻址法的时间复杂度是多少?

  • O(1),映射函数只需一步(x%N+N)%N,顺延查询最多顺延5个点

2. 开放寻址法的空节点值是多少?

  • 一个大于题目最大值,小于int 或 long long 最大值的数
  • 一般用变量ept表示,设置为0x3f3f3f3f3f

3. 开放寻址法的关键函数:

  • 查找函数:find(),插入操作也依赖于find()
  • find()的遍历关键:指针i到达N之后,可能0-k存在空节点/存在前面的值
    所以此时i = 0;

4. x一定存在于Hash[k~N-1]吗?

  • 不一定,当k ~ N存满时,i=0,在0~k-1进行存储
  • 所以查找的时候也是先从 k~N,再从 0~k-1进行查询
  • 由于Hash[]长度是数据个数2~3倍,所以要么遇到空,要么遇到x
    空节点个数管够
    神机百炼1.24-开放寻址法哈希_第3张图片

5. 何时在Hash[0~k-1]寻找x?

  • 由于Hash[]表长是输入量的两三倍,所以一般情况就在k ~ N-1即可查询得到x/空
  • 一般也不会返回去在0 ~k-1存储,但是思维完整性上还是%N吧
  • 那其实表中有无x的判断条件就是x和空哪个先出现在Hash[find(x)]后面

6. 开放寻址法和拉链法哈希表空节点表示的差异?

  • 拉链法中Hash[i]存储的是链表头节点的索引idx,当链表为空时idx=-1
  • 开放寻址法中Hash[i]存储的就是数据本身,当链表为空时,以无穷表示
  • 故初始化Hash[]有所不同

本篇感想:

  • 掌握开放寻址法哈希表,主要掌握应对哈希冲突的find()函数即可

  • 为什么星期6博客只更新了一篇?
    昨天了5个小时,感觉台球水平也到达了练气境中期,hh,有了球感之后不怎么需要瞄准了,希望下周保持

  • 看完本篇博客,恭喜已登 《练气境-中期
    练气期初阶
    距离登仙境不远了,加油

你可能感兴趣的:(acwing,哈希算法,算法,散列表,c++,链表)