あいさか たいがblogAisaka_Taiga的博客
//https://img2018.cnblogs.com/blog/1646268/201908/1646268-20190806114008215-138720377.jpg

浅谈字符串哈希

Toretto·2023-06-25 22:27·32 次阅读

浅谈字符串哈希

哈希HASH

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

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

哈希的思想#

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

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

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

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

来练练手:

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

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

code:

Copy
#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:

Copy
#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 冲突的几率变得更小。

Copy
#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 @   北烛青澜  阅读(32)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示
目录