【字符串哈希(8848,兩個密碼 霧)+哈希表】


 

哈希(hash)

  對於我來說,HASH就像一個加密軟件,你輸入一個值,他就會輸出值,並且比之前的值更優,更方便。而這個值呢,就叫做哈希值。然后字符串哈希就是輸入一個字符串,把它轉成對應的HASH值就行了。

  對於每個字符串,我們通過一個固定的轉換方式,使相同字符串的哈希值一定相同,不同字符串的值盡量不同。因為很可能存在兩個不同的字符串哈希值一樣的操作,我們稱之為“哈希沖突”

  我們此處傳換的方式,就是最常見的進制哈希,它的核心是給出一個固定進制base,把字符串上面的每一個元素看成base進制的每一個數字,然后轉換成十進制,最后的結果就是HASH值。最后我們只需要比較每一個字符串的HASH值就可以知道他們是不是同一個字符串。

  關於進制的選擇,還是很自由的,但是一定不要含有mod的質因子(那你還模什么模),所以我們取進制和mod時,一般都是質數。但是簡單的還是利用unsigned long long,不手動進行取模,它溢出時會自動對2^64取模

  下面我利用【模板】字符串哈希 給大家介紹一下這兩種進制哈希:

題目描述

  如題,給定N個字符串(第i個字符串長度為Mi,字符串內包含數字、大小寫字母,大小寫敏感),請求出N個字符串中共有多少個不同的字符串。

輸入輸出格式

輸入格式:

  第一行包含一個整數N,為字符串的個數。

  接下來N行每行包含一個字符串,為所提供的字符串。

輸出格式:

  輸出包含一行,包含一個整數,為不同的字符串個數。

輸入樣例1

5
abc
aaaa
abc
abcc
12345

輸出樣例1

4

1、自然溢出哈希

對於這個哈希,我們不對它取模,而是利用unsigned long long的溢出取模。   

 1 #include<bits/stdc++.h>
 2 #define FAST std::ios::sync_with_stdio(false),std::cin.tie(0),std::cout.tie(0)
 3 using namespace std;
 4 typedef unsigned long long ull;//typedef專門把C++的值的類型改名字,和宏定義一個道理,如自帶的int,char或者自定義的struct 
 5 int n,ans=1;//種類因為不搜第一個,所以初值是一 
 6 ull base=131;//進制數 
 7 int a[10001];//記錄hash值 
 8 int hash(string s)
 9 {
10     ull sum=0;//哈希值
11     for(int i=0;i<s.size();i++)
12     {
13         sum=sum*base+(ull)(s[i]);//乘進制數加上這一位 
14     }
15     return sum;//返回hash值 
16 }
17 int main()
18 {
19     FAST;//優化輸入輸出 
20     cin>>n;
21     for(int i=1;i<=n;i++)
22     {
23         string s;//輸入字符串 
24         cin>>s;
25         a[i]=hash(s);//給它hash值 
26     }
27     sort(a+1,a+1+n);//hash值排序 
28     for(int i=2;i<=n;i++)
29     {
30         if(a[i]!=a[i-1])ans++;//不一樣種類就加一 
31     }
32     cout<<ans;
33 }

2、單哈希

自定義取模的值就行了。

    

 1 #include<bits/stdc++.h>
 2 #define FAST std::ios::sync_with_stdio(false),std::cin.tie(0),std::cout.tie(0)
 3 using namespace std;
 4 int mod=20160817;//神奇的數字(質數)但是交上去只能得80分,所以我們用一個大一點的質數(212370440130137957ll) 不要在意后面的兩個符號,交上去就對了 
 5 int n,ans=1;
 6 long long base=131;
 7 int a[100001];
 8 int hash(string s)
 9 {
10     int sum=0;//哈希值
11     for(int i=0;i<s.size();i++)
12     {
13         sum=sum*base+(int)(s[i]);//乘進制數加上這一位 
14         sum%=mod;//模一下 
15     }
16     return sum;//返回hash值 
17 }
18 int main()
19 {
20     FAST;//優化輸入輸出 
21     cin>>n;
22     for(int i=1;i<=n;i++)
23     {
24         string s;//輸入字符串 
25         cin>>s;
26         a[i]=hash(s);//給它hash值 
27     }
28     sort(a+1,a+1+n);//hash值排序 
29     for(int i=2;i<=n;i++)
30     {
31         if(a[i]!=a[i-1])ans++;//不一樣種類就加一 
32     }
33     cout<<ans;
34 }

