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 (a∗P3+b∗P2+c∗P1+d∗P0)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]=a∗P0modQ |
“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]=(a∗P1+b∗P0)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]=(a∗P2+b∗P1+c∗P0)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]=(a∗P3+b∗P2+c∗P1+d∗P0)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]∗Pr−l+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)。