数据结构之字符串模式匹配

时间:2024-10-10 07:41:23

程序源代码:点击打开链接

1.引入

    字符串模式匹配。首先我们引入目标串,模式串的概念,而字符串模式匹配就是查找模式串在目标串中的位置。

-Force算法

    brute-Force算法,我的理解是这样的。首先设目标串target="t0t1t2t3t4",pattern="p0p1p2"。将p0与t0比较,若相同,则继续比较p1与t1。若不同,则从t1与p0开始比较。下面我们举个例子:

    target="aababcd",pattern="abcd";用target[i],pattern[j]分别表示target与pattern对应位置的字符(i,j初始为0)。

    首先target[i]的a与pattern[j]的a相同,于是i++,j++。此时target[i]的a与pattern[j]的b不同,于是i=i-j+1,j=0。比较target[i]的a与pattern[j]的a相同,于是i++,j++。此时target[i]的b与pattern[j]的b相同,于是i++,j++。此时target[i]的a与pattern[j]的c不相同,于是i=i-j+1,j=0。此时target[i]的b与pattern[j]的a不相同,于是i++,j=0。此时target[i]的a与pattern[j]的a相同,于是i++,j++。此时,target[i]的b与pattern[j]的b相同,于是i++,j++。此时target[i]的c与pattern[j]的c相同,于是i++,j++。此时target[i]的d与pattern的d相同,于是i++,j++。测试j==4,表示在目标串target中找到了模式串pattern,于是返回模式串在目标串中的位置3。

    下面是Brute-Force算法描述:

    设目标串中的字符为ti(0<=i<n),模式串中的字符为pj(0<=0<m,m<=n),将他们比较:

(1)若ti=pj,继续比较ti+1与pj+1,直到j=m,则“ti-m+1……ti”与“p0……pm-1”匹配成功,返回模式串在目标串中匹配子串序号i-m+1。

(2)若ti!=pj,表示ti-jtiti-j+m-1”与“p0…pjpm-1”匹配失败;目标串下一个匹配子串是“ti-j+1…ti-j+m”,继续比较ti-j+1与p0。此时,目标串回溯,从ti退回ti-j+1。

   程序流程图如下:


    代码如下:

  1. /**
  2. *
  3. * @author 冯利
  4. * @version 创建时间:2018年5月18日 上午9:51:20
  5. */
  6. public class MyStringMatch {
  7. /**
  8. * 从目标串的begin开始,查找模式串pattern在目标串中的位置
  9. *
  10. * @auther 冯利
  11. * @version 创建时间:2018年5月18日 上午9:51:08
  12. * @param pattern
  13. * @param begin
  14. * @return
  15. */
  16. public static int indexOf(String target, String pattern, int begin) {
  17. // i为目标串target的索引
  18. int i = begin;
  19. // j为模式串pattern的索引
  20. int j = 0;
  21. // 输入要求
  22. if (target == null || ("") || pattern == null || ("") || begin > ()) {
  23. return -1;
  24. }
  25. // 循环,直到target或pattern被遍历完
  26. do {
  27. if ((i) == (j)) {
  28. i++;
  29. j++;
  30. } else {
  31. i = i - j + 1;
  32. j = 0;
  33. }
  34. } while (i < () && j < ());
  35. // 模式串是否存在于目标串
  36. if (j == ()) {
  37. return i - () ;
  38. }
  39. return -1;
  40. }
  41. }   

    测试代码如下:

  1. public static void main(String[] args)
  2. {
  3. (("aababcd", "cd", 0));
  4. }

运行结果如下:





算法

    由于暴力匹配算法目标串存在回溯,有缺陷,因此引入了Kmp这种不存在回溯的算法。而这个算法的核心是找出模式串中相同的最长前缀子串和后缀子串的长度k。Kmp算法如下:


    KMP算法与brute-Force算法的区别,在于当ti!=pj时,brute-Force算法要回溯,也就是i=i-j+1,j=0;而KMP算法将从ti与pk进行比较。所以求k是关键,接下来我们介绍求k的算法。

    首先,我们需要一个数组next来存模式串中每个字符(假设匹配时在这里出错)的k。举例:模式串“abcabc”的next数组


    KMP算法充分利用前一次的比较结果,由next[j]逐个递推计算得到next[j+1]。下面是一些说明:

(1)约定next[0]=-1,-1表示下次匹配从tj+1与p0开始比较;约定next[1]=0,表示下次匹配从tj与p0开始比较。

(2)对模式串当前字符序号j(0<=j<m),设next[j]=k,表示在“p0……pj-1”串中存在长度为k的相同的前缀子串和后缀子串,即“p0……pk-1”="pj-k……pj-1",0<=k<j且k取最大值。

