字符串的模式匹配
字串的定位操作通常稱做模式匹配,是各種串處理系統中最重要的操作之一。本文主要介紹兩種常用的實現算法:
1、暴力匹配
2、KMP算法
1.暴力匹配
時間復雜度為O(n*m);n為主串長度,m為模式串長度
算法的基本思想:
從主串的起始位置(或指定位置)開始與模式串的第一個字符比較,若相等,則繼續逐個比較后續字符;否則從主串的下一個字符再重新和模式串的字符比較。依次類推,直到模式串成功匹配,返回主串中第一次出現模式串字符的位置,或者模式串匹配不成功,這里約定返回-1;
//偽代碼 int bruteForceStringMatch(String source, String pattern) { i = 0; j = 0; while(i < slen && j < plen) { if(s[i] == p[j]) ++i; ++j; else i = i - (j -1); //回溯上次匹配起始位置的后一位 j = 0; } if(j == plen) return i - j; //匹配成功 else return -1; //匹配失敗 }
實現代碼:

1 public static int bruteForceStringMatch(String source, String pattern) 2 { 3 int slen = source.length(); 4 int plen = pattern.length(); 5 char[] s = source.toCharArray(); 6 char[] p = pattern.toCharArray(); 7 int i = 0; 8 int j = 0; 9 10 if(slen < plen) 11 return -1; //如果主串長度小於模式串,直接返回-1,匹配失敗 12 else 13 { 14 while(i < slen && j < plen) 15 { 16 if(s[i] == p[j]) //如果i,j位置上的字符匹配成功就繼續向后匹配 17 { 18 ++i; 19 ++j; 20 } 21 else 22 { 23 i = i - (j -1); //i回溯到主串上一次開始匹配下一個位置的地方 24 j = 0; //j重置,模式串從開始再次進行匹配 25 } 26 } 27 if(j == plen) //匹配成功 28 return i - j; 29 else 30 return -1; //匹配失敗 31 } 32 }
2.KMP算法
KMP算法是D.E.Knuth、V.R.Pratt和J.H.Morris同時發現,所以命名為KMP算法。
此算法可以在O(n+m)的時間數量級上完成串的模式匹配。
主要就是改進了暴力匹配中i回溯的操作,KMP算法中當一趟匹配過程中出現字符比較不等時,不直接回溯i,而是利用已經得到的“部分匹配”的結果將模式串向右移動(j-next[k])的距離。稍后我們將詳細解釋next[k]的計算過程。
//偽代碼 int kmpStringMatch(String source, String pattern) { i = 0; j = 1; while(i < slen && j < plen) { if(j == 0 || s[i] == p[j]) ++i; ++j; else j = next[j]; } if(j == plen) return i - j; else return -1; }
實現代碼:

1 public static int kmpStringMatch(String source, String pattern) 2 { 3 int i = 0; 4 int j = 0; 5 char[] s = source.toCharArray(); 6 char[] p = pattern.toCharArray(); 7 int slen = s.length; 8 int plen = p.length; 9 int[] next = getNext(p); 10 while(i < slen && j < plen) 11 { 12 if(j == -1 || s[i] == p[j]) 13 { 14 ++i; 15 ++j; 16 } 17 else 18 { 19 //如果j != -1且當前字符匹配失敗,則令i不變, 20 //j = next[j],即讓pattern模式串右移j - next[j]個單位 21 j = next[j]; 22 } 23 } 24 if(j == plen) 25 return i - j; 26 else 27 return -1; 28 }
那么問題來了,next[k]是怎么計算出來的呢?
關於next[k]數組的計算引出的兩種辦法,一種是遞歸,一種對遞歸優化,第一種對應的就是KMP算法,第二種就是優化的KMP算法。
next函數值僅取決於模式串本身而和主串無關。
有很多講next函數值計算辦法的資料,在此我想用一種直觀的比較容易理解的辦法來表達。
舉個栗子:現在有一個模式串abab
模式串的各個字串 | 前綴 | 后綴 | 最大公共元素長度 |
a | null | null | 0 |
ab | a | b | 0 |
aba | a,ab | a,ba | 1 |
abab | a,ab,aba | b,ab,bab | 2 |
next函數值的實現:

private static int[] getNext(char[] p) { /** * 已知next[j] = k, 利用遞歸的思想求出next[j+1]的值 * 1.如果p[j] = p[k],則next[j+1] = next[k] + 1; * 2.如果p[j] != p[k],則令k = next[k],如果此時p[j] == p[k],則next[j+1] = k+1 * 如果不相等,則繼續遞歸前綴索引,令k=next[k],繼續判斷,直至k=-1(即k=next[0])或者p[j]=p[k]為止 */ int plen = p.length; int[] next = new int[plen]; int k = -1; int j = 0; next[0] = -1; //這里采用-1做標識 while(j < plen -1) { if(k == -1 || p[j] == p[k]) { ++k; ++j; next[j] = k; } else { k = next[k]; } } return next; }
國際慣例貼上源代碼:

import java.util.Scanner; public class PatternString { public static int bruteForceStringMatch(String source, String pattern) { int slen = source.length(); int plen = pattern.length(); char[] s = source.toCharArray(); char[] p = pattern.toCharArray(); int i = 0; int j = 0; if(slen < plen) return -1; //如果主串長度小於模式串,直接返回-1,匹配失敗 else { while(i < slen && j < plen) { if(s[i] == p[j]) //如果i,j位置上的字符匹配成功就繼續向后匹配 { ++i; ++j; } else { i = i - (j -1); //i回溯到主串上一次開始匹配下一個位置的地方 j = 0; //j重置,模式串從開始再次進行匹配 } } if(j == plen) //匹配成功 return i - j; else return -1; //匹配失敗 } } public static int kmpStringMatch(String source, String pattern) { int i = 0; int j = 0; char[] s = source.toCharArray(); char[] p = pattern.toCharArray(); int slen = s.length; int plen = p.length; int[] next = getNext(p); while(i < slen && j < plen) { if(j == -1 || s[i] == p[j]) { ++i; ++j; } else { //如果j != -1且當前字符匹配失敗,則令i不變, //j = next[j],即讓pattern模式串右移j - next[j]個單位 j = next[j]; } } if(j == plen) return i - j; else return -1; } private static int[] getNext(char[] p) { /** * 已知next[j] = k, 利用遞歸的思想求出next[j+1]的值 * 1.如果p[j] = p[k],則next[j+1] = next[k] + 1; * 2.如果p[j] != p[k],則令k = next[k],如果此時p[j] == p[k],則next[j+1] = k+1 * 如果不相等,則繼續遞歸前綴索引,令k=next[k],繼續判斷,直至k=-1(即k=next[0])或者p[j]=p[k]為止 */ int plen = p.length; int[] next = new int[plen]; int k = -1; int j = 0; next[0] = -1; //這里采用-1做標識 while(j < plen -1) { if(k == -1 || p[j] == p[k]) { ++k; ++j; next[j] = k; } else { k = next[k]; } } System.out.println("next函數值:"); for(int ii = 0;ii<next.length;ii++) { System.out.print(next[ii]+ " "); } System.out.println(); return next; } public static void main(String[] args) { Scanner sc = new Scanner(System.in); String a = sc.nextLine(); String b = sc.nextLine(); System.out.println(bruteForceStringMatch(a, b)); System.out.println(kmpStringMatch(a, b)); } }