//https://img2018.cnblogs.com/blog/1646268/201908/1646268-20190806114008215-138720377.jpg

浅谈字符串哈希

哈希HASH

哈希是对于字符串的一种操作。

在日常的百度搜索什么的都是根据关键字来查找,我们可以利用 hash 来加速这个过程。

哈希的思想

哈希其实是所有字符串操作中,最简单的操作了。哈希的过程,其实可以看作对一个串的单向加密过程,并且需要保证所加的密不能高概率重复,通过这种方式来替代一些很费时间的操作。

比如,最常见的,当然就是通过哈希数组来判断几个串是否相同(洛谷P3370)。此处的操作呢,很简单,就是对于每个串,我们通过一个固定的转换方式,将相同的串使其的“加密后的值”一定相同,不同的串尽量不同。

此处有人指出:那难道不能先比对字符串长度,然后比对 ASCLL 码之和吗?事实上显然是不行的(比如 ab 和 ba ,并不是同一个串,但是如是做却会让其认为是)。这种情况就叫做hash冲突,并且在如此的单向加密哈希中,hash 冲突的情况在所难免。

而我们此处介绍的,即是最常见的一种哈希:进制哈希。进制哈希的核心便是:给出一个固定进制 base,将一个串的每一个元素看做一个进制位上的数字,所以这个串就可以看做一个 base 进制的数,那么这个数就是这个串的哈希值;则我们通过比对每个串的的哈希值,即可判断两个串是否相同。

来练练手:

【模板】字符串哈希 - 洛谷

直接用 unsigned long long 自然溢出取模。

code:

#include <bits/stdc++.h>

#define int unsigned long long
#define N 10010

using namespace std;

const int base = 131;
int a[N], n, ans = 1;
char s[N];

inline int Hash(char s[])
{
    int len = strlen(s), res = 0;
    for(int i = 0; i < len; i ++)
        res = (res * base + (int)s[i]);
    return res;
}

signed main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        scanf("%s", s);
        a[i] = Hash(s);
    }
    sort(a + 1, a + n + 1);
    for(int i = 1; i < n; i ++)
        if(a[i] != a[i + 1]) ans ++;
    cout << ans << endl;
    return 0;
}

取模哈希

其实和上面的区别就是加了取模,当然也是对质数取模。

上面是用了 ull 自然溢出,但是自己取模更放心!

code:

#include <bits/stdc++.h>

#define int long long
#define P 998244353
#define N 10010

using namespace std;

const int base = 131;
int a[N], n, ans = 1;
char s[N];

inline int Hash(char s[])
{
    int len = strlen(s), res = 0;
    for(int i = 0; i < len; i ++)
        res = (res * base + (int)s[i]) % P;
    return res;
}

signed main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        scanf("%s", s);
        a[i] = Hash(s);
    }
    sort(a + 1, a + n + 1);
    for(int i = 1; i < n; i ++)
        if(a[i] != a[i + 1]) ans ++;
    cout << ans << endl;
    return 0;
}

双哈希

和上面的单哈希的区别就是多一个用不同质数取模的哈希值,更加安全,hash 冲突的几率变得更小。

#include <bits/stdc++.h>

#define int long long
#define P1 192608173
#define P2 998244353
#define N 100010

using namespace std;

const int base = 31;
int n, ans = 1;
char s[N];
struct sb{int h1, h2;}e[N];

inline int cmp(sb a, sb b)
{
    if(a.h1 == b.h1) return a.h2 < b.h2;
    else return a.h1 < b.h1;
}

inline int Hash1(char s[])
{
    int len = strlen(s), res = 0;
    for(int i = 0; i < len; i ++)
        res = (res * base + (int)s[i]) % P1;
    return res;
}

inline int Hash2(char s[])
{
    int len = strlen(s), res = 0;
    for(int i = 0; i < len; i ++)
        res = (res * base + (int)s[i]) % P2;
    return res;
}

signed main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++)
    {
        scanf("%s", s);
        e[i].h1 = Hash1(s);
        e[i].h2 = Hash2(s);
    }
    sort(e + 1, e + n + 1, cmp);
    for(int i = 1; i < n; i ++)
        if(e[i].h1 != e[i + 1].h1 && e[i].h2 != e[i + 1].h2) ans ++;
    cout << ans << endl;
    return 0;
}

关于效率:

自然溢出哈希快于取模单哈希快于双哈希。

主要还是在取模上面慢了。

但是双哈希的安全性更高,平常更推荐使用双哈希。

posted @ 2023-06-25 22:27  北烛青澜  阅读(24)  评论(1编辑  收藏  举报