1. 程式人生 > 實用技巧 >雜湊,hash

雜湊,hash

Hash,一般翻譯做雜湊、雜湊,或音譯為雜湊。————摘自百度百科
先來看個題:給你一坨一些鍵值集<key,value>,\(key\)的範圍是\([1,10^{10}]\),每次詢問\(x\),回答\(key=x\)\(value\)這種一看就知道暴力不行……於是,有些同學會說:我會用map!但map的查詢是 \(O(logn)\)的 QwQ。那麼雜湊可以怎麼做呢?我們可以讓\(hash[f(key)]=value\),其中\(f()\)函式被稱為雜湊函式。至於\(f()\)函式怎麼寫……想怎麼寫就怎麼寫!沒錯,你想怎麼寫就怎麼寫。一般有這麼幾種方法:

  1. 直接定值法:讓\(F(key)=key\)
    。讀者:那不和一般的一樣嘛!要你何用?咳咳,總要從最基礎的開始嘛……好了,這個方法就此結束。
  2. 摺疊法:將關鍵字分成幾部分,取這幾部分的和的前幾位為雜湊地址。例如ISBN碼(對就是那道入門題),以0-442-20586-4為例,可以得到其雜湊地址是:\(hash = 0442 + 2058 + 64 = 2564\)誒?之前不是說前幾位嗎?因為這裡沒有進位啊……如果得到結果是\(10934\),那麼其雜湊地址就是\(0934\)
  3. 取餘數法:這是\(OI\)中最最常用的,就是對其求餘為雜湊地址,如\(4325\),模\(233333\)後得\(4325\),那麼她的雜湊地址就是\(4325\)

那麼現在上面那個問題就好解決了,只要用取餘數法求得\(key\)

的雜湊地址就可以大大壓縮空間了!
但是,你不要高興太早!相信有許多人已經看出來了,雜湊的缺點很明顯,就是容易出現不同的元素有同一個雜湊地址的情況,我們一般稱其為:雜湊衝突。那麼有什麼方法能解決雜湊衝突呢?有許多方法:

  1. 線性探測再雜湊:如果多個數有同一個雜湊地址,如:0 0 0 34 6 44 0 0注:0表示沒有元素。然後又有一個元素\(8\),得到其雜湊地址也是4(即34所在的位置),那麼我們就往後挪一挪:大哥你先來的,我到後面去。於是來到了6的位置——也被佔了,那麼再往後移……最後到了7(即44後面那個)。然後查詢時只要依次往後找就可以了。
  2. 多重雜湊:個人比較喜歡叫雙模數,就是進行多次雜湊,當然,每次的模數都不一樣。如果兩次雜湊的地址都對應,我才認為是同一個。一般情況下雙模數就足夠了,所以我喜歡叫雙模數。要是有三模數,那基本不會衝突了。
  3. 鏈地址法:還記得鏈式前向星嗎?(不記得!那還不滾回去學)鏈式前向星就是把其後面一個一個地鏈起來,這個也一樣,如果雜湊地址相同,就鏈起來。於是乎,我們又想:那開個\(vector\)豈不是又方便又可以解決雜湊衝突?沒錯,我也喜歡用\(vector\)。這兩種你用哪一種都沒關係,不過還是先提醒一下:\(vector\)有可能會爆哦!這也是為什麼大家用鏈式前向星而不用\(vector\)的原因!(所以我也要開始習慣用鏈地址法了)。
  4. 建立一個公共溢位區:這個方法我不會!(不會還理直氣壯……)所以這裡不講,而且這個方法最後得到的模數不是一個質數,而且其因數很多,是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)都是質數。

就講這麼多吧,之後就要靠大家自己實現了!重點還是在多刷題啊!