雜湊表Hash:概念與基本操作
什麼是Hash
Hash就像是一個桶排,那隻不過是把各個元素的數值當做下標進行儲存.其最常用的用途就是用來判重.但是,如何對字串進行判重,不可能一個一個往前超,若n上萬則顯然不可行.我們可以選擇進行Hash,將每一個字串或者大數字進行一定的操作即可進行.
對大整數型別進行Hash
取模法
對於每一個大整數進行取模,即除以一個大質數(例如107,10007,1000007,1-奇數個0-7),這樣就作為陣列的下標進行儲存了.
為什麼要對一個大整數取模
emmmmmm......經眾多數學家證明重複的機率較小
萬一實在有不可避免的誤差/出題人卡你怎麼辦
有兩種方法:
1.線性探測開拓地址法
2.拉鍊法
對字串進行Hash
對字串,我們可以選擇按權展開法進行實現.
例如:對於字元abc,我們可以轉換成26進位制,即:126^0+226^1+3*26^2,很好理解吧
例如,對於字串3gT,我們可以轉換成某個質數進位制即可.
但是,不要忘了,在按權展開的時候需要對一個大整數取模
如何避免字串Hash的誤差
同樣:
1.線性探測開拓地址法
2.拉鍊法
線性探測開拓地址法
若hash[k]已經被填入,則嘗試填寫hash[k+1],hash[k+2]....等陣列.最後在查詢的時候也逐一查詢即可.但畢竟查詢不方便,個人更加偏向於拉鍊發
拉鍊法
跟據每一個數值的下標擴充一個連結串列,每次在連結串列上逐一查詢即可.
為了方便實現,我們可以選用STL中的vector(即動態陣列或不定長陣列)來代替連結串列,這樣也十分容易實現.
整數Hash例項:不重複數字【JLOI2011】
題目描述
給出N個數,要求把其中重複的去掉,只保留第一次出現的數。
例如,給出的數為1 2 18 3 3 19 2 3 6 5 4,其中2和3有重複,去除後的結果為1 2 18 3 19 6 5 4。
輸入輸出格式
輸入格式:
輸入第一行為正整數T,表示有T組資料。
接下來每組資料包括兩行,第一行為正整數N,表示有N個數。第二行為要去重的N個正整數。
輸出格式:
對於每組資料,輸出一行,為去重後剩下的數字,數字之間用一個空格隔開。
(原題體面戳這裡)
對於這道題,直接除以大質數1000007即可.(資料水,沒有判重AC了)
程式碼如下:
#include<bits/stdc++.h> using namespace std; int Hash[1000009]; inline int h(int x){return x%1000007;} void work() { memset(Hash,0,sizeof(Hash)); int n; scanf("%d",&n); for (int i=1;i<=n;i++) { int x; scanf("%d",&x); if (Hash[h(x)]==1) continue; } printf("\n"); } int main() { int T; scanf("%d",&T); while (T--) work(); }
字串Hash
原題題面戳這裡
這道題,我們採用301當做位權;我們採用大質數1000007來取模;用拉鍊法來處理重複;同時用vector來代替連結串列;程式碼實現難度不大.
程式碼如下:
#include<bits/stdc++.h>
using namespace std;
int n,ans=0;
int const M=1000007;//最後取模
vector<string>Link[1000009];
inline int hash(string x)
{
int sum=0;int w=307;//累加的值,位權
for (int i=0;i<x.length();i++)
sum=(sum*w+int(x[i]))%M;
return sum;
}
int main()
{
ios::sync_with_stdio(false);
cin>>n;
for (int i=1;i<=n;i++)
{
string s;cin>>s;
int k=hash(s),flag=1;
for (int j=0;j<Link[k].size();j++)
if (s==Link[k][j]) flag=0;
if (flag==1)
{
Link[k].push_back(s);
ans++;
}
}
cout<<ans;
return 0;
}