雜湊,hash
阿新 • • 發佈:2020-08-23
Hash,一般翻譯做雜湊、雜湊,或音譯為雜湊。————摘自百度百科
先來看個題:給你一坨一些鍵值集<key,value>
,\(key\)的範圍是\([1,10^{10}]\),每次詢問\(x\),回答\(key=x\)的\(value\)這種一看就知道暴力不行……於是,有些同學會說:我會用map
!但map
的查詢是 \(O(logn)\)的 QwQ。那麼雜湊可以怎麼做呢?我們可以讓\(hash[f(key)]=value\),其中\(f()\)函式被稱為雜湊函式。至於\(f()\)函式怎麼寫……想怎麼寫就怎麼寫!沒錯,你想怎麼寫就怎麼寫。一般有這麼幾種方法:
- 直接定值法:讓\(F(key)=key\)
- 摺疊法:將關鍵字分成幾部分,取這幾部分的和的前幾位為雜湊地址。例如ISBN碼(
對就是那道入門題),以0-442-20586-4為例,可以得到其雜湊地址是:\(hash = 0442 + 2058 + 64 = 2564\)誒?之前不是說前幾位嗎?因為這裡沒有進位啊……如果得到結果是\(10934\),那麼其雜湊地址就是\(0934\)。 - 取餘數法:這是\(OI\)中最最常用的,就是對其求餘為雜湊地址,如\(4325\),模\(233333\)後得\(4325\),那麼她的雜湊地址就是\(4325\)。
那麼現在上面那個問題就好解決了,只要用取餘數法求得\(key\)
但是,你不要高興太早!相信有許多人已經看出來了,雜湊的缺點很明顯,就是容易出現不同的元素有同一個雜湊地址的情況,我們一般稱其為:雜湊衝突。那麼有什麼方法能解決雜湊衝突呢?有許多方法:
- 線性探測再雜湊:如果多個數有同一個雜湊地址,如:
0 0 0 34 6 44 0 0
注:0表示沒有元素。然後又有一個元素\(8\),得到其雜湊地址也是4(即34所在的位置),那麼我們就往後挪一挪:大哥你先來的,我到後面去。於是來到了6的位置——也被佔了,那麼再往後移……最後到了7(即44後面那個)。然後查詢時只要依次往後找就可以了。 - 多重雜湊:個人比較喜歡叫雙模數,就是進行多次雜湊,當然,每次的模數都不一樣。如果兩次雜湊的地址都對應,我才認為是同一個。一般情況下雙模數就足夠了,所以我喜歡叫雙模數。要是有三模數,那基本不會衝突了。
- 鏈地址法:還記得鏈式前向星嗎?(
不記得!那還不滾回去學)鏈式前向星就是把其後面一個一個地鏈起來,這個也一樣,如果雜湊地址相同,就鏈起來。於是乎,我們又想:那開個\(vector\)豈不是又方便又可以解決雜湊衝突?沒錯,我也喜歡用\(vector\)。這兩種你用哪一種都沒關係,不過還是先提醒一下:\(vector\)有可能會爆哦!這也是為什麼大家用鏈式前向星而不用\(vector\)的原因!(所以我也要開始習慣用鏈地址法了)。 - 建立一個公共溢位區:這個方法我不會!(
不會還理直氣壯……)所以這裡不講,而且這個方法最後得到的模數不是一個質數,而且其因數很多,是2的次冪,所以……出題人卡你是很容易的!
解決雜湊衝突的方法一般就是這些啦!還有個問題,上面提到模數要是質數,為什麼呢?原因很簡單,根據質數的特性,質數每一個位置都能很好的利用起來,而合數不可以。而且這個質數要大一點(廢話,你來個19,玩個鬼哦)。
好,講完了基礎的,來看一看例題:
P3370 【模板】字串雜湊
噫,剛剛只說了整數雜湊啊!沒關係,記得ASCII碼嗎?我們可以通過ASCII碼,將其轉成一個\(base\)進位制數,當然,是模過的。然後再用鏈地址法,對同一雜湊值的字串進行遍歷,如果都不相同,加入並更新答案。
具體程式碼實現:
#include<cstdio>
#include<string>
#include<vector>
#include<iostream>
#define mod 23333
#define base 298
#define rg register
using namespace std;
int n,ans;
string s;
vector<string>v[mod+5];
void insert()
{
int hash=1;//記錄雜湊值,由於後面要乘所以初值是1
for(rg int i=0;i<s.length();i++)
hash=(1ll*hash*base+s[i])%mod;//1ll就是(long long)1,乘一個1ll,可以保證不爆精度(當然你爆long long或高精度我也沒辦法)
string t=s;//暫存一下
for(rg int i=0;i<v[hash].size();i++)
if(v[hash][i]==t) return ;//判斷,如果有相同的就退出
v[hash].push_back(t);//加入新的字串
ans++;//更新答案
return ;
}
int main()
{
scanf("%d",&n);
for(rg int i=1;i<=n;i++)
{
cin>>s;
insert();
}
printf("%d",ans);
return 0;
}
哦對了,一般233333(2後面跟一堆3)、100007(1和7中間隔一堆0)、1000009(1和9中間隔一堆0)都是質數。
就講這麼多吧,之後就要靠大家自己實現了!重點還是在多刷題啊!