1. 程式人生 > 其它 >10.區域性變數與全域性變數、匿名物件

10.區域性變數與全域性變數、匿名物件

技術標籤:演算法模板雜湊表資料結構

雜湊表

雜湊表的關鍵是雜湊函式,以及如何處理衝突。常見的處理衝突的方法有拉鍊法和開放定址法。

1)拉鍊法

使用陣列實現的雜湊表如圖所示,陣列中每個槽對應一條單鏈表。插入操作的基本原理是:根據雜湊函式給出的雜湊值,判斷陣列中該位置是否被佔用,如果沒有,則插入到此位置,如果被佔用,則插入到單鏈表的頭部(頭插法)。查詢操作的基本原理是:根據雜湊函式給出的雜湊值,如果陣列中該位置沒被佔用,則代表查詢的值不存在,如果該位置被佔用,則在位置對應的單鏈表中順序查詢。

演算法題中,雜湊表一般只有插入和查詢操作。如果要實現刪除操作的話,不會直接對陣列進行刪除,一般是再建立一個布林陣列,對需要刪除的元素打個標記(如上圖所示)。

2)開放定址法

只需使用一個一維陣列來儲存,且陣列長度需為一個質數,經驗上取為輸入資料量的2~3倍。插入操作的基本原理是:根據雜湊函式的雜湊值,判斷陣列中該位置是否被佔用,如果沒有,則插入到此位置,如果被佔用,就再看下一個位置是否被佔用,直到找到一個沒被佔用的位置插入。查詢操作的基本原理是:根據雜湊函式的雜湊值,判斷陣列中該位置是否被佔用,如果不被佔用,則查詢的值不存在,如果被佔用,則比較該位置的值與查詢到值是否相等,相等則表示查詢的值存在,不相等的話繼續看下一個位置,並重復前述的判斷。

如果碰到要實現刪除操作,也是不會直接對陣列元素進行刪除,而是再建立一個布林陣列,對需要刪除的元素在新陣列的相應位置打上標記。

如果碰到要手寫簡易雜湊表的情形,推薦使用開放定址法,程式碼比較簡單,不易出錯。程式碼模板如下:

// 陣列初始化,應初始化成一個輸入中不可能出現的值,下面用null表示
// N為輸入資料量的2~3倍
int[] h = new int[N];
for(int i = 0; i < N; i ++) h[i] = null;

// 如果x在雜湊表中,返回x所在陣列中的下標;如果x不在雜湊表中,返回x應該插入的位置
public int find(int x){
    int t = (x % N + N) % N;   // 防止x為負數時取餘運算的結果出錯
    while(h[t] != null &&
h[t] != x){ t ++; if(t == N) t = 0; } return t; }

字串雜湊

計算出字串任一子串的雜湊值,包括如下內容:

1)求出字串的雜湊值

可將字串看成一個 P P P進位制的數( m o d Q mod \space Q modQ,經驗上 P P P取131或13231, Q Q Q 2 64 2^{64} 264時出現衝突的概率很低。

例如,對於字串"abcd",可看作 P P P進位制數,轉換成十進位制數為 ( a ∗ P 3 + b ∗ P 2 + c ∗ P 1 + d ∗ P 0 ) m o d Q (a*P^3+b*P^2+c*P^1+d*P^0)\space mod \space Q (aP3+bP2+cP1+dP0)modQ

2)求出字串的字首雜湊

例如對於字串"abcd",需預處理出下面這些字首字串的雜湊值:

子串雜湊值
“” h [ 0 ] = 1 m o d Q h[0] = 1\space mod\space Q h[0]=1modQ
“a” h [ 1 ] = a ∗ P 0 m o d Q h[1] = a*P^0\space mod\space Q h[1]=aP0modQ
“ab” h [ 2 ] = ( a ∗ P 1 + b ∗ P 0 ) m o d Q h[2]=(a*P^1+b*P^0)\space mod \space Q h[2]=(aP1+bP0)modQ
“abc” h [ 3 ] = ( a ∗ P 2 + b ∗ P 1 + c ∗ P 0 ) m o d Q h[3]=(a*P^2+b*P^1+c*P^0)\space mod \space Q h[3]=(aP2+bP1+cP0)modQ
“abcd” h [ 4 ] = ( a ∗ P 3 + b ∗ P 2 + c ∗ P 1 + d ∗ P 0 ) m o d Q h[4]=(a*P^3+b*P^2+c*P^1+d*P^0)\space mod \space Q h[4]=(aP3+bP2+cP1+dP0)modQ

3)對於字串的任意子串,假設起始索引為 l l l,終止索引為 r r r(包括在內),那麼,該字串子串的雜湊值為 h [ r ] − h [ l ] ∗ P r − l + 1 h[r]-h[l]*P^{r-l+1} h[r]h[l]Prl+1

有個小技巧是,取模的數用 2 6 4 2^64 264,這樣直接用unsigned long long儲存,溢位的結果就是取模的結果。由於數值很大通常會溢位,而java中的long型別又為有符號整型,這裡給出C++程式碼。程式碼模板如下:

// 假設字串用char型別陣列str[1...n]來儲存與,起始位置為1,長度為n

typedef unsigned long long ULL;
ULL h[N], p[N];   // h[k]儲存字串前k個字母的雜湊值,p[k]儲存P^k mod 2^64

// 預處理
p[0] = 1;
for(int i = 1; i <= n; i ++){
    p[i] = p[i - 1] * P;
    h[i] = h[i - 1] * P + str[i];
}

// 計運算元串str[l~r]的雜湊值
ULL get(int l, int r){
    return h[r] - h[l] * p[r - l + 1];
}

上述字串雜湊看起來可能沒啥用,但在有些場景下還是可以應用起來以達到優化的目的:

  • 在經過預處理後,可以在 O ( 1 ) O(1) O(1)的時間內判斷字串的任意子串是否相等。
  • 在一些以String作為key的雜湊表中,查詢的時候並不是 O ( 1 ) O(1) O(1),而與字串的長度有關,這一時間複雜度常會被遺漏掉。可以考慮將這些作為key的字串轉換成對應的雜湊值,這樣的話可將查詢的時間複雜度優化至 O ( 1 ) O(1) O(1)