哈希算法

Oudasheng 2020-04-11

哈希表的存储结构

  1. 开放寻址法
  2. 拉链法

memset是按字节来初始化的,int中有四个字节,初始化成0x3f就是将每个字节都初始化成0x3f,所以每个int就是 0x3f3f3f3f

通过哈希函数h(x)

这个函数可以映射到某个位置

  1. x mod 10^5
  2. 冲突,两个不一样的数但是映射到同一个数
  3. 处理冲突

离散化是极其特殊的哈希方式。因为他要有序

拉链法

拉链的长度都比较短。一般没有删除

只有添加和查找某个数

删除的话就在那个位置做一个标记,开一个bool变量

做哈希的时候,数组长度,一般来说是一个质数

#include <cstdio>
#include <iostream>
#include <string.h>
using namespace std;
const int maxn = 1e5 + 3;
int h[maxn], edx[maxn], ne[maxn], idx;

// h[k]表示第k位数字指向的下一位地址
// ne[idx]表示第idx位数字的地址是多少
void insert(int x)
{
    int k = (x % maxn + maxn) % maxn;
    edx[idx] = x;
    ne[idx] = h[k];
    h[k] = idx;
    idx++;
}

bool find(int x)
{
    int k = (x % maxn + maxn) % maxn;  
    for (int i = h[k]; i != -1; i = ne[i]) // 空指针用-1来表示
        if (edx[i] == x)
            return true;
    return false;
}

int main()
{
    int n;
    char op[2];
    cin >> n;
    memset(h, -1, sizeof h);  // -1表示指向的下一个节点是空
    while (n -- )
    {
        //exit(0);
        int x;
        scanf("%s%d", op, &x);
        if(!strcmp(op, "I"))  // *op == ‘I‘
        {
            insert(x);
        }
        if(!strcmp(op, "Q"))
        {
            if(find(x)) puts("Yes");
            else puts("No");
        }
    }
    return 0;
}

开放寻址法

一般要开2-3倍的位置

上厕所一个道理,找不到厕所就找下一个,下一个还找不到就再下一个。最后的还找不到就去第一个

#include <cstdio>
#include <iostream>
#include <string.h>
using namespace std;
const int maxn = 200003, null = 0x3f3f3f3f;
int h[maxn];


int find(int x)
{
    int k = (x % maxn + maxn) % maxn; 
    while (h[k] != null && h[k] != x)  // 如果相同的话就返回相同的位置
    {
        k ++ ;
        if (k == maxn) k = 0;
    }
    
    return k;
}

int main()
{
    int n;
    char op[2];
    cin >> n;
    memset(h, 0x3f, sizeof h);  // -1表示指向的下一个节点是空
    while (n -- )
    {
        //exit(0);
        int x;
        scanf("%s%d", op, &x);
        int k = find(x);
        if(!strcmp(op, "I"))  // *op == ‘I‘
        {
            h[k] = x;
        }
        if(!strcmp(op, "Q"))
        {
            // if (h[k] != null) puts("Yes");
            // else puts("No");
            // 可能是k不在范围内,只要求了mod,肯定在正常范围内
            if (h[k] == null) puts("No");
            else puts("Yes");
        }
    }
    return 0;
}

时间复杂度是\(O(1)\) 但是用到哈希表的题目都是$ O(N)$

大部分的题目都是赋值成-1,0,0x3f

字符串哈希

想做的事情就是把字符串变成数字

这样能直接通过数字来比较两个字符串是否相等

计算方法

计算某一段的哈希值的时候比如

ABDCA

我要取出CA

根据定义分别求出\(hash[i]\)

$ hash[1]=s1$

$ hash[2]=s1?p+s2$

\(hash[3]=s1?p^2+s2?p+s3\)

\(hash[4]=s1?p^3+s2?p^2+s^3?p+s4\)

$ hash[5]=s1?p4+s2?p3+s3?p^2+s4?p+s5$

现在我们想求\(s_3s_4\)\(hash\)值,不难得出为\(s_3 * p +s_4\) ,并且从上面观察,如果看\(hash[4]?hash[2]\)并将结果种带有\(s_1,s_2\),系数的项全部消掉,就是所求。但是由于\(p\)的阶数,不能直接消掉,所以问题就转化成,将\(hash[2]\)乘一个关于\(p\)的系数,在做差的时候将多余项消除,从而得到结果。

不难发现,对应项系数只差一个\(p^2\)
,而\(4 - 3 + 1 = 2\)(待求\(hash\)子串下标相减再加一),这样就不难推导出来此例题的求解式子。

\[hash[4] - hash[2] * p ^{ 4 - 3 + 1}\]

具体方法

  1. 每一个字符变成一个数字
  2. 把其想成一串K进制的数字

注意事项

  1. 字母不能映射成0 AA,A,AAA 都是0。会冲突
  2. RP足够好,不存在冲突
  3. p = 131, 13331
  4. q = 2^64 99.99% 不存在冲突
  5. 不能容忍冲突,能处理冲突

好处

快速判断两个字符串是否相等(把字符串变成数字)

用公式算出字串的哈希值

h[]表示某一前缀的哈希值

#include <cstdio>
#include <iostream>
using namespace std;
typedef unsigned long long ULL; // 用unsigned long long就不需要取模了
const int maxn = 1e5 + 10, P = 131;
ULL p[maxn], h[maxn];
char str[maxn];
int n, m;

// h[i] = h[i - 1] * p + str[i];
// h[r] - h[l - 1] * p[r - l + 1]; l - r之间有几个数字

ULL get(int l, int r)
{
    return h[r] - h[l - 1] * p[r - l + 1];
}
int main()
{
    int n, m;
    p[0] = 1;
    cin >> n >> m;
    scanf("%s", str + 1);
    for (int i = 1; i <= n; i ++ )
    {
        
        p[i] = p[i - 1] * P; // P进制
        h[i] = h[i - 1] * P + str[i];  // 移动了一位之后,ascii码不会超过P
    }
    
    while (m -- )
    {
        int l1, l2, r1, r2;
        cin >> l1 >> r1 >> l2 >> r2;
        if(get(l1, r1) == get(l2, r2)) puts("Yes");
        else puts("No");
    }
    return 0;
}

KMP可以求字符串的循环节

坐在马桶上看算法

相关推荐