哈希和哈希表(超詳細!!!)

介紹

哈希算法是經過一個哈希函數,將一段數據(也包括字符串、較大的數字等)轉化爲可以用變量表示或是直接就可做爲數組下標的數字,這樣轉化後的數值咱們稱之爲哈希值, 也就是算出一個數來表明一個字符串算法

咱們經過哈希值從而實現很快地查找和匹配,數組

經常使用:字符串Hash和哈希表。ide

字符串Hash流程 

若是咱們用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正確性

字符串Hash對於任意不一樣的字符串所產生的哈希值必然是互不相同的嗎?顯然不是的,但機率很低在競賽中咱們經常認爲這種狀況不會發生

即使如此,咱們還能夠再用「雙哈希」下降出現相同哈希值的機率,即取不一樣的模數,把不一樣模數算出的哈希值都記下來,只有幾個哈希值都同樣,咱們才能斷定匹配。咱們一般用雙哈希就能夠將衝突的機率降到很低,若是分別取h=10^9+7h=10^9+9,就幾乎不可能發生衝突,由於他們是一對「孿生素數」。


 【例題1】Oulipo(信息學奧賽一本通 1455)

【題目描述】

給出兩個字符串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】圖書管理(信息學奧賽一本通 1456)

【題目描述】

圖書管理是一件十分繁雜的工做,在一個圖書館中天天都會有許多新書加入。爲了更方便的管理圖書(以便於幫助想要借書的客人快速查找他們是否有他們所須要的書),咱們須要設計一個圖書查找系統。 該系統須要支持 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

 

相關文章
相關標籤/搜索