Java中的KMP算法是一种字符串匹配算法,其基本思想是利用已知信息跳过不必要的比较。
在KMP算法中,主要有两个步骤:预处理和匹配。
在预处理步骤中,我们需要创建一个前缀数组next,用来记录在当前字符之前的子串中有多大长度的相同前缀后缀,以便在匹配过程中跳过一些不必要的比较。
next
具体的实现方式如下:
public static int[] getNext(String pattern) { int n = pattern.length(); int[] next = new int[n]; next[0] = -1; int i = 0, j = -1; while (i < n - 1) { if (j == -1 || pattern.charAt(i) == pattern.charAt(j)) { i++; j++; next[i] = j; } else { j = next[j]; } } return next; }
在上面的代码中,我们使用两个指针i和j来遍历字符串pattern,其中i表示后缀的末尾,j表示前缀的末尾。初始时,i和j都是0。当i和j指向的字符相等时,我们将i和j都加1,并将next[i]设置为j。如果i和j指向的字符不相等,则我们将j更新为next[j],继续与i指向的字符进行比较。
在执行完上述步骤之后,我们得到了一个前缀数组next,它记录了在当前字符之前的子串中有多大长度的相同前缀后缀。这将在匹配步骤中帮助我们跳过不必要的比较。
在匹配步骤中,我们使用两个指针i和j来遍历字符串text和pattern,其中i表示text的当前位置,j表示pattern的当前位置。初始时,i和j都是0。
public static int kmp(String text, String pattern) { int m = text.length(), n = pattern.length(); int[] next = getNext(pattern); int i = 0, j = 0; while (i < m && j < n) { if (j == -1 || text.charAt(i) == pattern.charAt(j)) { i++; j++; } else { j = next[j]; } } if (j == n) { return i - j; } return -1; }
在上面的代码中,我们首先调用getNext方法来获取前缀数组next,然后使用两个指针i和j来遍历字符串text和pattern。如果text.charAt(i)等于pattern.charAt(j),则i和j都加1,继续比较下一个字符。如果text.charAt(i)不等于pattern.charAt(j),则将j更新为next[j],继续与text.charAt(i)进行比较。
getNext
在匹配过程中,如果j等于n,说明pattern已经完全匹配text,返回i-j即可。如果匹配失败,则返回-1。
KMP算法的时间复杂度为O(m+n),其中m是text的长度,n是pattern的长度。在预处理步骤中,我们需要遍历整个pattern,并为每个字符计算前缀数组next。在匹配步骤中,我们需要遍历整个text,并使用next数组跳过一些不必要的比较。
KMP算法的优点在于可以利用已知信息跳过不必要的比较,因此其时间复杂度比朴素的字符串匹配算法要低。但是,KMP算法需要使用额外的空间来存储前缀数组next,因此其空间复杂度也相应较高。
下面是一个使用KMP算法实现字符串匹配的例子:
public class KMP { public static int kmp(String text, String pattern) { int m = text.length(), n = pattern.length(); int[] next = getNext(pattern); int i = 0, j = 0; while (i < m && j < n) { if (j == -1 || text.charAt(i) == pattern.charAt(j)) { i++; j++; } else { j = next[j]; } } if (j == n) { return i - j; } return -1; } public static int[] getNext(String pattern) { int n = pattern.length(); int[] next = new int[n]; next[0] = -1; int i = 0, j = -1; while (i < n - 1) { if (j == -1 || pattern.charAt(i) == pattern.charAt(j)) { i++; j++; next[i] = j; } else { j = next[j]; } } return next; } public static void main(String[] args) { String text = "hello world"; String pattern = "world"; int index = kmp(text, pattern); if (index == -1) { System.out.println("Pattern not found"); } else { System.out.println("Pattern found at index " + index); } } }
在上面的例子中,我们首先定义了一个KMP类,其中包含了kmp方法和getNext方法。在main方法中,我们使用kmp方法来查找字符串text中是否包含字符串pattern,并输出结果。
以下是对KMP算法的补充说明和示例代码:
KMP算法的核心思想是通过利用已匹配的信息,跳过不必要的比较,从而提高字符串匹配的效率。它利用前缀函数(也称为部分匹配表)来确定在匹配过程中出现不匹配时,应该将模式串的指针向前移动多少位,而不是直接回溯到模式串的起始位置。
补充说明:
KMP算法的前缀函数(部分匹配表)的构建是关键步骤之一。下面是对getNext方法的具体说明:
next[0]
i
j
i=0
j=-1
j==-1
i++
j++
next[i]
j=next[j]
next[j]
下面是一个示例代码,演示了如何使用KMP算法进行字符串匹配:
public class KMPAlgorithm { public static int kmp(String text, String pattern) { int m = text.length(); int n = pattern.length(); int[] next = getNext(pattern); int i = 0; // 指向text的指针 int j = 0; // 指向pattern的指针 while (i < m && j < n) { if (j == -1 || text.charAt(i) == pattern.charAt(j)) { i++; j++; } else { j = next[j]; } } if (j == n) { return i - j; // 返回匹配的起始位置 } return -1; // 匹配失败 } private static int[] getNext(String pattern) { int n = pattern.length(); int[] next = new int[n]; next[0] = -1; int i = 0; int j = -1; while (i < n - 1) { if (j == -1 || pattern.charAt(i) == pattern.charAt(j)) { i++; j++; next[i] = j; } else { j = next[j]; } } return next; } public static void main(String[] args) { String text = "ABABDABACDABABCABAB"; String pattern = "ABABCABAB"; int index = kmp(text, pattern); if (index == -1) { System.out.println("Pattern not found"); } else { System.out.println("Pattern found at index " + index); } } }
在上面的示例中,我们使用KMPAlgorithm类来演示KMP算法的使用。在main方法中,我们定义了一个文本字符串text和一个模式字符串pattern,然后调用kmp方法来查找模式字符串在文本中的位置。如果找到了匹配的位置,我们输出其起始索引;否则,输出"Pattern not found"。
text
pattern
运行上述示例代码,输出结果为:
Pattern found at index 10
这表明模式字符串"ABABCABAB"在文本字符串"ABABDABACDABABCABAB"中的起始位置是索引10。
通过KMP算法,我们可以高效地进行字符串匹配,尤其适用于处理大文本和复杂模式的情况。它的时间复杂度为O(m+n),其中m是文本的长度,n是模式的长度。相较于朴素的字符串匹配算法,KMP算法通过利用已知信息跳过不必要的比较,提高了匹配效率。
当使用KMP算法进行字符串匹配时,有几点需要注意:
下面是一个扩展示例,展示如何查找所有匹配位置的代码:
public class KMPAlgorithm { public static List<Integer> kmp(String text, String pattern) { List<Integer> matches = new ArrayList<>(); int m = text.length(); int n = pattern.length(); int[] next = getNext(pattern); int i = 0; // 指向text的指针 int j = 0; // 指向pattern的指针 while (i < m) { if (j == -1 || text.charAt(i) == pattern.charAt(j)) { i++; j++; } else { j = next[j]; } if (j == n) { matches.add(i - j); j = next[j]; } } return matches; } private static int[] getNext(String pattern) { int n = pattern.length(); int[] next = new int[n]; next[0] = -1; int i = 0; int j = -1; while (i < n - 1) { if (j == -1 || pattern.charAt(i) == pattern.charAt(j)) { i++; j++; next[i] = j; } else { j = next[j]; } } return next; } public static void main(String[] args) { String text = "ABABDABACDABABCABAB"; String pattern = "ABAB"; List<Integer> matches = kmp(text, pattern); if (matches.isEmpty()) { System.out.println("Pattern not found"); } else { System.out.println("Pattern found at positions:"); for (int position : matches) { System.out.println(position); } } } }
在上面的示例中,我们使用一个List<Integer>来存储所有匹配的起始位置。在每次匹配成功后,我们将匹配的起始位置添加到列表中。如果列表为空,则表示未找到匹配位置。
List<Integer>
Pattern found at positions: 0 2 10
这表明模式字符串"ABAB"在文本字符串"ABABDABACDABABCABAB"中出现在索引位置0、2和10。
通过扩展KMP算法,我们可以找到文本字符串中所有匹配模式的位置,而不仅仅是第一个匹配位置。
当使用KMP算法进行字符串匹配时,还可以进行一些优化来进一步提高算法的效率。其中一个优化是在构建next数组时,处理重复的前缀。
在标准的KMP算法中,当模式串中出现重复的前缀时,构建next数组时会进行重复的比较,造成一定的性能损失。为了避免这种情况,可以通过观察模式串的特征,跳过重复的前缀的比较。
下面是优化后的getNext方法的实现:
private static int[] getNext(String pattern) { int n = pattern.length(); int[] next = new int[n]; next[0] = -1; int i = 0; int j = -1; while (i < n - 1) { if (j == -1 || pattern.charAt(i) == pattern.charAt(j)) { i++; j++; // 优化:如果下一个字符与当前字符相等,则直接跳过比较 if (pattern.charAt(i) == pattern.charAt(j)) { next[i] = next[j]; } else { next[i] = j; } } else { j = next[j]; } } return next; }
在优化后的getNext方法中,我们在处理重复的前缀时进行了优化。当下一个字符与当前字符相等时,我们将next[i]的值设为next[j],以跳过比较。
这种优化可以减少比较的次数,从而提高KMP算法的性能。尤其在模式串中包含大量重复前缀的情况下,优化的getNext方法能够更好地发挥作用。
请注意,优化的getNext方法仅适用于处理重复前缀的情况。对于其他类型的模式串,标准的getNext方法仍然是有效的。
通过优化KMP算法,我们可以进一步提高字符串匹配的效率,并更好地应对包含重复前缀的模式串。
原文链接:codingdict.net