KMP算法与应用

KMP算法

基本原理

目标:找出字符串pattern在字符串text中出现的次数与位置,
方法:先计算pattern字符串的前缀表,即next[]数组,然后再使用next数组来进行字符串匹配
Tips
1.next[i] 的含义:前面长度为i的子串的最大公共前后缀的长度

代码实现

//经典KMP算法
//找出字符串p在字符串t中出现的次数与位置

#include <iostream>
#include <cstdio>
#include <cstring>

using  namespace std;

const int N = 1e6+10;
char  p[N],t[N]; //p为短的字符串,t为长的字符串
int next[N];	//数字next为前缀码表
int sum;		//字符串p在字符串t中出现的次数


void  getNext(const char P[], int next[]) {
	int  m = strlen(P);
	int i = 0, j;
	j = next[0] = -1;
	while (i < m) {
		while (-1 != j && P[i] != P[j])j = next[j];
		j++;i++;
		next[i] = j;
	}
}

void  kmp(const char P[], const char T[] , int next[]) {
	int n = strlen(T), m = strlen(P);
	int i, j;
	getNext(P, next);
	i = j = 0
		;
	while (i < n) {
		while (-1 != j && T[i] != P[j])j = next[j];
		i++; j++;
		if (j >= m) {
			sum++;
			printf("%d\n", i);
			j = next[j];//这儿修改很重要,不然会超时
		}
	}
}

int main() 
{
	strcpy(p,"aba");
	strcpy(t, "ababcabcbababcabacaba");
	kmp(p,t,next);
	printf("字符串匹配次数为:%d\n", sum);
	//system("pause");
	return 0;
}

KMP算法的应用

next数组的应用

来源POJ2752 Seek the Name, Seek the Fame

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

const int N = 4 * 1e5 + 10;
void getNext(char P[], int next[])
{
	int m = strlen(P);
	int i, j;
	i = 0;
	j = next[0] = -1;
	while (i < m)
	{
		while (j != -1 && P[i] != P[j]) j = next[j];
		j++; i++;
		next[i] = j;
	}
}

int main()
{
	char S[N];
	int next[N];
	vector<int> a; //保存满足条件的姓名的长度
	while (scanf("%s", S)!= EOF)
	{
		a.clear();
		int len = strlen(S);
		a.push_back(len);
		memset(next,0, sizeof(next));
		getNext(S, next);
		while (next[len] > 0)
		{
			a.push_back(next[len]);
			len = next[len];
		}
		sort(a.begin(), a.end());
		for (int i = 0; i < a.size(); i++)
			cout << a[i] << " ";
		cout << endl;
	}
	return 0;
}

最小循环节和循环周期

例一

来源POJ2406 Power Strings
思路:根据定理可以得到:假设字符串 S S 的长度为 l e n len ,那么 最小循环节 L = l e n n e x t [ l e n ] L=len-next[len] . 如果 L L 可以整除 l e n len ,那么循环周期 T = l e n / L T=len/L . 否则该字符串不能由循环节循环得到,输出1.

#include <iostream>
#include <string.h>

using namespace std;

const int N = 1e6 + 10;

void getNext(char P[], int next[])
{
	int m = strlen(P);
	memset(next, 0, sizeof(next));
	int i, j;
	i = 0;
	j = next[0] = -1;
	while (i < m)
	{
		while (j != -1 && P[i] != P[j]) j = next[j];
		i++; j++;
		next[i] = j;
	}
}

int main()
{
	char S[N];
	int next[N];
	int L, T;//L为最小循环节的长度,T为循环周期
	while (scanf("%s", S) != EOF)
	{
		if (!strcmp(S, ".")) break;   //strcmp(str1,str2) :若 str1<str2 则返回负数;若str1>str2 则返回正数;若str1=str2 则返回0
		getNext(S, next);
		int len = strlen(S);
		int L = len - next[len];
		if (len%L == 0)
			cout << len / L << endl;
		else
			cout << 1 << endl;
	}	
	return 0;
}

例二

来源POJ1961 Peroids
思路:此题为例一的扩展,求出由给定字符串的前 i 个字符组成的子串的循环节和循环周期。遍历整个字符串就能求出,遍历时候的循环节长度 L = i n e x t ( i ) L=i-next(i)

//最小循环节与循环周期问题
//最小循环节: L = len - next(len)
//循环周期: T = len/L (L可以整除len)

#include <iostream>
#include <string.h>

using namespace std;

const int N = 1e3 + 10;

void getNext(char P[], int next[])
{
	int m = strlen(P);
	memset(next, 0, sizeof(next));
	int i, j;
	i = 0;
	j = next[0] = -1;
	while (i < m)
	{
		while (j != -1 && P[i] != P[j])
			j = next[j];
		j++; i++;
		next[i] = j;
	}
}

int main()
{
	int next[N];
	char S[N];
	char s1[N];
	int L, T;		//L为最小循环节,T为循环周期
	int k;
	int q = 0;
	while (scanf("%d", &k) != EOF)
	{
		if (k == 0) break;
		q++;
		scanf("%s", S);
		cout << "Test case #" << q << endl;
		getNext(S, next);
		for (int i = 2; i <= k; i++)
		{
			L = i - next[i];
			if (i%L == 0&& L<i)
			{
				T = i / L;
				cout << i << " " << T << endl;
			}
		}
	}
	system("pause");
	return 0;
}
发布了9 篇原创文章 · 获赞 4 · 访问量 1021

猜你喜欢

转载自blog.csdn.net/L18273121172/article/details/89713583