(3)对next[j+1]而言,求“p0……pj-1pj”中相同前后子串的长度k,需要比较前缀子串“p0……pk-1”与后缀子串“pj-k+1……pj”是否匹配。

    说明说完了,计算next数组的真正方法:假设target与pattern在ti于pj+1处匹配失败(即ti!=pj+1),并且已知“p0……pk-1”=“pj-k……pj-1”(也就是next[j]=k),此时只需要比较pk与pj是否相同。

    1.如果pk=pj,或k=-1时则next[j+1]=k+1=next[j]+1.

    2.如果pk!=pj,则在“p0……pj”串中寻找较短的相同前后缀子串(即令k=next[k],再比较pj与pk)。

    下面举个例子,求模式串“abcabdabcabcaa”的next数组

    当在j=12时匹配失败,则比较p11与p5(k=next[11]=5)。此时p11!=p5,于是k=next[5]=2。此时比较p11与p2,相等,于是next[j+1]=2+1=3。

    求next的流程图如下:


    至此,kmp的求next算法已经有了。这儿有一个改进了的求next数组的方法。下面介绍。

    当ti!=pj时,next[j]=k,若pk=pj,可知ti!=pk,则下次模式匹配串从pnext[k]开始比较。显然next[k]<next[j]越小,next[j]越小,模式串右移的距离越远,比较的次数也越少。下面举两个例子。

    模式串“abcabc”的next数组


    模式串“abcabdabcabcaa”的next数组


    改进了的求next流程图如下:



kmp代码如下:

  1. /**
  2. * kmp模式匹配算法
  3. *
  4. * @auther 冯利
  5. * @version 创建时间:2018年5月18日 下午9:38:43
  6. * @param target
  7. * 目标串
  8. * @param pattern
  9. * 模式串
  10. * @param begin
  11. * 从目标串中起始匹配位置
  12. * @return -1:表示匹配失败;其他表示pattern在target中的位置
  13. */
  14. public static int indexOf_KMP(String target, String pattern, int begin) {
  15. //未改进的next求法
  16. //int next[] = getNextW(pattern);
  17. //改进了的next求法
  18. int next[]=getNext(pattern);
  19. // i为目标串target的索引
  20. int i = begin;
  21. // j为模式串pattern的索引
  22. int j = 0;
  23. // 输入要求
  24. if (target == null || ("") || pattern == null || ("") || begin > ()) {
  25. return -1;
  26. }
  27. // 循环,直到target或pattern被遍历完
  28. do {
  29. if ((i) == (j)) {
  30. i++;
  31. j++;
  32. } else {
  33. j = next[j];
  34. if (j == -1) {
  35. i++;
  36. j++;
  37. }
  38. }
  39. } while (i < () && j < ());
  40. // 模式串是否存在于目标串
  41. if (j == ()) {
  42. return i - ();
  43. }
  44. return -1;
  45. }
  46. /**
  47. * 未改进的求next方法
  48. * @auther 冯利
  49. * @version 创建时间:2018年5月18日 下午9:45:59
  50. * @param pattern
  51. * 模式串
  52. * @return
  53. */
  54. private static int[] getNextW(String pattern) {
  55. int next[] = new int[()];
  56. next[0] = -1;
  57. int j = 1;
  58. while (j < ()) {
  59. int k = next[j - 1];
  60. while (true) {
  61. if (k == -1 || (j-1) == (k)) {
  62. next[j] = k + 1;
  63. break;
  64. } else {
  65. k = next[k];
  66. }
  67. }
  68. j++;
  69. }
  70. return next;
  71. }
  72. /**
  73. * 改进了的求next方法
  74. * @auther 冯利
  75. * @version 创建时间:2018年5月18日 下午9:45:59
  76. * @param pattern
  77. * 模式串
  78. * @return
  79. */
  80. private static int[] getNext(String pattern) {
  81. int next[] = new int[()];
  82. next[0] = -1;
  83. int j = 1;
  84. while (j < ()) {
  85. int k = next[j - 1];
  86. while (true) {
  87. if (k == -1 || (j-1) == (k)) {
  88. next[j] = k + 1;
  89. break;
  90. } else {
  91. k = next[k];
  92. }
  93. }
  94. if((j)==(next[j]))
  95. {
  96. next[j]=next[next[j]];
  97. }
  98. j++;
  99. }
  100. return next;
  101. }

测试代码:

  1. /**
  2. *
  3. * @author 冯利
  4. * @version 创建时间:2018年5月18日 上午10:01:20
  5. */
  6. public class Main {
  7. public static void main(String[] args)
  8. {
  9. (MyStringMatch.indexOf_Force("aababcd", "cd", 0));
  10. (MyStringMatch.indexOf_Force("abcdabcabbabcabc", "abcabc", 0));
  11. (MyStringMatch.indexOf_KMP("aababcd", "cd", 0));
  12. (MyStringMatch.indexOf_KMP("abcdabcabbabcabc", "abcabc", 0));
  13. }
  14. }

运行结果:


程序源码:点击打开链接