雜湊表:如何實現word編輯器的拼寫檢查?

仙花鬥影發表於2019-02-09

Word文件編輯器大家應該經常使用吧,大家有沒有留意到它編輯功能,當我們輸入一個錯誤的單詞時,單詞單面就會標紅提示“拼寫錯誤”,這個功能是怎麼實現的呢?其實啊,它是通過雜湊表實現的,學習了雜湊表原理後你就懂得這個功能的實現方式了。

雜湊表

雜湊表的英文名叫Hash Table,一般叫雜湊表或雜湊表,雜湊表用的是陣列支援按照下標隨機訪問資料的特性,所以雜湊表就是陣列的一種擴充套件,由陣列演化而來,可以說,如果沒有陣列就沒有雜湊表。

我用一個列子解釋一下,我們去游泳館游泳時一般都會寄存衣物,這時前臺就會登記我們名字後分配一個儲物櫃編號卡,後面我們通過這個編號卡就能快速地找到櫃子儲存衣物,回去時也能快速找到櫃子取回衣物。

這裡儲物櫃是按照編號順序排列,就相當於一個陣列,由於每天去游泳的人都各不相同,就不能每個櫃子都貼上對應人的名字了,所以儲物前就會先去前臺分配一個編號,再根據編號的下標儲存在陣列的下標位置。

這就是典型的雜湊思想。每個去游泳的人的名字我們叫做(key)或者關鍵字。我們把前臺通過名字分配儲物櫃號的對應過程叫作雜湊函式,而通過雜湊函式計算得到的儲物櫃號碼叫作雜湊值

雜湊函式

雜湊函式,顧名思義,它就是一個函式,我們可以把它定義為hash(key),其中key就是元素的鍵,hash(key)就是通過雜湊函式計算得到的雜湊值。

雜湊表:如何實現word編輯器的拼寫檢查?

剛剛舉的例子中,雜湊函式其實就是前臺工作人員將名字和號碼牌對應起來的一個對應關係,這個例子比較不恰當,並沒有一個固定的公式。那麼,實用場景中,要怎麼設計構造雜湊函式呢,我總結了三點基本的要求:

  1. 雜湊函式計算得到的雜湊值必須是一個非負整數;
  2. key1 = key2,那hash(key1) = hash(key2);
  3. key1 ≠ key2,那hash(key1) ≠ hash(key2);

第一點很容易理解,雜湊值最後是作為陣列的下標的,陣列下標是從0開始的;第二點,相同的key,得到的雜湊值也應該是相同的。

第三點看起來合情合理,但是在真實場景中,要想找到一個不同的鍵得出的雜湊值都不一樣的雜湊函式幾乎是不可能的,即便像業界著名的MD5、SHA、CRC等雜湊演算法也無法避免雜湊衝突,因為陣列的空間有限,函式計算得到的值還必須在陣列個數範圍內,因此就會有很大概率出現衝突。

雜湊衝突

再好的雜湊函式也無法避免雜湊衝突,那怎麼辦呢?只能通過其他方式解決,一般雜湊衝突的解決辦法有兩類:開放定址法連結串列法

1.開放定址法

開放定址法的核心思想是,如果出現了雜湊衝突,就尋找下一個空閒位置,插入新的資料。開放定址法也有多種方式,將介紹一個簡單的探測方法,線性探測(Linear Probing)。

線性探測

當我們往雜湊表插入資料時,如果經過雜湊函式雜湊之後,儲存位置已經被佔用了,我們就從當前位置依次往後查詢,將資料插入到找到的空閒位置,如果遍歷到尾部仍沒有空閒位置,我們就從表頭開始找,直到找到為止。如圖所示

雜湊表:如何實現word編輯器的拼寫檢查?

通過線性探測要查詢資料時,和插入資料類似,也是通過雜湊函式得到對應位置的元素,和要查詢的資料作對比,如果一致,則取出該值,如果不一致,則從該位置往下到雜湊表的空閒位置一個個查詢,如果找到,取出對應值,如果沒有找到,則資料不存在。

而但通過線性探測法刪除一個元素時就比較麻煩,如果查詢到對應的元素時直接將該元素對應的位置置空的話,那按照上面說的線性探測查詢方法,遇到一個空的位置時就停止查詢,那這個空位置如果是剛剛被刪除的元素,這時候這個查詢方法就失敗了。所以,刪除一個元素時並不是直接刪除,而是在要刪除的位置標記deleted。在查詢一個元素時如果遇到deleted標記的元素,則繼續往下查詢,如下圖所示。

雜湊表:如何實現word編輯器的拼寫檢查?

通過上面的介紹,我們可以知道,線性探測法有一個弊端。就是雜湊表剩餘空間不足時,就會頻繁地出現雜湊衝突,導致效率不高,極端情況下插入一個元素會時間複雜度為O(n)。

對於開放定址的衝突解決方法,除了線性探測方法外,還有另外兩種比較經典的探測方法,分別為二次探測雙重雜湊

二次探測

二次探測法,和線性探測法類似,線性探測每次的探測步長是1步,它探測的下標序列是hash(key)+0,hash(key)+1,hash(key)+2,hash(key)+3...而二次探測的步長是原來的“二次方步長”,它的探測下標序列是列是hash(key)+0,hash(key)+1,hash(key)+4,hash(key)+9...

雙重雜湊

雙重雜湊,意思是不僅要使用一個雜湊函式,我們要使用一組雜湊函式hash1(key),hash2(key),hash3(key)...先使用第一個雜湊函式計算雜湊位置,如果出現衝突,再使用第二個雜湊函式,依次類推,直到找到空閒位置。

不管使用哪種線性衝突解決方法,當空閒位置較少的時候,出現衝突的概率就會加大,為了保證雜湊表的操作效率,一般會保證雜湊表有一定比例的空閒位置,我們用裝載因子來表示雜湊表的空閒比例,它的計算公式如下

雜湊表的裝載因子 = 填入表中的元素個數 / 雜湊表長度
複製程式碼

2.連結串列法

連結串列法是一種更加普遍的雜湊表衝突解決方法,相比線性探測法,它更簡單更容易理解。如圖所示,雜湊表的元素就是一個“桶”或“槽”,每個桶都放入一個連結串列,將雜湊值相同的元素都放在同一個連結串列中。

雜湊表:如何實現word編輯器的拼寫檢查?

當插入的時候,我們只需要通過雜湊函式計算出對應的雜湊槽位,將其插入到對應的連結串列中即可,時間複雜度為O(1)。當要查詢或刪除時,即通過同樣的方法找到對應的槽位,再遍歷連結串列查詢或刪除,那查詢和刪除的時間複雜度是多少呢?

查詢和刪除的時間複雜度和每個槽位連結串列的長度成正比,假設連結串列平均長度為k,那時間複雜度則為O(k)。對於雜湊比較均勻的雜湊函式,理論上k = n / m,其中n為雜湊表中資料的個數,m為雜湊表的長度。

解答開篇

有了上面的雜湊表的介紹,我們再來回顧下開篇提到的Word文件編輯器的拼寫錯誤提示是怎麼實現的?

我們常用的英文單詞大約有20萬個左右,假設平均每個單詞有10個字母,那每個單詞就大約有10個位元組,20萬個單詞就有差不多2M左右的大小,對於現代的計算機來說,完全可以將20萬個單詞放在記憶體中,儲存在雜湊表,每次輸入一個單詞時,就通過雜湊表查詢,如果能找到就是拼寫正確的,如果找不到則提示拼寫錯誤。

相關文章