雜湊 hash 空間換時間 超詳細講解 C語言

2021-10-11 18:03:20 字數 4501 閱讀 7046

是常用的演算法思想之一,在很多程式中都會有意無意地使用到。

先來看乙個簡單的問題:給出n個正整數,再給出m個正整數,問這m個數中的每個數分別是否在n個數**現過,其中n,m都小於等於105,且所有正數均不超過105。例如n=5,m=3,n個正整數為,欲查詢單的m個正整數為,於是後者之中有7和2在n個正整數**現過,而4是沒有出現的。

方法一:

對這個問題,最直觀的思路是:對每個欲查詢的正整數x,遍歷所有n個數,看是否有乙個數與x相等。這種做法的時間複雜度為o(mn),當m和n都很大(105級別)時,顯然是無法承受的。

該如何做呢?

方法二(雜湊思想):

不妨用空間換時間,即設定乙個bool型別的陣列hashtable[100010],其中hashtable[x]==true表示正整數x在n這個正整數**現過,而hashtable[x]==false表示整數x在n個正整數裡面沒有出現過。這樣就可以在一開始讀入n個正整數時就進行預處理,即當讀入的數為x時,就令tabletable[x]==true(初始化的時候權威false)。於是,對於m個欲查詢的數,就能直接通過hashtable陣列判斷出每個數是否出現過。顯然這種做法的時間複雜度為o(n+m)

**如下:

#include

const

int maxn =

100010

;bool hashtable[maxn]=;

intmain()

for(

int i =

0;i)else

}}

上面的兩個問題都有個特點, 那就是直接把輸入的數作為陣列的下標來對這個的性質進行統計(這種做法非常實用,請務必掌握).這是乙個很好的用空間換時間的策略,因為它將查詢的複雜度降到了o(1)級別。但是,這個策略暫時還有乙個問題一上面的題目**現的每個數都不會超過105,因此直接作為陣列下標是可行的,但是如果輸入可能是109大小 的整數(例如111)或者甚至是乙個字串(例如"i love you"),就不能將它們直接 作為陣列下標了。要是有種做法, 可以把這些亂 七八糟的元素轉換為乙個在能接受範圍內的整數,那該多麼美好呀!

這樣的做法當然是存在的,那就是雜湊(hash)。一般來說,雜湊可以濃縮成一句話「將元素通過乙個函式轉換為整數,使得該整數可以盡量唯一地代表這個元素」。 其中把這個轉 換函式稱為雜湊函式h,也就是說,如果元素在轉換前為key, 那麼轉換後就是乙個整數h(key)。

那麼對key是整數的情況來說,有哪些常用的雜湊函式呢? -般來說,常用的有直接定 址法、平方取中法、除留餘數法等,其中直接定址法是指恒等變換(即h(key)= key**,本節開始的問題就是直接把key 作為陣列下標,是最常見最實用的雜湊應用**)或是線性變換(即h(key)=a*key+b);而平方取中法是指取key的平方的中間若干位作為hash值(很少用)。一般來說比較實用的還有除留餘數法,我們對其進行特別介紹。

除留餘數法是指把key除以-乙個數mod得到的餘數作為hash值的方法,即

h(key)= key % mod

通過這個雜湊函式,可以把很大的數轉換為不超過mod的整數,這樣就可以將它作為可行的陣列下標(注意:表長tsize必須不小於mod,不然會產生越界)。顯然,當mod是乙個素數時,h(key)能盡可能覆蓋[0, mod)範圍內的每乙個數。因此一般為了方便起見,下文中取 tsize是個素數, 而mod直接取成與tsie相等

但是稍加思考便可以注意到,通過除留餘數法可能會有兩個不同的數key1和key2,它們的hash值h(key1)與h(key2)是相同的,這樣當key1已經把表中位置為h(key1)的單元佔據時,key2 便不能再使用這個位置了。我們把這種情況叫作「衝突」。

既然衝突不可避免,那就要想辦法解決衝突。下面以三種方法來解決衝突為例,其中第 一種和第 二種都計算了新的hash值,又稱為開放定址法。

1.線性探查法(linear probing)

當得到key的hash值h(key),但是表中下標為h(key)的位置已經被某個其他元素使用了, 那麼就檢查下乙個位置h(key)+ 1是否被佔,如果沒有,就使用這個位置;否則就繼續檢查 下乙個位置(也就是將hash值不斷加1)。 如果檢查過程中超過了表長,那麼就回到表的首位繼續迴圈,直到找到乙個可以使用的位置,或者是發現表中所有位置都已被使用。顯然,這個做法容易導致扎堆,即表中連續若干個位置都被使用,這在一一定程度上會降低效率。

2.平方探查法(quadratic probing)

