拼寫檢查的四種實現

算法有趣的地方在於,能夠用不一樣的方式處理同一個問題,而且總有更好的方法。好比拼寫檢查。html

拼寫檢查大致是這樣的,給出一個字典文件,給出一個比對文件。比對文件裏的單詞,若是某個單詞不在字典文件裏的話,就認爲拼寫錯誤。要作的就是找出比對文件裏全部拼寫錯誤的單詞。git

暴力破解(brute force)

最直觀的方法,就講文件裏的單詞一一同字典裏的單詞作比較,若是不在字典裏,就輸出。最簡單,也最費時。github

假設字典文件一共有nd個字符,用於比對的文件有nt字符,複雜度則爲O(nd * nt)。算法

具體實現數組

咱們看一下運行速度,測試用的字典裏大約有14萬個單詞,用做比對的文件大約有12萬個單詞。
用時將近3分鐘。顯然速度太慢,須要優化。ruby

單詞查找樹(Trie)

咱們知道,若是給出的單詞以a開頭,那麼字典裏以其餘字母開頭單詞,就不須要咱們作比較了。就比如二叉樹查找,每次查找,老是能夠丟掉不符合要求的那一半。
收到二叉樹的啓發,咱們可使用一種叫作Trie的多叉樹來實現。函數

假設字典裏有ab, ac, c, d這四個單詞,生成的trie會是以下形狀(t一、t2只是表述,方便作討論):post

root(t1)
  /    \     \
 a(t2)  c(t3) d(t4)
/     \
b(t5)  c(t6)

作檢查的時候,須要從跟節點,一級一級的向下找。
假設咱們查找"ad",根節點的子節點t2的值就是a,第一個字母匹配成功了。咱們再匹配第二個字節'd',t2的子樹裏,沒有'd'這個字母,匹配失敗。則"ad"爲一個錯誤的拼寫。
只有當咱們匹配了單詞的每個字母,而且最後一個字母是結尾字母,那麼這個單詞纔是在字典中存在的。測試

具體實現優化

使用一樣輸入只花了不到1.3秒的時間,快了上百倍。

哈希表(Hash Table)

拼寫查找,要求的是插入和查找速度快,Hash Table恰好符合要求。
一樣的數據,只用了0.14秒,比trie的實現快了將近10倍。
具體實現

Bloom Filter

Hash Table 從速度上來講,已是極限了,看起來是最適合的算法。但Bloom Filter某種程度上更合適!

Bloom Filter相對於Hash Table來講,最大的優點就是,Bloom Filter不須要存完整的key(好比單詞)。
Bloom Filter將一組key(好比單詞)經過多個哈希方程映射到一個數組內,並能夠根據這個數組來判斷這個單詞是否已經被插入了。

算法的大致思路以下:
初始化一個n個bit的數組arr,插入某個值時,咱們經過哈希方程,獲得對應的index,將數組這個位置設爲1.
假設咱們使用2個哈希方程h1, h2,咱們會獲得兩個index(多是1個),咱們就將數組的這兩個位置設爲1。

查找的時候,用全部的哈希方程,獲得對應的index,而後檢查數組在這些位置上,是否均爲1,若是均爲1,則認爲這個值被插入過。

僞代碼以下:

require 'bitarray'
class BloomFilter
  attr_accessor :arr
  def initialize
    self.arr = BitArray.new(n)
  end

  def insert(key)
    arr[h1(key)] = 1
    arr[h2(key)] = 1
  end

  def include?(key)
    arr[h1(key)] == 1 && arr[h2(key)] = 1
  end

  # BloomFilter會有多個哈希函數,2個只是爲了說明算法的工做原理。
  def h1(key)
    # xxx
  end

  def h2(key)
    # xxx
  end
end

Bloom Filter雖然大大的節省了空間,但卻有必定的機率出錯(false positive),這種錯誤是,當查找時,一個值沒有被插入,但卻被誤認爲是插入過了。這是由於,哈希方程會產生衝突(collision)。

能夠經過更長的數組和更多的哈希方程來解決這個問題,同事空間實用也不多,因此這是個頗有用的算法。

Bloom Filter還有一個弊端,就是不能作刪除操做。不過能夠經過給每一位加上個counter來解決,就是所謂的Counting Bloom Filter,具體看看這篇文章,就不詳述了。

具體實現

一樣的輸入,須要0.11秒。

執行速度對比

SpellChecker
                      user     system      total        real
brute_force     162.660000   1.560000 164.220000 (168.961906)
by_trie           1.160000   0.100000   1.260000 (  1.305419)
by_hash_table     0.140000   0.000000   0.140000 (  0.145828)
by_bloom_filter   0.100000   0.010000   0.110000 (  0.108583)

相關代碼

參考

Problem Set 5: Mispellings
Bloom Filters: Heuristic Analysis
Bloom Filter概念和原理
A Look Into Bloom Filters with Ruby

相關文章
相關標籤/搜索