kmp算法总结及题目

KMP算法(看毛片算法)是一种改进的字符串匹配算法

       KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)

kmp算法中最总要的是next数组,然后什么是next数组呢?

next数组就是优化“看毛片算法”的辅助数组,没有next数组kmp的时间复杂度就变成O(m*n),所以next数组是kmp的重点。

next数组是把在字符串里的当前字符的前缀与后缀的最长重复个数。

比如字符串为“aabaaabaa”next数组为:

下标012345678
模式串aabaaabaa
next数组010122312
怎么求得next数组呢?
就是两个指针先是i指针在0位置,j指针在-1位置上然后向前移动,如果不懂请看一下下面的代码:
private static int[] getNext(String f) {
		int next[] = new int[f.length() + 1];//next数组
		int j = -1;
		int i = 0;
		next[0] = -1;
		while (i < f.length()) {
			if (j == -1 || f.charAt(i) == f.charAt(j)) {//如果j=-1说明上一次已经执行过j=next[j]了,next数组到头了,不能再退了所以需要向前走
				i++;
				j++;
				next[i] = j;//next数组赋值
			} else {
				j = next[j];//回溯到上一层
			}
		}
		return next;
	}

一般我喜欢把next数组扩大一位,然后第一位也就是next[0]定义为-1,然后next数组里的数向后移动一位。

比如:aaaab的next数组为[0, 1, 2, 3, 0],然后我喜欢把它变成[-1, 0, 1, 2, 3, 0](这个肯定有根据的。)

求完next数组,我们该进去正题:求kmp了。

kmp算法

    也是两个指针 i  ,  j  指针  i  在主串上 , j  在模式串上,如果主串上的字符和模式串上的字符相同时,i 和 j 同时向前移动一个位置,也就是说 i++,j++ ;然后如果不相同 i 不动 j = next [ j ] ,就是去next数组里找前缀相同的地方。

直到 i ==(主串长度-1),或者满足条件,循环结束。如果看不太懂请看下面的代码:

private static int kmp(String s, String f) {
		int next[] = getNext(f);//得到上面代码的next数组
		int i = 0;
		int j = 0;
		while (i < s.length()) {
			if(j==-1||s.charAt(i)==f.charAt(j)) {
				i++;
				j++;
			}else {
				j=next[j];
			}
			if (j == f.length()) {
				return i-j+1;//这一行根据题意写代码
			}
		}
		return -1;
	}

只看代码是不行的需要做题来巩固!!!

最重要的事情说三遍!!

下面我推荐几道kmp的基础题:

杭电上的1711题,刚刚上面那两个代码就是这个代码,这个题是个模板题,上面两个代码不同的就是把字符串换成数组,俗话说“换汤不换药”,这个代码就不发了。

Problem - 1711   

hdu1358     Problem - 1358

题意:给出一个字符串求出每个前缀的最小周期

这个题需要研究下next数组,与kmp没有半毛钱关系。

一看就知道 t=(len-next[len]) 这个关系。代码如下:

    static int r=1;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (sc.hasNext()) {
            int n = sc.nextInt();
            if(n==0)break;
            String s=sc.next();
            int next[]=getNext(s);
            System.out.println("Test case #"+(r++));
            for (int i = 1; i <=n; i++) {
                if(next[i]!=0&&i%(i-next[i])==0) {
                    System.out.println(i+" "+i/(i-next[i]));
                }
            }
            System.out.println();
        }
    }

HDU3336      Problem - 3336

 题意:输入一个字符串求每个前缀在串中出现的次数和

题目比较简单,直接上代码:

public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (sc.hasNext()) {
            int n = sc.nextInt();
            for (int i = 0; i < n; i++) {
                int t=sc.nextInt();
                String s = sc.next();
                int next[]=getNext(s);
                int sum=(next[s.length()]%10007+s.length()%10007)%10007;
                for (int j = 0; j <s.length(); j++) {
                    if(next[j]>0&&next[j]+1!=next[j+1])
                    sum=(sum+next[j])%10007;
                }
                System.out.println(sum%10007);
            }
        }
    }

hdu2087   Problem - 2087

题意:给出主串和模式串,看模式串在主串出现几次,主串的元素不同重复利用。

裸KMP从前向后扫一遍kmp就好了,代码如下:

import java.util.Scanner;

public class Main{
    static int r=1;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (sc.hasNext()) {
            String s=sc.next();
            if(s.equals("#"))break;
            String f=sc.next();
            System.out.println(kmp(s,f));
        }
    }

    private static int kmp(String s, String f) {
        int sum = 0;
        int next[] = getNext(f);
        int i = 0;
        int j = 0;
        while (i < s.length()) {
            while (j != -1 && s.charAt(i) != f.charAt(j)) {
                j = next[j];
            }
            i++;
            j++;
            if (j == f.length() ) {
                sum++;
                j=0;
            }
        }
        return sum;
    }

    private static int[] getNext(String f) {
        int next[]=new int[f.length()+1];
        int j = -1;
        int i = 0;
        next[0] = -1;
        while (i < f.length() ) {
            if (j == -1 || f.charAt(i) == f.charAt(j)) {
                i++;
                j++;
                next[i] = j;
            } else {
                j = next[j];
            }
        }
        return next;
    }
}

下面也有几道也是板子题,我就不一一给出代码了都是简单题,有空好好研究一下 next (这个是核心

推荐几道题(都是简单题):

poj2406   2406 -- Power Strings

这个题是给定一个串求出串的最小周期:len/(len-next[len])

poj2752   2752 -- Seek the Name, Seek the Fame

题意:给定一个串求出满足既是前缀又是后缀的串的起始位置

poj2185   2185 -- Milking Grid

题意:输入一个矩阵由字符组成,求出矩阵的最小组成单位。

poj3461   3461 -- Oulipo

题意:找出第一个字符串在第二个字符串中出现次数。

以下是一道经典的 KMP 题目: 给定一个文本串 T 和一个模式串 P,求模式串 P 在文本串 T 中出现的次数。 输入格式: 第一行输入整数 n,表示文本串 T 的长度。 第二行输入字符串 T。 第三行输入整数 m,表示模式串 P 的长度。 第四行输入字符串 P。 输出格式: 输出一个整数,表示模式串 P 在文本串 T 中出现的次数。 输入样例: 8 abababab 2 ab 输出样例: 4 这道题可以使用 KMP 算法来解决,具体思路是: 1. 首先使用 KMP 算法对模式串 P 进行预处理,得到其 next 数组。 2. 然后在文本串 T 上使用 KMP 算法进行匹配,具体思路是:从文本串 T 的第一个字符开始,依次读入每个字符,如果当前字符与模式串 P 的某个字符不匹配,则根据 next 数组回溯到模式串 P 的某个位置重新开始匹配。 3. 如果在文本串 T 上匹配到了一个完整的模式串 P,则将匹配次数加 1,然后继续从下一个字符开始匹配。 最终匹配次数即为模式串 P 在文本串 T 中出现的次数。 下面是 Python 代码实现: ```python def kmp(text, pattern): n, m = len(text), len(pattern) if m == 0: return 0 next = [0] * m j = 0 for i in range(1, m): while j > 0 and pattern[i] != pattern[j]: j = next[j - 1] if pattern[i] == pattern[j]: j += 1 next[i] = j res = 0 j = 0 for i in range(n): while j > 0 and text[i] != pattern[j]: j = next[j - 1] if text[i] == pattern[j]: j += 1 if j == m: res += 1 j = next[j - 1] return res n = int(input()) text = input().strip() m = int(input()) pattern = input().strip() print(kmp(text, pattern)) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值