在平方探查法中,為了盡可能避免扎堆現象,當表中下標為h(key)的位置被佔時,將按 下面的順序檢查表中的位置: h(key)+ 12、h(key)-12、 h(key)+22、h(key)- 22、h(key)+32…如果檢查過程中h(key)+k2超過了表長tsize,那麼就把h(key)+ k2對錶長tsize取模;如果檢查過程**現h(key)-k2

<0的情況(假設表的首位為0),那麼將((h(key)-k2) %tsize+ tsize) % tsize作為結果(等價於將h(key)- k2不斷加上tsize直到出現第乙個非負數)。 如果想避免負數的麻煩,可以只進行正向的平方探查。可以證明,如果k在[0, tsize)範圍內都無法找到位置,那麼當k≥tsize時,也一定無法找到位置。

3.鏈位址法(拉鍊法)

和上面兩種方法不同,鏈位址法不計算新的hash值,而是把所有h(key)相同的key連線成一條單鏈表。這樣可以設定乙個陣列link,範圍是 link[0] ~ link[mod-1], 其中link[h]存放h(key)= h的一條單鏈表,於是當多個關鍵字key的hash值都是h時,就可以直接把這些衝突的key直接用單鏈表連線起來,此時就可以遍歷這條單鏈表來尋找所有h(key)=h的key。

當然,一般來說, 可以使用標準庫模板庫中的 map來直接使用 hash的功能(c++11以後可以用unordered _map, 速度更快),因此除非必須模擬這些方法或是對演算法的效率要求比較高,一般不需要自己實現上面解決衝突的方法。

如果key不是整數,那麼又應當如何設計雜湊函式呢?

乙個例子是:如何將個二維整點 p的座標對映為 乙個整數使得整點p可以由該整數唯一地代表。假設乙個整點p的座標是(x,y),其中0≤x, y本節的重點在於字串hash。字串hash是指將一乙個字串 s對映為-乙個整數,使得 該整數可以盡可能唯一地代表字串 s。本節只討論將字串轉換為唯一的整數。

為了討論問題方便,先假設字串均由大寫字母a~z構成。在這個基礎上,不妨把a~z視為0~25,這樣就把26個大寫字母對應到了二十六進製制中。接著,按照將二十六進製制轉換為十進位制的思路,由進製轉換的結論可知,在進製轉換過程中,得到的十進位制肯定是唯一的, 由此便可實現將字串對映為整數的需求(注意: 轉換成的整數最大為是26len-1,其中len為字串長度)。**如下:

int

hashfunc

(char s,

int len)

else

if(s[i]

>=

'a'&& s[i]

<=

'z')

而如果出現了數字,一般有兩種處理方法:

①按照小寫字母的處理方法,增大進製數至62。

②如果保證在字串的末尾是確定個數的數字,那麼就可以把前面英文本母的部分按上面的思路轉換成整數,再將末尾的數字直接拼接上去。例如對由三個字元加一位數字組成的字串「bcd4」來說,就可以先將前面的「bcd」轉換為整數731,然後直接拼接上末位的4變為7314即可。下面的**體現了這個例子:

int

hashfunc

(char s,

int len)(

id = id *10+

(s[len-1]

-'0');

return id;

}

以乙個問題結尾:給出n個字串(由恰好三位大寫字母組成),再給出m個查詢字串,問每個查詢字串在n個字串**現的次數。

下面直接給出使用字串hash的**,讀者可以自己體會一下:

#include

const

int maxn =

100;

char s [maxn][5

],temp[5]

;int hashtable[26*

26*26+

10];int hashfunc (

char s[

],int len)

return id;

}int

main()

for(

int i=

0;i)return0;

}

JS雜湊表演算法 空間換時間

題目 力扣 給定乙個整數陣列 nums 和乙個目標值 target,請你在該陣列中找出和為目標值的那 兩個 整數,並返回他們的陣列下標。你可以假設每種輸入只會對應乙個答案。但是,陣列中同乙個元素不能使用兩遍。示例 給定 nums 2,7,11,15 target 9 因為 nums 0 nums 1...

空間換時間

空間換時間這樣的思路,在程式設計演算法 硬體設計燈 戰爭領域都是有涉及的,我這裡所講的空間換時間是被包含在資料倉儲範疇裡面的。空間換時間,我的理解是什麼呢?就是我們需要設計一種資料結構,這種資料結構最大的特點是提高查詢的效率。我在這裡總結一下,空間換時間的做法。在業務系統裡面,一般會按照資料庫的三正...

空間換時間隨筆

空間換時間隨筆 在實際的生活中,難免遇到排隊的事情,比如去銀行存錢取錢,轉賬等,如果去櫃檯都有先拿個號,按號排隊處理業務,這是非常常見的,火爆的餐館也是這樣。為提高效率,減少等待時間,銀行的做法可以是簡化辦事流程,提供營業員的處理速度來減少使用者等待的時間,另乙個做法可以是增加櫃檯,多些營業員來處理...