有時,簡單的算法也有其實用的意義,由於之前公司內部搜索引擎優化的需要,我根據《編程珠璣》中查找英文單詞變位詞的算法,來實現搜索糾錯的功能。
在搜索時,有時記不住單詞,會出現拼寫錯誤的情況,例如,搜索“height”時手誤,搜索了“heigth”,那么我們要在搜索不到的情況下,給他變位詞作為提示(注:因為公司內部搜索,該算法已經夠用,真正的搜索引擎應該是采用更高效的算法,請有經驗的前輩賜教)。
算法分三步:①對單詞簽名; ②根據簽名的字典序排序; ③根據排序結果擠壓,使變位詞聚合在一起;
a) 快速單詞簽名算法(當前僅使用於英文單詞,如果包含其他字符,需做拓展):
鑒於英文字母可枚舉且數量只有26個,故這里采用變形的基數排序算法,以實現快速簽名:
Step 1: 定義一個26位的 int[] 數組,數組從0~25分別表示:A,B,C,D …… X,Y,Z (如圖1),初始化全部為0:
A |
B |
C |
D |
E |
F |
G |
H |
I |
J |
K |
L |
M |
N |
O |
P |
G |
R |
S |
T |
U |
V |
W |
X |
Y |
Z |
|
(圖1)
Step 2: 將英文字母轉換統一(大小寫統一),如 Jary → jary,再使用如下公式:存入下標為x-97的位置,例如 a的ascii碼為97,則它對應存入byte[]數組中的97-97=0的位置,即該位置自增1;以此類推。
Stpe 3: 給單詞簽名,從0到25掃描數組,例如 what → ahtw, wath → ahtw, system → ems2ty ;
這樣我們就可以只通過一次掃描來獲取一個單詞的簽名,代碼如下:
1 public static string SignWord(string word) 2 { 3 int[] dict = new int[26]; 4 5 var array = word.ToLower().ToArray(); 6 foreach(var a in array) { 7 dict[a - 97]++; 8 } 9 StringBuilder sb = new StringBuilder(); 10 for (var i = 0; i < 26; i++) { 11 if (dict[i] != 0) { 12 var t = dict[i].ToString().Equals("1") ? "": dict[i].ToString(); 13 sb.Append((char)(i + 97) + t); 14 } 15 } 16 return sb.ToString(); 17 }
b) 相同簽名的單詞的擠壓,例如,what跟wath有相同的簽名,則放到一起,我們可以根據簽名的字典排序,但是,使用C#提供的Dictnary可以快速排序加壓,代碼如下:
1 public static Dictionary < string,List < string >> SqueezeList(string filePath) 2 { 3 Dictionary < string,List < string >> wordDict = new Dictionary < string,List < string >> (); 4 StreamReader fs = new StreamReader(filePath, Encoding.Default); 5 string line; 6 while (!string.IsNullOrEmpty(line = fs.ReadLine())) { 7 var key = SignWord(GetCleanString(line)); 8 if (wordDict.ContainsKey(key)) { 9 wordDict[key].Add(line); 10 } else { 11 var list = new List < string > () { 12 line 13 }; 14 wordDict.Add(key, list); 15 } 16 } 17 fs.Close(); 18 return wordDict; 19 }
這樣,我們就將單詞以簽名為Key,放入到 Dictionary 中;當然,實際應用中,我們會以將Dictionary序列化到文本文件中,這樣就不用每次使用時都算一次了;
PS:對於搜索方面的學習,都是看着論文什么的摸索前進,其中走了很多彎路和歪解,如果本文有什么紕漏或錯誤的地方,請指出和諒解,謝謝;