字串近似搜尋

2022-02-23 15:07:39 字數 3850 閱讀 8535

**:.net.newlife。

需求:假設在某系統儲存了許多位址,例如:「北京市海淀區中關村大街1號海龍大廈」。使用者輸入「北京 海龍大廈」即可查詢到這條結果。另外還需要有容錯設計,例如輸入「廣西 京島風景區」能夠搜尋到"廣西壯族自治區京島風景名勝區"。最終的需求是:可以根據使用者輸入,匹配若干條近似結果共使用者選擇

目的:避免使用者輸入類似位址導致資料出現重複項。例如,已經存在「北京市中關村」,就不應該再允許存在「北京中關村」。

舉例

此類技術在搜尋引擎中早已廣泛使用,例如「查詢**」功能。

要實現此演算法,首先需要明確「字串近似」的概念。

計算字串相似度通常使用的是動態規劃(dp)演算法。

常用的演算法是 levenshtein distance。用這個演算法可以直接計算出兩個字串的「編輯距離」。所謂編輯距離,是指乙個字串,每次只能通過插入乙個字元、刪除乙個字元或者修改乙個字元的方法,變成另外乙個字串的最少操作次數。這就引出了第一種方法:計算兩個字串之間的編輯距離。稍加思考之後發現,不能用輸入的關鍵字直接與句子做匹配。你必須從句子中選取合適的長度後再做匹配。把結果按照距離公升序排序。

using system;

using system.collections.generic;

using system.linq;

using system.text;

namespace beststring

, stringsplitoptions.removeemptyentries);

foreach (string word in words)

);})

orderby str

select str;

datas = q.toarray();

}return datas;

}static

int distance(string str1, string str2)

return c[n, m];}}

}

分析這個方法後發現,每次對乙個句子進行相關度比較的時候,都要把把句子從頭到尾掃瞄一次,每次掃瞄還需要以最大誤差作長度控制。這樣一來,對每個句子的計算次數大大增加。達到了二次方的規模(忽略距離計算時間)。

所以我們需要更高效的計算策略。在紙上寫出乙個句子,再寫出幾個關鍵字。乙個乙個塗畫之後,偶然發現另一種字串相關的演算法完全可以適用。那就是 longest common subsequence(lcs,最長公共字串)。為什麼這個演算法可以用來計算兩個字串的相關度?先看乙個例子:

關鍵字:     少年時代的神話

播下了浪漫注意

句子:   就是少年時代

大量神話傳說在其心田裡播下了浪漫主義這顆難以磨滅的種子

這裡用了兩個關鍵字進行搜尋。可以看出來兩個關鍵字都有部分匹配了句子中的若干部分。這樣可以單獨為兩個關鍵字計算 lcs,lcs之和就是簡單的相關度。看到這裡,你若是已經理解了核心思想,已經可以實現出基本框架了。但是,請看下面這個例子:

關鍵字:      東土大唐       唐三藏

句子:  我本是東土大唐欽差御弟唐三藏大徒弟孫悟空行者

看出來問題了嗎?下面還是使用同樣的關鍵字和句子。

關鍵字:     東土大         (唐唐)三藏

句子: 我本是東土大唐欽差御弟唐

三藏大徒弟孫悟空行者

舉這個例子為了說明,在進行 lcs 計算的過程中,得到的結果並不能保證就是我們期望的結果。為了①保證所匹配的結果中不存在交集,並且②在句子中的匹配結果盡可能的短,需要採取兩個補救措施。(為什麼需要滿足這樣的條件,讀者自行思考)

第一:可以在單次計算 lcs 之後,用貪心策略向前(向後)找到最先能夠完成匹配的位置,再用相同的策略向後(向前)掃瞄。這樣可以滿足第二個條件找到句子中最短的匹配。如果你對 lcs 演算法有深入了解,完全可以在計算 lcs 的過程中找到最短匹配的結束位置,然後只需要進行一次向前掃瞄就可以完成。這樣節約了一次掃瞄過程。

第二:增加乙個標記陣列,記錄句子中的字元是否被匹配過。

最後標記陣列中標記過的位置就是匹配結果。

相信你看到這裡一定非常頭暈,下面用乙個例子解釋:(句子)

關鍵字:   abcd

句子:     xay

abzcb

xcdd

yz句子分解: x y  z  x   yz

a   b c   d

a   b c d

你可能會匹配成 ayabzcbxcdd,ayabzcbxcd,abzcbxcdd,abzcbxcd。我們實際需要的只是abzcbxcd。

使用lcs匹配之後,得到的很可能是 xayabzcbxcddyz;

用貪心策略向前處理後,得到結果為 xayabzcbxcddyz;

用貪心策略向後處理後,得到結果為 xayabzcbxcddyz。

這樣處理的目的是為了避免得到較長的匹配結果(類似正規表示式的貪婪、懶惰模式)。

以上只是描述了怎麼計算兩個字串的相似程度。除此之外還需要:①剔除相似度較低的結果;②對結果進行排序。

剔除相似度較低的結果,這裡設定了乙個閾值:差錯比例不能超過匹配結果長度的一半。

對結果進行排序,不能夠直接使用相似度進行排序。因為相似度並沒有考慮到句子的長度。按照使用習慣,通常會把匹配度高,並且句子長度短的放在前面。這就得到了排序因子:(不匹配度+0.5)/句子長度。

最後得到我們最終的搜尋方法

using system;

using system.collections.generic;

using system.linq;

using system.text;

using system.diagnostics;

namespace beststring

, stringsplitoptions.removeemptyentries)

.orderby(s => s.length)

.toarray();

var q = from sentence in items.asparallel()

let mll = mul_lncs_length(sentence, words)

where mll >= 0

orderby (mll + 0.5) / sentence.length, sentence

select sentence;

return q.toarray();}//

static int[,] c = new int[100, 100];

//////

//////

///多個關鍵字。長度必須大於0,必須按照字串長度公升序排列。

///static

int mul_lncs_length(string sentence, string words)

}else

c[i + 1, j + 1] = math.max(c[i, j + 1], c[i + 1, j]);

lcs_l = c[i, j];

if (lcs_l <= wlength >> 1)

return -1;

while (i > 0 && j > 0)

first = i;

}else

if (c[i - 1, j] == c[i, j])

i--;

else

//if (c[i, j - 1] == c[i, j])

j--;

}if (lcs_l <= (last - first + 1) >> 1)

return -1;

}return result;}}

}

NGram近似字串匹配

介紹ngram是來自給定序列的n個單位的子串行。這些單位可以是單詞,字元等。例如,短語 hello world 的2 gram或bigram是 he el ll lo o w wo or rl 和 ld 用途ngram建模通常用於對自然語言進行建模。它還可用於 序列中的下乙個專案。ie,給定具有發生...

字串搜尋

include include include includeusing namespace std char grid 100 100 word 100 int n m int x int y int xx yy void search if k len int main int t cin t ...

字串搜尋

include include include includeusing namespace std char grid 100 100 word 100 int n m int x int y int xx yy void search if k len int main int t cin t ...