哈希算法是經過一個哈希函數,將一段數據(也包括字符串、較大的數字等)轉化爲可以用變量表示或是直接就可做爲數組下標的數字,這樣轉化後的數值咱們稱之爲哈希值, 也就是算出一個數來表明一個字符串。算法
咱們經過哈希值從而實現很快地查找和匹配,數組
經常使用:字符串Hash和哈希表。ide
若是咱們用O(m)的時間來計算長度爲m的字符串的哈希值,則總的時間複雜度並無改觀,這裏就須要用到一個叫作滾動哈希的優化技巧。函數
咱們選取兩個合適的互素常數b(進制)和h(模數)(b < h),假設字符串C =c1c2...cm,那麼咱們定義哈希函數:優化
正常的數字是十進制的,這裏b是基數,至關於把字符串看作是b進制數。spa
這一過程是遞推計算的,設H(c, k)爲前k個字符的構成的字符串的哈希值,則:(如下均不考慮取模的狀況)設計
如字符串C=「ACDA」(爲方便處理,咱們令‘A’表示1,‘B’表示2,以此類推),則:3d
一般題目要求的判斷字符串C 從位置k+1開始的長度爲n的子串C'=ck+1ck+2...ck+n的哈希值與另外一匹配串S = s1s2...sn的哈希值是否相等,則:code
因而只要預處理出bn,就能在O(1)時間內獲得任意的字符串子串哈希值,從而完成字符串匹配,那麼上述字符串匹配問題的總複雜度就爲O(n + m)。blog
如字符串C=「ACDA」,S=」CD」,當k=1, n=2時:
所以子串C'與匹配串S匹配。
在實現時,能夠利用64位無符號整數計算哈希值,即取h=2^64,經過天然溢出省去求模運算。
字符串Hash對於任意不一樣的字符串所產生的哈希值必然是互不相同的嗎?顯然不是的,但機率很低,在競賽中咱們經常認爲這種狀況不會發生。
即使如此,咱們還能夠再用「雙哈希」下降出現相同哈希值的機率,即取不一樣的模數,把不一樣模數算出的哈希值都記下來,只有幾個哈希值都同樣,咱們才能斷定匹配。咱們一般用雙哈希就能夠將衝突的機率降到很低,若是分別取h=10^9+7和h=10^9+9,就幾乎不可能發生衝突,由於他們是一對「孿生素數」。
【題目描述】
給出兩個字符串s1,s2((只有大寫字母),求s1在s2中出現多少次。 例如:s1="ABA",s2="ABAABA",答案爲2。
【輸入】
輸入T組數據,每組數據輸出結果。
【輸出】
如題述。
【輸入樣例】
3
BAPC
BAPC
AZA
AZAAZAAZA
VEEDI AVERDXIVYERDLAN
【輸出樣例】
1
3
0
如今要存儲和使用下面的線性表:A(1,75,324,43,1353,91,40)。
定義一個一維數組A[1...n],此時n=7,將表中元素按大小順序存儲在A[i]中,但這樣就算使用二分查找,咱們仍須要用O(log n)的時間去查找某個元素。
爲了用O(1)的時間實現查找,能夠開一個一維數組A[1...1353],使得A[key]=key,但顯然形成了空間上的很大浪費,尤爲是數據範圍很大時。
爲了使空間開銷減小,咱們能夠對第二種方法加以優化,設計一個哈希函數H(key) = key mod 13,而後令A[H(key)]=key,這樣一來定義一個一維數組A[0...12]就已足夠,這種方法就是哈希表。
但剛纔那樣的存儲是有問題的,如H(1)=H(40)=1,在存儲40時又把1給覆蓋掉了,那麼查詢就會出現錯誤,這種不一樣的數據產生相同哈希值的狀況咱們稱之爲衝突。
這裏與字符串Hash有所不一樣,可能不論咱們怎樣選用哈希函數,仍是很難避免產生衝突。
所以咱們考慮對每個哈希值記一個鏈表(其實也就至關於鄰接表),把全部等於同一個哈希值的數字都存儲下來,而查詢只要遍歷對應的鏈表便可,這樣實際複雜度取決於查詢的鏈表長度,也能夠看作是常數級。
例如咱們定義哈希函數H(x) = x mod 16,插入一些數據的效果以下圖。
一般狀況下,咱們用除餘法來構造哈希函數。 ·即選擇一個適當的正整數b,用其對取模的餘數做爲哈希值。
其關鍵是b的選取,爲了儘可能避免衝突,通常選爲可以存儲下而且儘可能大的素數(通常狀況下咱們根據空間取10^6左右的素數)。通常地說,若是b的約數越多,那麼衝突的概率就越大。
using namespace std; const int N = 50000; //定義總共存入哈希表的數字個數
const int b = 999979; //定義哈希函數中的模數
int tot, adj[H], nxt[N], num[N]]; void insert(int key) //將一個數字插入哈希表
{ int h = key % b; //除餘法
for (int e = adj[h]; e; e = nxt[e]) { if (num[e] == key) return ; } //若是鏈表中已經出現過當前的數字就不用再存儲一遍
nxt[++tot] = adj[h], adj[h] = tot; num[tot] = key; //創建鏈表,存儲下全部哈希值等於h的數字
} bool query(int key) //查詢數字x是否存在於哈希表中
{ int h = key % b; //一樣先進行取餘,求其哈希值
for (int e = adj[h]; e; e = nxt[e]) if (num[e] == key) return true; //查詢對應鏈表,查詢到則表示存在
return false; //不存在返回 false
} }
【題目描述】
圖書管理是一件十分繁雜的工做,在一個圖書館中天天都會有許多新書加入。爲了更方便的管理圖書(以便於幫助想要借書的客人快速查找他們是否有他們所須要的書),咱們須要設計一個圖書查找系統。 該系統須要支持 2 種操做: add(s) 表示新加入一本書名爲 s 的圖書。 find(s) 表示查詢是否存在一本書名爲 s 的圖書。
【輸入】
第一行包括一個正整數 n,表示操做數。 如下 n 行,每行給出 2 種操做中的某一個指令條,指令格式爲:add s 或 find s 在書名 s 與指令(add,find)之間有一個隔開,咱們保證全部書名的長度都不超過 200。能夠假設讀入數據是準確無誤的。
【輸出】
對於每一個 find(s) 指令,咱們必須對應的輸出一行 yes 或 no,表示當前所查詢的書是否存在於圖書館內。 注意:一開始時圖書館內是沒有一本圖書的。而且,對於相同字母不一樣大小寫的書名,咱們認爲它們是不一樣的。
【輸入樣例】
4
add Inside C#
find Effective Java
add Effective Java
find Effective Java
【輸出樣例】
no
yes