1686: 算法4-1,4-3:定位子串 + kmp算法

Description

  若干对字符串,每对字符串占一行并用一个空格分开。前一个字符串为母串,后者为子串。字符串只包含英文字母的大小写。每个字符串不超过98个字符。
  输出子串在母串中首次出现的位置,如果母串中不包含子串则输出0。每个整数占一行。

Input and Output

输入

ACMCLUB ACM
DataStructure data
domybest my

输出

1
0
3

Source

  蓝桥杯ACM训练系统 http://www.dotcpp.com/oj/problem1686.html

Solution

方法一:滑窗法。时间是 O(n*m)

#include <iostream>
#include <cstring>
#define Max_nums 100
using namespace std;

int findSubIndex(char *str1, char* str2){
	for(int i=0; i<=strlen(str1)-strlen(str2); i++){
		int j=0;
		// 这里开个大小strlen(str2)的滑窗
		while(j<strlen(str2) && str1[i+j]==str2[j])	j++;
		if(j==strlen(str2)) return i+1;
	}
	return 0;
}

int main(){
	char str1[Max_nums],str2[Max_nums];
	while(cin>>str1>>str2){
		cout<< findSubIndex(str1, str2)<<endl;
	}
	return 0;
}

方法二:kmp算法。时间: O(m+n)

#include <iostream>
#include <cstring>
#define Max_nums 100
using namespace std;


void getNext(char *str, int *next){
	//生成next数组值 
    next[0] = -1;
    int k=-1, j=0;
    while(j < strlen(str)){
        if(k==-1 || str[k]==str[j])
            next[++j] = ++k;
        else
            k=next[k];
    }
}


int findSubIndex_KMP(char *str1, char *str2, int *next){
	//查找模式串str2是否为主串str1的子串
	//若是返回子串在母串中第一次出现的位置
	//若不是则输出0
	int i=0, j=0;
	int len2=strlen(str2); //strlen()返回无符号整数,不能用于和负数比较 
	while(i<strlen(str1) && j<len2){
		if(j==-1 || str1[i]==str2[j]){
			i++;
           	j++;
		}
	 	else 
			j=next[j];
    }
    
    return j==strlen(str2) ? i+1-j : 0;
}



int main(){
    char str1[Max_nums],str2[Max_nums];
    int next[Max_nums];
    while(cin>>str1>>str2){
    	getNext(str2, next);
        cout<<findSubIndex_KMP(str1,str2,next)<<endl;
    }
    return 0;
}




kmp算法

一、参考:

二、字符匹配问题

设主串为:a b a c a a b a c a b a c a b a a b b
模式串为:a b a
问模式串是否为主串的一个子串,这即是一个字符串匹配问题。

  • 常用的暴力解法是滑窗法(时间是O(n*m),n、m分别是主串、模式串数量级),在主串中取和模式串一样大小的窗,比较窗中字符串是否等于模式串,不等于这个窗就往右移1位(窗左边弹出,右边入新字符),... ...。

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

三、什么是next数组:

在简单的一次匹配失败后,我们会想将模式串尽量的右移和主串进行匹配。右移的距离在KMP算法中是如此计算的:在已经匹配的模式串子串中,找出最长的相同的前缀和后缀,然后移动使它们重叠。我们提前计算出每个匹配失败的位置应该移动的距离,存放在next()中。

例子1:

模式串 A B C D A B D
j 0 1 2 3 4 5 6
next -1 0 0 0 0 1 2

例子2:

| 模式串| A| B | A | B | A | B | C| A|
| :-------- | :--------😐 :------: | :--------😐 :------: | :--------😐 :------: |
| j| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7|
| next | -1 | 0| 0| 1| 2| 3| 4| 0|

例子3:

模式串 A A A A A B
j 0 1 2 3 4 5
next -1 0 1 2 3 4

我们换一个方式,结合算法解释一下 j 和 next 关系:
首先 j 是匹配过程中遍历模式串的指针、就是模式串字符数组下标。i 是遍历主串的指针,就是主串字符数组下标。
然后,匹配过程中,当到第 j 位出现字符不匹配时:

  • 在滑窗法中, j 回归0(j=0),主串滑动选下一个窗口,i是需要回退的。
  • 在kmp算法中, j回归到next[j](j=next[i]),主串的 i 不需要回退。

四、代码如下

生成next数组:
void getNext(char *str, int *next){
	//生成next数组值 
    next[0] = -1;
    int k=-1, j=0;
    while(j < strlen(str)){
        if(k==-1 || str[k]==str[j])
            next[++j] = ++k;
        else
            k=next[k];
    }
}
kmp算法
int findSubIndex_KMP(char *str1, char *str2, int *next){
	//查找模式串str2是否为主串str1的子串
	//若是返回子串在母串中第一次出现的位置
	//若不是则输出0
	int i=0, j=0;
	int len2=strlen(str2); //strlen()返回无符号整数,不能用于和负数比较 
	while(i<strlen(str1) && j<len2){
		if(j==-1 || str1[i]==str2[j]){
			i++;
           	j++;
		}
	 	else 
			j=next[j];
    }
    
    return j==strlen(str2) ? i+1-j : 0;
}

posted on 2019-01-24 22:39  yejifeng  阅读(560)  评论(0编辑  收藏  举报

导航