很顯然,大家看到上面的代碼,還是有可能出現哈希沖突的情況,針對這種情況,我們要不就把模的數再大一點,要不就換種方式,來解決。

3、多重哈希

 這其實就是你用不同的兩種或多種方式哈希,然后分別比對每一種哈希值是否相同——顯然是增加了空間和時間,但也確實增加了其正確性。

 1 #include<bits/stdc++.h>
 2 #define FAST std::ios::sync_with_stdio(false),std::cin.tie(0),std::cout.tie(0)
 3 using namespace std;
 4 int mod1=20160817;
 5 int mod2=19260817; 
 6 int n,ans=1;
 7 int base=131;
 8 struct node{
 9     int x,y;
10 }a[100001];
11 int hash1(string s)
12 {
13     int sum=0;
14     for(int i=0;i<s.size();i++)
15     {
16         sum=base*sum+(int)(s[i]);
17         sum%=mod1;
18     }
19     return sum;
20 }
21 int hash2(string s)
22 {
23     int sum=0;
24     for(int i=0;i<s.size();i++)
25     {
26         sum=base*sum+(int)(s[i]);
27         sum%=mod2;
28     }
29     return sum;//返回hash值 
30 }
31 bool sj(node x,node y)
32 {
33     return x.x<y.x;
34 }
35 int main()
36 {
37     FAST;//優化輸入輸出 
38     cin>>n;
39     for(int i=1;i<=n;i++)
40     {
41         string s;//輸入字符串 
42         cin>>s;
43         a[i].x=hash1(s);//給它hash值 
44         a[i].y=hash2(s);
45     }
46     sort(a+1,a+1+n,sj);//hash值排序 
47     for(int i=2;i<=n;i++)
48     {
49         if(a[i].x!=a[i-1].x||a[i].y!=a[i-1].y)ans++;
50     }
51     cout<<ans;
52 }

 哈希表

說到底,我們哈希還是來分辨字符串的。有人說:字符串不可以用map嗎?

可以的,但是你要知道,map是O(n)的,而哈希只需要計算一下,就直接找到,O(1)的,所以明白了嗎?

這就是hash_map,也叫哈希表

哈希表呢,就是開一串數組,然后每一個字符串對應一個下標,而這個下標,就是他的哈希值(因為這里的數組開不了很大,所以一般不用自然溢出)

所以就存在哈希值重復的情況,我們這里有三種操作方式

開鏈法(鏈式前向星)

當哈希值沖突的時候,我們就可以用鏈式前向星(就和那個存圖一樣的)

void add(int x)
{
    cnt++;
    int key=x%mod;
    sum[cnt].pre=last[key];
    last[key]=cnt;
    sum[cnt].x=x;
}

這里的key是哈希值,last[i]是最后一個哈希值為i的編號,然后pre就是上一個,連起來就行了。查找的時候就像基本的前向星一樣查找就是了,一個一個向上遍歷

這里要提醒一下,當你的mod大,數組就大,空間就大,相應的沖突就小,所以時間就比較快。而mod小了,數組就小,空間小,相應的沖突大,所以時間就會慢,有的時候會超時。

線性勘測法

這里的線性勘測法就是有沖突的,下標就加一,如果還有,就繼續加一......

1 void add(int x)
2 {
3     int key=x%mod;
4     while(sum[key].x&&sum[key].x!=x)key=(key+1)%mod;
5     sum[key].x=x;
6     return ;
7 }

找的時候一樣

bool find(int x)
{
    int key=x%mod;
    while(sum[key].x&&sum[key].x!=x)key=(key+1)%mod;
    if(!sum[key].x)return false;
    return true;
}

所以這個時間復雜度還是很麻煩的,因為你會發現元素都是堆在一堆的,查找很慢,所以我們要跳來跳去

二次勘測法

這個用的就是平方跳跳跳,1^2,-1^2,2^2.......(S是平方數組)

void add(int x)
{
    int key=x%mod;
    int i=1;
    while(sum[key].x&&sum[key].x!=x)
    {
        key=((key+S[i])%mod+mod)%mod;
        i++;
    }
    sum[key].x=x;
}
 1 bool find(int x)
 2 {
 3     int key=x%mod;
 4     int i=1;
 5     while(sum[key].x&&sum[key].x!=x)
 6     {
 7         key=((key+S[i])%mod+mod)%mod;
 8         i++;
 9     }
10     if(!sum[key].x)return false;
11     return true;
12 }

因為這種方法存在一直在跳循環,所以數組空間最好開大點......


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM