字串Hash入門 例題詳解

2021-10-08 05:32:04 字數 4297 閱讀 3510

最近打暑假多校,發現有許多字串演算法自己有所遺忘,今天就藉著補題在這開乙個坑,把那幾個基礎的字串演算法總結複習一下,順便寫幾個模板,供今後使用。這篇部落格主要就是總結一下字串hash,並提一下例題。

我們學習乙個演算法,肯定要先知道它要解決的是什麼問題。字串hash,就是解決字串匹配問題的良藥,即尋找長度為 n 的主串 s 中的匹配串 t(長度為 m)出現的位置或次數問題。

對於上述問題,樸素的想法是列舉 s 所有起始位置,再直接檢查是否匹配,我們可以不使用 o(m) 的直接比較字串的方法,而是比較長度為 m 的主串 s 的子串的雜湊值與 t 的雜湊值是否相等,這就是雜湊演算法解決這類問題的原理,這個原理稱為字串 hash。

大多數字串 hash 問題可以用 kmp 求解,但如果是從主串中每次選出兩個子串判斷是否匹配的問題,還是要用字串 hash 求解(不必糾結,遇到自然懂)。

如果我們用 o(m) 的時間計算長度為 m 的字串的雜湊值,則總的時間複雜度並沒有改觀,這裡就需要乙個叫做滾動雜湊的優化技巧。

我們選取兩個合適的互質常數 b 和 h(b1c

2…cm ,那麼我們定義雜湊函式:h(c)=(c1bm-1 + c2bm-2 + … + cmb0) mod h。

正常的數字是十進位制的,這裡b 是基數,相當於把字串看作是 b 進製數。

這一過程是遞推計算的,設 h(c,k) 為前 k 個字元構成的字串的雜湊值,則:(以下均不考慮取模的情況)。

h(c,k+1) = h(c,k)*b + ck+1

如字串 c=「acda」(為方便處理,我們令"a"表示 1,"b"表示 2,以此類推),則:

h(c,1) = 1

h(c,2) = 1*b + 3

h(c,3) = 1*b

2 + 3*b + 4

h(c,4) = 1*b

3 + 3*b

2 + 4*b + 1

**實現如下

h[0]

=0;for

(int i=

1;i<=

4;i++

) h[i]

=h[i-1]

*b+(c[i]

-'a'+1

);

通常,題目要求的是判斷主串的一段字元與另乙個匹配串是否匹配,即判斷字串 c=c1c2…cm 從位置 k+1 開始的長度為 n 的子串 c』=ck+1ck+2…ck+n 的雜湊值與另一匹配串 s=s1s2…sn 的雜湊值是否相等,則:

h(c』) = h(c,k+n) - h(c,k)*bn

於是我們只要預處理求得 b^n ,就能在 o(1)時間內得到任意字串的子串雜湊值,從而完成字串匹配,那麼上述字串匹配問題的演算法時間複雜度就為 o(n+m)。

**實現如下

for

(int i=

0;i<=m-n;

++i)

如字串 c=「acda」,s=「cd」,當 k=1, n=2 時:

h(c』) = h(c,1+2) - h(c,1)*b2

= (1*b2+3*b+4) - (1*b2)

= 3*b + 4

h(s) = 3*b + 4

因此子串 c』 與匹配串 s 匹配。

在實現演算法時,可以利用 32 位或 64 位無符號整數計算雜湊值,此時 h=232 或 h=264,通過自然溢位省去求模運算。(因為無符號整數,大於最大值後會以最大值+1為模數取模)

注1:眾所周知,hash演算法有時會產生衝突,但是在一般比賽中用字串hash產生衝突的概率是很小的,如果發現錯了,可以換個基數或模數,或者採用「雙雜湊」來避免衝突。

注2:我們在預處理 b^n 時,要根據題目來選擇預處理方式(雖然一般不會卡這個),如果只有一組輸入,匹配串長度固定,那麼利用快速冪求即可。如果有多組輸入,每組匹配串長度皆不同,遞推更好一些。

遞推**

pow[0]

=1;for

(int i=

1;i<=

10002

;i++

)//預處理base^n

pow[i]

=pow[i-1]

*base;

oulipo(poj3461)

題目大意

給出兩個串 s

1,s2(只有大寫字母),求 s

1 在 s

2 **現了多少次。例如 s

1=「aba」,s

2=「ababa」,答案為 2。輸入 t 組資料,對每組資料輸出結果。每組資料保證 strlen(s

1) <= 10

4,strlen(s

2) <= 10

6。 解題思路

將匹配串 s

1 的雜湊值求出來,再將母串 s

2 的雜湊值求出來,根據 h(c』) = h(c,k+n) - h(c,k)*b

n求出與匹配串長度相等的母串子串的雜湊值,與匹配串 s

1 的雜湊值比較,如果相等,答案+1。

**實現

/**

快速冪預處理,取模

跑了 813ms

*/#include

#include

#include

#include

#include

using

namespace std;

const

int maxn=

1e6+5;

const

int maxm=

1e4+5;

const

int base=29;

const

long

long mod=

1e7+5;

int n;

char w[maxm]

,t[maxn]

;long

long

quickpow

(long

long a,

int b)

//快速冪

return sum;

}int

main()

if(hashw==hasht)

ans++

;long

long cnt=

quickpow((

long

long

)base,lenw-1)

;//預處理b^n

for(

int i=lenw;i++i)

//t向後找子串去和w比較

printf

("%d\n"

,ans);}

return0;

}

/**

遞推預處理,未取模

跑了 235ms

*/#include

#include

#include

#include

#include

typedef

unsigned

long

long ull;

using

namespace std;

const

int maxn=

1e6+5;

const

int maxm=

1e4+5;

const

int base=29;

ull pow[maxm]

;char w[maxm]

,t[maxn]

;ull hasht[maxn]

;int n;

ull hashs;

int ans;

void

init()

intmain()

printf

("%d\n"

,ans);}

return0;

}

字串hash,在比賽中還是經常出現的,因為用map可能會tle或者mle。在一些題目裡,是作為某一關鍵的步驟。所以,多練多用,才能熟能生巧,在比賽中靈活運用。

字串Hash入門

字串hash可以通俗的理解為,把乙個字串轉換為乙個整數。但是根據以前學習的hash,會有衝突。但是當我們取p為131 或者13331 mod取2 64時 99.99 的情況是不會有衝突的 hash公式 unsigned long long hash n hash i hash i 1 p id s ...

Hash 字串 字串雜湊

luo gu luogu luogup 3370 p3370 p337 0如題,給定n個字串 第i個字串長度為mi,字串內包含數字 大小寫字母 請求出n個字串中共有多少個不同的字串。第一行包含乙個整數n,為字串的個數。接下來n行每行包含乙個字串,為所提供的字串。輸出包含一行,包含乙個整數,為不同的字...

白兔的字串 字串hash

原題 一道典型的字串hash,至於hash,這裡講的非常好。一開始用map函式一直超時,後來改用unordered map就過了,至於這2個map的區別,這裡講的挺清楚的。之後去查了一下其它方法,發現還有一種方法是手寫map函式 強 指明 大佬 unordered map是跑了600ms,重寫跑了1...