哈希

定義

\(Hash\) ,通常翻譯作散列、雜湊,或音譯爲哈希,是把任意長度的輸入(又叫作預映射 \(pre-image\) )經過散列算法變換成固定長度的輸出,該輸出就是散列值。這種轉換是一種壓縮映射,也就是,散列值的空間一般遠小於輸入的空間,不一樣的輸入可能會散列成相同的輸出,因此不可能從散列值來肯定惟一的輸入值。簡單的說就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數。(百度百科)html

\(Hash\) 也稱散列、哈希,對應的英文都是 \(Hash\) 。基本原理就是把任意長度的輸入,經過 \(Hash\) 算法變成固定長度的輸出。這個映射的規則就是對應的 \(Hash\) 算法,而原始數據映射後的二進制串就是哈希值。活動開發中常用的 \(MD5\) (密碼學有木有?!)\(SHA\) 都是歷史悠久的 \(Hash\) 算法。(某神仙逼乎)ios

說人話哈希的過程,其實能夠看做對一個串的單向加密過程,而且須要保證所加的密不能高几率重複,經過這種方式來替代一些很費時間的操做。算法

特色及做用

哈希能夠應用於字符串單向加密,加速查詢(例如百度搜索關鍵字)等等。數組

對於字符哈希的實現,能夠分爲無錯哈希多重哈希進制哈希等。函數

下面對於這三類最經常使用哈希展開論述。加密

進制哈希

進制哈希的核心即是給出一個固定進制(一般選用 \(131\) 進制),將一個串的每個元素看作一個進制位上的數字,因此這個串就能夠看作一個該進制的數,那麼這個數就是這個串的哈希值;則咱們經過比對每一個串的的哈希值,便可判斷兩個串是否相同。spa

也就是把字符賦予進制和模數,將每個字符串映射爲一個小於模數數字,而後判斷是否相同。翻譯

具體操做:code

設置進製爲 \(131\) ,模數爲 \(998244353\) ,如今對一個字符串 \(s\) 進行哈希.htm

這樣 hash[len] 裏面就是字符串s的哈希值了。

char s[10];
  cin >> (s + 1);
  int len = strlen(s + 1);
  int base = 131, mod = 998244353;
  for (int i = 1; i <= len; ++i)	  
{
        hash[i] = ((hash[i - 1] * base) + s[i]) % mod;
      
}

\(hash\) 還有一個方便的操做就是取子串的 \(hash\) 值。

hash[l, r] = (hash[r] - hash[l - 1] * pw[r - l + 1]) % mod
//僞代碼 pw[r-l+1]爲進制數的(r-l+1)次方

無錯哈希

記錄每個已經誕生的哈希值,而後對於每個新的哈希值,咱們均可以來判斷是否和已有的哈希值衝突,若是衝突,那麼能夠將這個新的哈希值不斷加上一個大質數,直到再也不衝突 (簡單粗暴)

代碼:

for (int i = 1; i <= m; i++) //m個串
{
    cin >> str; //下一行的check爲bool型
    while (check[hash(str)])
        hash[i] += 19260817;
    hash[i] += hash(str);
}

此種方式相似有桶查找,故存在弊端:

  • 數據過大時,\(check\) 數組就顯得比較乏力。
  • 數據具備跳躍性時,會大幅浪費統計次數。

多重哈希

用不一樣的兩種或多種方式對數據進行哈希,而後分別比對每一種哈希值是否相同。

這顯然是增長了空間和時間,但也確實增長告終果的正確性。

代碼:

//多重哈希的判斷操做
//check 表示當前hash的判斷結果,ans表示目前相同hash操做相同的次數

for(僞代碼排序,用來使哈希值單調(更好判斷相 / 不一樣的數量))
for (int i = 1; i <= m; i++)
{
    check = true;
    for (int j = 1; j <= qwq; j++)
    if (hash[j][i] == hash[j][i + 1])
    {
        check = false;
        break;
    }
    if (check)
        ans++; //此爲判斷相同個數
}

例題

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

題目描述

給定 \(N\) 個字符串(第 \(i\) 個字符串長度爲 \(M_i\) ,字符串內包含數字、大小寫字母,大小寫敏感),請求出 \(N\) 個字符串中共有多少個不一樣的字符串。

代碼(單哈希):

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
using namespace std;
typedef unsigned long long ull;
ull base = 131;
ull a[10010];
char s[10010];
int n, ans = 1;
int prime = 233317;
ull mod = 212370440130137957ll;
ull hashe(char s[])
{
    int len = strlen(s);
    ull ans = 0;
    for (int i = 0; i < len; i++)
        ans = (ans * base + (ull)s[i]) % mod + prime;
    return ans;
}
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        scanf("%s", s);
        a[i] = hashe(s);
    }
    sort(a + 1, a + n + 1);
    for (int i = 1; i < n; i++)
    {
        if (a[i] != a[i + 1])
            ans++;
    }
    printf("%d", ans);
}

注意:

\(hash\) 操做會致使哈希衝突,即兩個不一樣的字符處理後的哈希值相同,這樣會形成結果出錯。

解決方案:

  • 模數取大質數

    適度增長剩餘系,減小哈希衝突概率(可是模數過大會致使爆負數)

  • 雙模數哈希

    相似於多重哈希。設置兩個不一樣的哈希模數,當且僅當兩次哈希結果都想同時才斷定相同,出錯概率微乎其微(只要機率足夠小,咱們就能夠把它看做不可能事件)。

    神仙題目見 BZOJ3098(hzwer版)(含題解)。

LOJ 103. 子串查找

題目描述

給定一個字符串 \(A\) 和一個字符串 \(B\) ,求 \(B\)\(A\) 中的出現次數。\(A\)\(B\) 中的字符均爲英語大寫字母或小寫字母。

\(A\) 中不一樣位置出現的 \(B\) 可重疊。

思路

一道經典的模板題

代碼

/*
By Frather_

*/
#include <iostream>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
#include <vector>
#include <set>
#include <map>
#include <stack>
#define ll long long
#define InF 0x7fffffff
#define kMax 10e5
#define kMin -10e5
#define kMOD 998244353
#define P 133
using namespace std;
/*=========================================快讀*/
int read()
{
    int x = 0, f = 1;
    char c = getchar();
    while (c < '0' || c > '9')
    {
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
    {
        x = (x << 3) + (x << 1) + (c ^ 48);
        c = getchar();
    }
    return x * f;
}
/*=====================================定義變量*/
char a[1000010], b[1000010];
int t[1000010];
int sum[1000010];
int s;
int ans;
/*===================================自定義函數*/

/*=======================================主函數*/
int main()
{
    cin >> a + 1 >> b + 1;
    int la = strlen(a + 1);
    int lb = strlen(b + 1);
    t[0] = 1;
    for (int i = 1; i <= 1000010; i++)
        t[i] = t[i - 1] * P;
    for (int i = 1; i <= la; i++)
        sum[i] = (sum[i - 1] * P + a[i] - 'A' + 1);
    for (int i = 1; i <= lb; i++)
        s = (s * P + b[i] - 'A' + 1);
    for (int i = 0; i <= la - lb; i++)
        if (s == sum[i + lb] - sum[i] * t[lb])
            ans++;
    printf("%d\n", ans);
    return 0;
}

一些奇技淫巧

  • 使用 unsigned long long

    在數據足夠大時,能夠觸發該數據類型的天然溢出,省去了哈希操做中的取模。

最後

鳴謝 笨蛋花的小窩qwqKnightL

鳴謝《信息學奧賽一本通提升篇》,《算法競賽進階指南》。

持續更新。

相關文章
相關標籤/搜索