在軟件工程中,我們用到字符串匹配的地方非常多,比如:文本編輯軟件中的查找功能,判斷兩個字符串是否相等。字符串匹配分爲兩種情況:(1)字符串一對一的匹配,(2)在一個字符串中同時查找多個子串。
1.對於一對一的匹配,有經典的BF算法(Brute Force)暴力匹配算法:
核心思想:字符串匹配算法中有兩個核心詞:(1)基礎字符串(主串)(2)模式串
(例如:在字符串A中查找字符串B,那麼A就是主串,B就是模式串)
假如主串長度爲m,模式串長度爲n,根據模式串的長度,可以將主串分解成m-n+1個子串。然後拿着模式串與主串逐一進行匹配,時間複雜度爲O(m*n)
主串:abcbdef 子串:bcd
可以將主串分解爲:abc bcd cbd bde def
public class StringMatch {
public static void main(String[] args) {
String basic = "zhangsanlisi";
String pattern = "lisi";
StringMatch match = new StringMatch();
int i = match.bf(basic,pattern);
System.out.println("在基礎字符串中第一次出現的位置:"+i);
}
/*暴力匹配算法:
* 將基礎字符串按照模式串分解成a-b+1個,然後逐個與模式串進行匹配
* 時間複雜度爲O(n*m) n爲基礎字符串的長度,m爲模式串的長度*/
public int bf(String basic,String pattern) {
int a = basic.length();
int b = pattern.length();
int k;
char[] bas = basic.toCharArray();
char[] pat = pattern.toCharArray();
/*判斷參數是否有效*/
if(a == 0 || b == 0 || a-b<0) return -1;
/*當前for循環的意思是:基礎串與模式串比較的次數,一個基礎串可以分解成a-b+1個模式串*/
for(int i=0;i<=a-b;i++) {
k = 0;
/*拿模式串與當前分解的基礎串進行逐個字符的匹配,參數k用來記錄匹配到的字符串的個數*/
for(int j=0;j<b;j++) {
if(bas[i+j] == pat[j]) {
k++;
}else {
break;
}
}
/*如果k與模式串的長度b相等的話,那麼證明兩個字符串就是相等的*/
if(k == b) return i;
}
return -2;
}
}
在實際的軟件開發中,主串和模式串的長度可能相對比較短,並且在匹配的過程中,如果遇到不相等的字符,就會停止當前的匹配操作,最壞的情況下時間複雜度爲O(m*n),但是實際情況都比這個值低,並且暴力匹配算法思想簡單,容易實現,所以在條件允許的情況下是首選。
2.Trie樹(字典樹):
像是Google,百度這樣的搜索引擎,當我們在搜索欄中鍵入某一些文字的時候,它就會在下拉欄中自動提示出一些相應的關鍵詞。這種搜索引擎的自動提示功能就可以使用Trie樹來實現。
Trie數的本質就是利用字符串之間的公共前綴,將重複的前綴合並在一起,構成一個(字符串集合)一顆字典樹。
Trie樹的結構大致如下:
在Trie數中,根節點不包含任何信息,其它每一個節點都包含某一字符串中的一個字符。在Trie中有兩個重要的操作:
(1)通過字符串集合來構建一顆Trie樹:因爲構建Trie樹需要遍歷所有的字符串,所以時間複雜度爲O(n)
(2)在Trie中查詢某一個字符串:假設需要查詢的字符串長度爲k,那麼我們只需比對大約k個節點,所以查找的時間複雜度爲 O(k)。
3.Trie樹和散列表,紅黑樹的比較:
字符串的匹配問題,歸根結底就是數據的查找問題,對於動態高效數據操作的數據結構,其實也可以實現字符串的匹配功能。例如,散列表,紅黑樹,跳錶等。他們各自的優缺點如下:
利用Trie樹進行字符串匹配,其實對數據的要求比較苛刻:
(1)首先字符集不能太大,因爲Trie樹在存儲數據時,不僅要存儲數據本身,還要存儲指針的引用,所以會浪費更多的空間。
(2)同時需要字符串前綴重合的比較多,不然同樣會消耗更多的空間。
(3)Tried樹中的數據是通過指針串起來的,所以在內存中的分佈是不連續的,對CPU緩存不友好。
(4)如果要使用Trie樹來實現字符串匹配,需要從0到1來構建,增加了工程的複雜度。
實際上,Tried樹只是不適合精確查找,如果進行精確查找,那麼可以使用其它動態操作數據的容器。Trie樹適合匹配查找這種應用場景