哈希專題總結

哈希專題算是我學的最比較好的專題之一了…感覺。

我把哈希的操作籠統地總結爲,你有很多東西,然後你要get一件新的東西,你需要判斷有沒有這件東西,然後你再拿下它。

而哈希的核心問題就集中在如何判斷上面。

先拋開這個問題,我們想另外一件事,哈希可以用來做什麼。最簡單地,可以用來判重。而依我的最近刷的題加以理解,哈希既可以用來幫助狀態記錄,也可以用來減少枚舉量。

回到開始的問題,怎樣判重。我覺得,所有的哈希都無異於設一個集合,往裏面及元素罷了。而最簡單的方式就是開一個數組(即桶),記錄正整數元素出現的有無。而對於其他類型的元素,我們可以用到一些數據結構比如哈希樹,甚至STL中的map,set等。

我們來討論各個方式的優劣。開數組無疑是最快捷的,然而它的缺點也同樣明顯,對於沒有出現過的元素,它仍然要開闢同樣大的內存,導致空間開銷很吃緊,優點是時間調用很快;對於樹,代碼實現稍顯繁瑣,而空間複雜度與出現元素個數成正比,但查詢速度略差於數組;下面重點談下STL_map的優缺點:在等同的情況下,map的時空複雜度無疑是佔劣勢的,而在時空寬裕的情況下,它的優勢就同樣顯現出來了。Map對未出現的元素並不開闢空間,這也就意味着,在某些情況下,map的空間甚至可以達到更優。而在另一些情況下,map的時間(並不是時間複雜度)也會達到更優,如對string處理的時間優於一般的字符串處理。另外兩個顯而易見的優點,一是可以做到0衝突,二是實現起來也和數組一樣方便。所以,我們並不能一味地排斥STL,選擇更優的算法而不是更高端的算法。

話說回來,我們要考慮如何將現有的參量轉化爲可記錄在集合(Hash表)中的元素,暫且列舉常見的幾大類(其實大都可以用map,下面不再另外進行討論):
大整數 直接對大素數取餘
狀態 常見於廣搜問題中(如八數碼/立體八數碼(UVa 1604)),可以把狀態轉化爲大整數
字符串 轉化爲多進制數
特殊整數_全排列 康拓展開及其逆運算

不難發現,最方便的方法其實都是將元素轉化爲易於處理的數字。而這帶來一個很大的問題,數字一大,數組下標不夠用了怎麼辦呢—-前面已經提到,可以取模。而取了模,不同的元素有可能會具有相同的哈希值,這時候我們就需要一些技術,類如開放尋址和拉鍊。另外在空間允許的情況下,可以在相同的哈希位值上開放更多的“輔助數組”,記錄更多的元素特徵以防止衝突。如[POJ2000 Squares],由一條對角線確定一個正方形,兩點確定一條直線,可以記錄對角線的頂點的座標來判斷衝突。同時也要注意,輔助數組並不是多多益善,最好用盡量少的特徵來描述狀態,如這題,由|x|,|y|<20000,我們可以把一個頂點的座標記錄在一個int上,把y累在x之後,即point=x*100000+y[ (23333,6666)->233336666],當然要注意負數的處理。這樣,可以將四個輔助數組縮減到兩個。

可見,哈希的主要難點集中在空間和時間的優劣上,事實上,很多哈希題目在暴力情況下的時間和空間是有衝突的,即兩點只能取其一,所以需要一些解決辦法。某些情況下,改變枚舉方式可達到空間時間更優。簡單的如[POJ 1840 Eqs],另外如[CodeVS1306廣播操的遊戲],使用map,若把所有子串直接push進去直接MLE,而如果每次分別枚舉相同長的子串,分別進行哈希,不但可以減少枚舉量,每個長度for完後對map進行clear,因爲並不會和之後的相同了,還使得空間複雜度降低。

最後再講一個例子來說明好的枚舉也可以有利於哈希:[POJ 1971 Parallelogram Counting].這一題,有種做法枚舉3個點確定2條邊,由這2條邊確定第4的點,再用Hash判斷是否存在,但是O(n^3)絕對TLE。後來想到,由平行四邊形對角線相互平分,即兩條中點相同的線段確定一個平行四邊形,只要枚舉兩點,並記錄他們的中點,再Hash搜索這個中點是否出現過就可以進行計數了。

由此看來,哈希只是一種手段,運用到題目中是很考驗想法的。哈希處理最大的難處可能並不在與時間,空間可能出現更大的問題。所以,儘量用精煉的信息代表狀態,必要時還需保留逆運算。在空間允許的條件下,儘量避免衝突。

以上,最後祝大家Hash Hash 愉快—–

2105.5.17
By Foggy

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章