BM算法,全稱是Boyer-Moore算法,1977年,德克薩斯大學的Robert S. Boyer教授和J Strother Moore教授發明了一種新的字符串匹配算法。
BM算法定義了兩個規則:
1、壞字符規則:當文本串中的某個字符跟模式串的某個字符不匹配時,我們稱文本串中的這個失配字符為壞字符,此時模式串需要向右移動,移動的位數 = 壞字符在模式串中的位置 - 壞字符在模式串中最右出現的位置。此外,如果"壞字符"不包含在模式串之中,則最右出現位置為-1。
2、好后綴規則:當字符失配時,后移位數 = 好后綴在模式串中的位置 - 好后綴在模式串上一次出現的位置,且如果好后綴在模式串中沒有再次出現,則為-1。
關於壞字符規則和好后綴規則的具體講解,以及怎么移動,可以查看阮一峰老師的詳細講解:http://www.ruanyifeng.com/blog/2013/05/boyer-moore_string_search_algorithm.html
這里根據講解畫了兩張圖,方便自己理解壞字符規則:
2019年11月14日15:38:41 修改
具體代碼如下:
1 private static final int SIZE = 256; // 全局變量或者是局部變量 2 3 /** 4 * 壞字符規則哈希表構建方法 5 * 6 * @param b 7 * 模式串 8 * @param m 9 * 模式串的長度 10 * @param bc 11 * 散列表 12 */ 13 private void generateBC(char[] b, int m, int[] bc) { 14 for (int i = 0; i < SIZE; ++i) { 15 bc[i] = -1; // 初始化bc 16 } 17 18 for (int i = 0; i < m; ++i) { 19 int ascii = (int) b[i]; // 計算b[i]的ASCII值 20 bc[ascii] = i; 21 } 22 } 23 24 /** 25 * 好后綴規則構建哈希表 26 * 27 * @param b 28 * 模式串 29 * @param m 30 * 模式串長度 31 * @param suffix 32 * suffix數組的下標 k,表示后綴子串的長度, 33 * 下標對應的數組值存儲的是,在模式串中跟好后綴{u}相匹配的子串{u*}的起始下標值 34 * @param prefix 35 * 記錄模式串的后綴子串是否能匹配模式串的前綴子串 36 */ 37 private void generateGS(char[] b, int m, int[] suffix, boolean[] prefix) { 38 for (int i = 0; i < m; ++i) { // 初始化 39 suffix[i] = -1; 40 prefix[i] = false; 41 } 42 43 for (int i = 0; i < m - 1; ++i) { 44 int j = i; 45 int k = 0; // 公共后綴子串長度 46 while (j >= 0 && b[j] == b[m - 1 - k]) { // 與b[0, m-1]求公共后綴子串 47 --j; 48 ++k; 49 suffix[k] = j + 1; // j+1表示公共后綴在b[0,i]中的起始下標 50 } 51 if (j == -1) { 52 prefix[k] = true; // 如果公共后綴子串也是模式串的后綴子串 53 } 54 } 55 } 56 57 /** 58 * 完整的BM算法 好后綴+壞字符 59 * 60 * @param a 61 * 主串 62 * @param n 63 * 主串的長度 64 * @param b 65 * 模式串 66 * @param m 67 * 模式串的長度 68 * @return 69 */ 70 public int bm(char[] a, int n, char[] b, int m) { 71 int[] bc = new int[SIZE]; 72 generateBC(b, m, bc); // 構建壞字符哈希表 73 int[] suffix = new int[m]; 74 boolean[] prefix = new boolean[m]; 75 generateGS(b, m, suffix, prefix); // 構建好字符哈希表 76 int i = 0; // j 表示主串與模式串匹配的第一個字符 77 while (i < n - m) { 78 int j = 0; 79 for (j = m - 1; j >= 0; --j) {// 模式串從后向前匹配 80 if (a[i + j] != b[j]) { 81 break; // 壞字符串 82 } 83 } 84 if (j < 0) { 85 return i;// 匹配成功,返回主串和模式串第一個匹配字符的位置 86 } 87 int x = j - bc[(int) a[i + j]]; 88 int y = 0; 89 if (j < m - 1) { // 如果有好后綴的話 90 y = moveByGS(j, m, suffix, prefix); 91 } 92 i = i + Math.max(x, y); 93 } 94 return -1; 95 } 96 97 private int moveByGS(int j, int m, int[] suffix, boolean[] prefix) { 98 int k = m - 1 - j; // 好后綴的長度 99 if (suffix[k] != -1) { 100 return j - suffix[k] + 1; 101 } 102 for (int r = j + 2; r <= m - 1; ++r) { 103 if (prefix[m - r] == true) { 104 return r; 105 } 106 } 107 return m; 108 }
現在再看BM算法,原來之前自己是一點也沒弄懂!只是當做文章簡單讀了一遍,阿西吧!
首先,那個壞字符的散列表的構建就沒有弄懂:
1、為什么在散列表數組中要初始化每一個值為-1?
這里是在壞字符匹配的時候,如果主串與模式串中字符沒有匹配上(把壞字符在模式串中下標記做xi),此時的xi=-1
2、你有沒有考慮過模式串中的相同的字符的ASCII碼是相同的,那樣循環處理的話,只是記錄模式串中相同字符中最后面的那個字符的下標,沒有問題嗎?
這個是沒有問題的!這里無非有兩種情況,就是壞字符與非壞字符:
壞字符:因為是從后向前倒序匹配,只需要知道后面的字符下標,就可以計算出移動距離
非壞字符:需要去尋找壞字符或者使用好后綴規則
2019年11月14日15:34:37 修改
此大部分內容來自極客時間專欄,王爭老師的《數據結構與算法之美》