字串匹配的三個演算法(KMP+字典樹+AC自動機)
字串匹配的意思是給一個字串集合,和另一個字串集合,看這兩個集合交集是多少。
(1)若是都只有一個字串,那麼就看其中一個是否包含另外一個(一對一,KMP)
https://blog.csdn.net/fkyyly/article/details/48007965
(2)若是父串集合(比較長的,被當做模板)的有多個,子串(拿去匹配的)只有一個,就是問這個子串是否存在於父串之中(字典樹則是一對多的時候匹配常用演算法)
(3)若是子串父串集合都有多個,那麼就是問交集了(AC自動機就是多對多的匹配或者用HashMap)
1. 一對一KMP
2. 一對多Trie樹
Trie中文名又叫做字典樹,字首樹等,因為其結構獨有的特點,經常被用來統計,排序,和儲存大量的字串,經常見於搜尋提示,輸入法文字關聯等,當輸入一個值,可以自動搜尋出可能的選擇。當沒有完全匹配的結果時,可以返回字首最為相似的可能。
基本性質:
1.根節點不含有字元,其餘各節點有且只有一個字元。
2.根節點到某一節點中經過的節點儲存的值連線起來就是對應的字串。
3.每一個節點所有的子節點的值都不應該相同。
借用一下維基百科上的一張圖片:
//定義字首樹資料結構 class TrieNode { TrieNode[] son;// TrieNode[26] in this case 儲存直接的孩子節點 boolean isEnd;// end of a string. char val;// No this field mostly. TrieNode() { son = new TrieNode[26];//26 letters.CaseInsensitive.只是給了26這個長度,但是裡面具體有多少個元素表示有多少個son isEnd = false; } } public class One2Many { /** 字典樹的Java實現。實現了插入、查詢 */ TrieNode root = new TrieNode(); public void insert(String str) { if (str == null || str.length() == 0) { return ; } TrieNode node = root; char[] letters=str.toCharArray(); for (int i = 0, len = str.length(); i < len; i++) { int pos = letters[i] - 'a'; if (node.son[pos] == null) { node.son[pos] = new TrieNode(); node.son[pos].val = letters[i]; } node = node.son[pos]; } node.isEnd = true;//每個單詞的結束isEnd才為true,中間預設為flase } // search a word in the tree.Complete matching. public boolean has(String str) { if (str == null || str.length() == 0) { return false; } TrieNode node = root; char[] letters=str.toCharArray(); for (int i = 0, len = str.length(); i < len; i++) { int pos = letters[i] - 'a'; if (node.son[pos] != null) { node = node.son[pos]; } else { return false; } } return node.isEnd;//只有走到末尾才返回true,如果是字首也是false } //Trie樹的根節點用特殊字元(這樣下面就可以26個分支了) public static void main(String[] args) { One2Many tree = new One2Many(); //用strs構建Trie樹 String[] strs={ "banana", "band", "bee", "absolute", "acm", }; for(String str:strs){ tree.insert(str); } //判斷一個元素在不在tire中也就是一個元素在不在另外一個集合中 System.out.println(tree.has("band")); } }
https://blog.csdn.net/u010233260/article/details/45752777
http://bylijinnan.iteye.com/blog/1525203
3. 多對多AC自動機或者Map
給一個字典,再給一個m長的文字(m長的文本里麵包含很多的詞),問這個文本里出現了字典裡的哪些字。
3.1 方法一:使用HashMap複雜度是O(maxLengh(word)*length(str))這樣和字典的大小沒有關係
import java.util.ArrayList;
import java.util.HashMap;
/**
* 一對多,把多的那個存入hashmap中
* 應用場景最大正向匹配分詞
*/
public class Many2Many {
public void matchFun(String str){
String[] dic = {"請", "播放", "一首", "黎明", "的", "太陽", "黎明的太陽"};
HashMap<String, String> dicMap = new HashMap<>();
int maxLength = 0;
for (int i = 0; i < dic.length; i++) {
dicMap.put(dic[i], "1");
maxLength = dic[i].length();
}
String temp = "";
for (int length = maxLength; length > 0 ; length --) {
for (int i = 0; i < str.length() && i+length < str.length()+1; i++) {
temp = str.substring(i, i+length);
if (dicMap.get(temp) != null){
System.out.println(temp);
}
}
}
}
public static void main(String[] args) {
One2Many many2Many = new Many2Many();
Many2Many.matchFun("你好,請播放黎明的太陽");
}
}
3.2 方法二:AC自動機
如果用AC自動機來解決的話,效率將為線性O(length(str))時間複雜度。
AC自動機也運用了一點KMP演算法的思想。簡述為字典樹+KMP也未為不可。其實就是在trie樹上做KMP。AC自動機增加了失敗轉移,轉移到已經輸入成功的文字的字尾,來實現。
首先講一下acnode的結構:
與字典樹相比,就多了個*fail對吧,這個就相當於KMP演算法裡的next陣列。只不過它存的是失配後跳轉的位置,而不是跳轉之後再向前跳了多少罷了。
3.2.1.多模式匹配
多模式匹配就是有多個模式串P1,P2,P3...,Pm,求出所有這些模式串在連續文字T1....n中的所有可能出現的位置。
例如:求出模式集合{"nihao","hao","hs","hsr"}在給定文字"sdmfhsgnshejfgnihaofhsrnihao"中所有可能出現的位置。
3.2.2.Aho-Corasick演算法
使用Aho-Corasick演算法需要三步:
1.建立模式的Trie
2.給Trie新增失敗路徑
3.根據AC自動機,搜尋待處理的文字
下面說明這三步:
建立多模式集合的Trie樹
Trie樹也是一種自動機。對於多模式集合{"say","she","shr","he","her"},對應的Trie樹如下,其中紅色標記的圈是表示為接收態:
為多模式集合的Trie樹新增失敗路徑,建立AC自動機
AC自動機裡面比較難理解的應該是它的失配指標的計算過程。這個計算過程從本質上講就是進行一遍廣搜,於此同時維護fail指標,每一步的維護過程可用下圖表示。
構造失敗指標的過程概括起來就一句話:設這個節點上的字母為C,沿著他父親的失敗指標走,直到走到一個節點,他的兒子中也有字母為C的節點。然後把當前節點的失敗指標指向那個字母也為C的兒子。如果一直走到了root都沒找到,那就把失敗指標指向root。
使用廣度優先搜尋BFS,層次遍歷節點來處理,每一個節點的失敗路徑。
特殊處理:第二層要特殊處理,將這層中的節點的失敗路徑直接指向父節點(也就是根節點)。
https://blog.csdn.net/asdfghjkl1993/article/details/43371951