【数据结构与算法】字符串匹配(头歌习题)【合集】

目录

  • 第1关:实现朴素的字符串匹配
    • 任务描述
    • 相关知识
    • 编程要求
    • 评测说明
    • 完整代码
  • 第2关:实现KMP字符串匹配
    • 任务描述
    • 相关知识
    • 编程要求
    • 评测说明
    • 完整代码
  • 第3关:【模板】KMP算法
    • 任务描述
    • 相关知识
      • C++ STL容器string
        • 1、string的定义
        • 2、string中内容的访问
        • 3、string常用函数实例解析
        • 4、C语言中将字符串转换为数值的函数
        • 5、C++11中将string转换为数值类型
        • 6.不同编译器打开-std=c++11编译开关的方法:
    • 编程要求:
    • 完整代码
  • 第4关:利用kmp算法求子串在主串中不重叠出现的次数
    • 任务描述
    • 编程要求
    • 完整代码
  • 第5关:利用KMP算法求子串在主串中重叠出现的次数
    • 任务描述
    • 编程要求
    • 完整代码

第1关:实现朴素的字符串匹配

任务描述

本关任务是实现函数int FindSubStr(char* t, char* p)。

相关知识

在一个长字符串中寻找一个短字符串出现的位置,这是字符串匹配问题。

例如:长字符串是 “string” ,短字符串是 “ring” ,那么短字符串在长字符串中出现的位置是 2 ,即 “ring” 在 “string” 中出现的开始位置是 2 。

编程要求

本关的编程任务是补全 step1/mystr.cpp 文件中的FindSubStr函数,以实现朴素的字符串匹配。

具体请参见后续测试样例。
本关涉及的代码文件 mystr.cpp 的代码框架如下:

int FindSubStr(char* t, char* p)
/*
从字符串t查找子字符串p。
字符串以数值结尾,例如p="str",那么p[0]='s',p[1]='t',p[2]='r',p[3]=0。
采用朴素的匹配算法,返回子字符串第一次出现的位置,例如t="string ring",p="ring",则返回2。
若没有找到,则返回-1。
*/
{
    // 请在此添加代码,补全函数FindSubStr
    /********** Begin *********/
    /********** End **********/
}

评测说明

本关的测试文件是 step1/Main.cpp ,测试过程如下:

平台编译 step1/Main.cpp ,然后链接相关程序库并生成 exe 可执行文件;
平台运行该 exe 可执行文件,并以标准输入方式提供测试输入;
平台获取该 exe 可执行文件的输出,然后将其与预期输出对比,如果一致则测试通过;否则测试失败。
以下是平台对 step1/Main.cpp 的样例测试集:

样例输入:
string
tri
样例输出:
Location: 1

开始你的任务吧,祝你成功!

完整代码

/*************************************************************
    date: April 2009
    copyright: Zhu En
    DO NOT distribute this code.
**************************************************************/

int FindSubStr(char* t, char* p)
/*
从字符串t查找子字符串p。
字符串以数值结尾,例如p="str",那么p[0]='s',p[1]='t',p[2]='r',p[3]=0。
采用朴素的匹配算法。
返回子字符串第一次出现的位置,例如t="string ring",p="ring",则返回2。
若没有找到,则返回-1。
*/
{
    // 请在此添加代码,补全函数FindSubStr
    /********** Begin *********/
    int cnt=0;
    char* tr; 

    while(*t!='\0' && *p!='\0'){
        if(*t!=*p){
            t++;
            cnt++;
        }else{
            tr=t;
            t++;
            p++;
            while(*p!='\0'){
                if(*t==*p){
                    t++;
                    p++;
                }
            }
            if(*p=='\0'){
                return cnt;
            }else{
                t = tr;
                p = &p[0];
            }
        }
    }
    if(*t=='\0'){
        return -1;
    }

    /********** End **********/
}

【数据结构与算法】字符串匹配(头歌习题)【合集】_第1张图片

第2关:实现KMP字符串匹配

任务描述

本关的编程任务是补全 step2/kmp.cpp 文件中的KmpGenNext函数,以实现 KMP 字符串匹配。该函数生成给定字符串的next数组。

相关知识

第 1 关中实现的朴素的字符串匹配算法在实际应用系统中效率低,而 KMP 字符串匹配算法可以实现高效的匹配。

假设长字符串为t,短字符串为p。为了进行 KMP 匹配,首先需要计算字符串p的next数组,后面实现了计算该数组的函数void KmpGenNext(char* p, int* next)。对于 “abcabcab” ,计算出的next数组如下图:
【数据结构与算法】字符串匹配(头歌习题)【合集】_第2张图片

其中:next[i]给出如下信息:从左到右将p的字符与t的字符进行比对时,若在p的i号位置出现不匹配,就将字符串p相对t右移i-next[i]位;若next[i]>=0,则右移后比对位置从next[i]号位置开始,否则从0号位置开始。下图 1 给出了一个匹配示例:

【数据结构与算法】字符串匹配(头歌习题)【合集】_第3张图片

本关涉及两个函数:

void KmpGenNext(char* p, int* next)
// 生成p的next数组, next数组长度大于等于字符串p的长度加1。
int KmpFindSubWithNext(char* t, char* p, int* next)
// 从t中查找子串p的第一次出现的位置。
// 若找到,返回出现的位置,否则返回-1。

编程要求

本关的编程任务是补全 step2/kmp.cpp 文件中的KmpGenNext函数,以实现 KMP 字符串匹配。该函数生成给定字符串的next数组,生成算法请你查阅相关资料。

具体请参见后续测试样例。
本关涉及的代码文件 kmp.cpp 的代码框架如下:

#include 
#include 
#include "kmp.h"
///
void KmpGenNext(char* p, int* next)
// 生成p的next数组, next数组长度大于等于字符串p的长度加1
{
    // 请在此添加代码,补全函数KmpGenNext
    /********** Begin *********/
    /********** End **********/
}
int KmpFindSubWithNext(char* t, char* p, int* next)
// 从t中查找子串p的第一次出现的位置
// 若找到,返回出现的位置,否则返回-1
{
    int i=0, j=0;
    while(p[i]!=0 && t[j]!=0)    {
        if(p[i]==t[j])     { 
            i++;  
            j++; 
        }
        else  if (next[i]>=0) {
            i = next[i];
        }
        else  { 
            i=0;  
            j++; 
        }
    }
    if(p[i]==0)  return j-i; //found
    else  return -1;  //not found
}

评测说明

本关的测试文件是 step2/Main.cpp ,测试过程如下:

平台编译 step2/Main.cpp ,然后链接相关程序库并生成 exe 可执行文件;
平台运行该 exe 可执行文件,并以标准输入方式提供测试输入;
平台获取该 exe 可执行文件的输出,然后将其与预期输出对比,如果一致则测试通过;否则测试失败。

输入输出格式:
输入格式:
第一行输入母串
第二行输入子串

输出格式:
输出Location: #,其中#是子串在母串中的位置编号

以下是平台对 step2/Main.cpp 的样例测试集:
样例输入:
stringabcedf1stringabcdef2stringabcdef3stringabcdef4stringabcdef5stringabcdef6stringabcdef7
stringabcdef7

样例输出:
Location: 78

开始你的任务吧,祝你成功!

完整代码

/*************************************************************
    date: 
    copyright: Zhu En
    DO NOT distribute this code without my permission.
**************************************************************/
//字符串 实现文件
//
#include 
#include 
#include "kmp.h"
#include
/

void KmpGenNext(char* p, int* next)
//生成p的next数组, next数组长度大于等于字符串p的长度加1
{
    // 请在此添加代码,补全函数KmpGenNext
    /********** Begin *********/
    int j,k;  // j遍历t, k求t[j]前与开头相同的字符个数
    k = -1;
    j = 0;
    next[0] = -1;

    while(j<strlen(p)-1){  
        if(k==-1 || p[j]==p[k]){
            j++;
            k++;
            next[j] = k;
        }else{
            k=next[k];
        }
    }
    /********** End   *********/
}


int KmpFindSubWithNext(char* t, char* p, int* next)
//从t中查找子串p的第一次出现的位置
//若找到,返回出现的位置,否则返回-1
{
	int i=0, j=0;
	while(p[i]!=0 && t[j]!=0)	{
		if(p[i]==t[j]) 	{ 
			i++;  
			j++; 
		}
		else  if (next[i]>=0) {
			i = next[i];
		}
		else  { 
			i=0;  
			j++; 
		}
	}
	if(p[i]==0)  return j-i; //found
	else  return -1;  //not found
}

【数据结构与算法】字符串匹配(头歌习题)【合集】_第4张图片

第3关:【模板】KMP算法

任务描述

本关任务: 给出两个字符串text和pattern,其中pattern为text的子串,求出pattern在text中所有出现的位置。

为了减少骗分的情况,接下来还要输出子串的前缀数组next。

相关知识

为了完成本关任务,你需要了解:C++ STL中字符串容器string的使用。

C++ STL容器string

在C语言中,一般使用字符数组char str[]来存放字符串,但是使用字符数组有时会显得操作麻烦,而且容易因经验不足而产生一些错误。为了使编程者可以更方便地对字符串进行操作,C++在STL中加入了 string类型,对字符串常用的需求功能进行了封装,使得操作起来更方便,且不易出错。
如果要使用 string,需要添加 string 头文件,即#include (注意 string.h 和 string 是不一样的头文件)。除此之外,还需要在头文件下面加上一句:“using namespace std;”,这样就可以在代码中使用string 了。下面来看string的一些常用用法。

1、string的定义

定义string的方式跟基本数据类型相同,只需要在string后跟上变量名即可:
string str;
如果要初始化,可以直接给string类型的变量进行赋值:

string str = “abcd”;
//或者:
char* p = “hello”;
string str§;

2、string中内容的访问

(1) 通过下标访问
一般来说,可以直接像字符数组那样去访问string,要注意的是,string中字符的索引值从0开始计算。

#include 
#include 
using namespace std;
int main() {
    string str = "abcd";
    for (int i = 0; i < str.length(); i++) {
        printf("%c", str[i]);        //输出abcd
    }
    return 0;
}

输出结果:abcd

也可以使用string的成员函数at(int index)访问index位置的字符。例如:在上面的示例中,str[i]等价于str.at(i)。
如果要读入和输出整个字符串,则只能使用cin和cout:

#include     //cin和cout都在iostream头文件中,而不是stdio.h中
#include 
using namespace std;
int main() {
    string str;
    cin >> str;
    cout << str;
    return 0;
}

上面的代码对任意的字符串输入,都会输出同样的字符串。
那么,真的没有办法用printf输出string吗?其实是有的,即用c_str( )将string类型转换为字符数组进行输出,示例如下:

#include 
#include 
using namespace std;
int main() {
    string str = "abcd";
    printf("%s\n", str.c_str());
    return 0;
}

输出结果:abcd

(2)通过迭代器访问
一般仅通过(1)即可满足访问的要求,但是有些函数比如insert( )与erase( )则要求以迭代器为参数,因此还是需要学习一下string迭代器的用法。
由于string不像其他STL容器那样需要参数,因此可以直接如下定义:
string::iterator it;
这样就得到了迭代器it,并且可以通过*it来访问string里的每一个字符:

#include 
#include 
using namespace std;
int main() {
    string str = "abcd";
    string::iterator it;
    for (it = str.begin(); it != str.end(); it++) {
        printf("%c", *it);
    }
    return 0;
}

输出结果:abcd

最后指出:string和vector 一样,支持直接对迭代器进行加减某个数字,如str.begin( ) + 3的写法是可行的。

3、string常用函数实例解析

事实上,string的函数有很多,伹是有些函数并不常用,因此下面就一些常用的函数举例。

(1)operator+=
这是string的加法,可以将两个`string直接拼接起来。
示例如下:

#include 
#include 
using namespace std;
int main() {
    string str1 = "abcd";
    string str2 = "xyz";
    string str3 = str1 + str2;    //将str1和str2拼接,赋值给str3
    str1 += str2;                    //将str2直接拼接到str1上
    cout << str1 << endl;
    cout << str3 << endl;
    return 0;
}

输出结果:
abcdxyz
abcdxyz

(2)比较运算符(compare operator)
两个string类型可以直接使用==、!=、<、<=、>、>=比较大小,比较规则是字典序。
示例如下:

#include 
#include 
using namespace std;
int main() {
    string str1 = "aa";
    string str2 = "aaa";
    string str3 = "abc";
    string str4 = "xyz";
    if (str1 < str2)                //如果str1字典序小于str2
        cout << "ok1" << endl;        //则输出ok1
    if (str1 != str3)                //如果str1字典序和str3不等
            cout << "ok2" << endl;    //则输出ok2
    if (str4 >= str3)                //如果str4字典序大于等于str3
            cout << "ok3" << endl;    //则输出ok3
    return 0;
}

输出结果:
ok1
ok2
ok3

(3)length( ) 和size( )
length( )返回string的长度,即存放的字符数,时间复杂度为O(1)。size( )和length( )功能相同。
示例如下:

#include 
#include 
using namespace std;
int main() {
    string str = "abcdxyz";
    cout << str.length() << " " << str.size() << endl;
    return 0;
}

输出结果:
7 7

(4)insert( )
string的insert( )函数有多种使用方法,其时间复杂度都是O(n),其中n是字符串的长度。
① insert(pos,string): 在pos位置插入字符串string。
示例如下:

string str = “abcxyz”;
str.insert(3, “789”);
cout << str << endl;
输出结果:
abc789xyz
② insert(it, it2, it3): it为原字符串的待插入位置,it2和it3为待插入的字符串的首尾迭代器,用来表示待插入的字符串[it2, it3)将被插在it的位置上。
示例如下:

#include 
#include 
using namespace std;
int main() {
    string str1 = "abcxyz";        //str1为原字符串
    string str2 = "789";            //str2为待插入的字符串
    //在str1的3号位,即在字符x的位置插入字符串789
    str1.insert(str1.begin() + 3, str2.begin(), str2.end());
    cout << str1 << endl;
    return 0;
}

输出结果:abc789xyz

(5)erase( )
erase( )有两种用法:删除单个字符、删除一个区间内的所有字符。时间复杂度均为O(n),其中n为字符串的长度。
① 删除单个字符
string str;
str.erase(it)用于删除单个字符,it为需要删除的字符的迭代器。
示例如下:

#include 
#include 
using namespace std;
int main() {
    string str = "abcdefg";
    str.erase(str.begin() + 4);    //删除4号位的字符,即字符e
    cout << str << endl;
    return 0;
}

输出结果:abcdfg

② 删除一个区间内的所有元素。
删除一个区间内的所有元素有两种方法:
第一种方法:str.erase(first, last),其中first为需要删除的区间的起始迭代器,而last则为需要删除的区间的末尾迭代器的下一个地址,也即为删除[first, last)。 示例如下:

#include 
#include 
using namespace std;
int main() {
    string str = "abcdefg";
    str.erase(str.begin() + 2, str.end() - 1);    //删除cdef
    cout << str << endl;
    return 0;
}

输出结果:abg

第二种方法:str.erase(pos, length),其中pos为需要开始删除的起始位置,length为删除的字符个数。
示例如下:

#include 
#include 
using namespace std;
int main() {
    string str = "abcdefg";
    str.erase(3, 2);    //删除从3号位置开始的2个字符,即删除de
    cout << str << endl;
    return 0;
}

输出结果:abcfg

(6)clear( )
clear( )用于清空string中的数据,时间复杂度一般为O(1)。
示例如下:

#include 
#include 
using namespace std;
int main() {
    string str = "abcde";
    str.clear();            //清空字符串
    cout << str.length() << endl;
    return 0;
}

输出结果:0

(7)substr( )
substr(pos, len)返回从pos位置开始、长度为len的子串。如果第二个参数len没有给出,则一直取到末尾。
示例如下:

#include 
#include 
using namespace std;
int main() {
    string str = "Thank you for your smile.";
    cout << str.substr(0, 5) << endl;            //Thank
    cout << str.substr(14, 4) << endl;            //your
    cout << str.substr(19, 5) << endl;            //smile
    cout << str.substr(14) << endl;                //your smile.
    return 0;
}

输出结果:
Thank
your
smile
your smile.

(8)string::npos
string::npos是一个常数,其本身的值为-1,但是由于是unsigned int类型,因此实际上也可以认为是unsigned int类型的最大值。string::npos一般用作find函数失败时的返回值。例如在下面的示例中可以认为string::npos等于-1或者是4294967295(这个值就是unsigned int类型的最大值2
32
−1)。
示例如下:

#include 
#include 
using namespace std;
int main() {
    if (string::npos == -1) {
        cout << "-1 is true." << endl;
    }
    if (string::npos == 4294967295) {
        cout << "4294967295 is also true." << endl;
    }
    return 0;
}

输出结果:
-1 is true.
4294967295 is also true.

(9)find( )
find( )函数有2个重载的使用形式,如下:
① str.find(str2):当str2是str的子串时,返回其在str中第一次出现的位置;如果str2不是str的子串,那么返回string::npos。
② str.find(str2, pos):从str的pos位置开始匹配str2,返回值与上面相同。
示例如下:

#include 
#include 
using namespace std;
int main() {
    string str = "Thank you for your smile.";
    string str2 = "you";
    string str3 = "me";
    if (str.find(str2) != string::npos) {
        cout << str.find(str2) << endl;
    }
    if (str.find(str2, 7) != string::npos) {
        cout << str.find(str2, 7) << endl;
    }
    if (str.find(str3) != string::npos) {
        cout << str.find(str3) << endl;
    } else {
        cout << "I know there is no position for me." << endl;
    }
    return 0;
}

输出结果:
6
14
I know there is no position for me.

(10)replace( )
str.replace(pos, len, str2)把str从pos位置开始、长度为len的子串替换为str2。
str.replace(it1, it2, str2)把str的迭代器[it1, it2)范围的子串替换为str2。
示例如下:

#include 
#include 
using namespace std;
int main() {
    string str = "Maybe you will turn around.";
    string str2 = "will not";
    string str3 = "surely";
    cout << str.replace(10, 4, str2) << endl;
    cout << str.replace(str.begin(), str.begin() + 5, str3) << endl;
    return 0;
}

输出结果:
Maybe you will not turn around.
surely you will not turn around.

(11)reverse()
reverse(): 逆转字符串。string类本身没有提供逆转字符串的成员函数,而是STL算法逆转字符串。实际上,reverse()函数用来逆转序列化容器(如vector、list,包括C/C++的数组)。
使用该函数,需要增加头文件:#include ,该头文件包含次函数的声明。
函数原型如下:
void reverse ( BidirectionalIterator first, BidirectionalIterator last);
示例:

#include 
#include 
#include     //使用reverse函数
using namespace std;
int main(int argc, char const *argv[])
{
    string s = "hello";
    reverse(s.begin(), s.end());
    cout << s << endl;
    return 0;
}

输出结果:
olleh

4、C语言中将字符串转换为数值的函数

注意:在使用下面的函数时,需要包含头文件#include ``。 (1) atoi( ): 将字符串转换为整数。函数原型如下: int atoi(const char* s);`
示例如下:

/* atoi example */
#include 
#include 
int main ()
{
    int i;
    char szInput [256];                //定义字符数组
    printf ("Enter a number: ");
    scanf("%s",szInput);            //输入一个数值字符串
    i = atoi (szInput);                //转换为整数
    printf ("The value entered is %d\n", i);
    return 0;
}

运行结果1:
Enter a number: 234
The value entered is 234
运行结果2:
Enter a number: 234.567
The value entered is 234
运行结果3:
Enter a number: 234abc567
The value entered is 234

``(2) atol( ): 将字符串转换为长整数(long int)。函数原型如下: long int atol(const char* s);`
示例如下:

/* atol example */
#include 
#include 
int main ()
{
    int li;
    char szInput [256];                //定义字符数组
    printf ("Enter a long number: ");
    scanf("%s",szInput);            //输入一个数值字符串
    li = atol (szInput);            //转换为长整数
    printf ("The value entered is %d\n", li);
    return 0;
}

运行结果:
Enter a number: 23456789
The value entered is 23456789

(3) atof( ): 将字符串转换为double类型。函数原型如下:
double atof(const char* s);
示例如下:

/* atof example: sine calculator */
#include 
#include 
#include 
int main ()
{
    double n, m;
    double pi = 3.1415926535;
    char szInput [256];                //定义字符数组
    printf ( "Enter degrees: " );    
    gets ( szInput );                //输入一个角度字符串
    n = atof ( szInput );            //转换为double类型
    m = sin (n * pi / 180);        //求正弦函数
    printf ( "The sine of %f degrees is %f\n" , n, m );
    return 0;
}

运行结果:
Enter degrees: 45
The sine of 45.000000 degrees is 0.707107

其他还有:
strtol: Convert string to long integer (function )
strtoul: Convert string to unsigned long integer (function )
strtod: Convert string to double (function )
不再一一举例。更多详情,参见下面的网址:
http://www.cplusplus.com
在该网址下面的“C Library”分支下面有所有C语言的库函数:
http://www.cplusplus.com/reference/clibrary/

5、C++11中将string转换为数值类型

注意:下面的函数在使用时,要包含头文件#include ,并使用using namespace std; 同时还需要将所使用的编译器的编译开关-std=c++11打开。

(1) stoi( ): 将string转换为int。
示例如下:

// stoi example
#include 
#include 
using namespace std;
int main ()
{
    int i;
    string str;                        //定义string类型的字符串
    cout << "Enter a number: ";
    cin >> str;                        //输入一个string类型的字符串
    i = stoi ( str );                //转换为int类型
    cout << "The value entered is " << i << endl;
    return 0;
}

运行结果:
Enter a number: 234567
The value entered is 234567

(2) stol( ): 将string转换为long int。
(3) stof( ): 将string转换为double。
其他还有:

stoul: Convert string to unsigned integer (function template )
stoull: 将string转换为unsigned long long
不再一一举例。更多详情,参见下面的网址:
http://www.cplusplus.com

6.不同编译器打开-std=c++11编译开关的方法:

1、 Dev-Cpp
【数据结构与算法】字符串匹配(头歌习题)【合集】_第5张图片
【数据结构与算法】字符串匹配(头歌习题)【合集】_第6张图片

2、CodeBlocks
【数据结构与算法】字符串匹配(头歌习题)【合集】_第7张图片
【数据结构与算法】字符串匹配(头歌习题)【合集】_第8张图片

编程要求:

给出两个字符串text和pattern,其中pattern为text的子串,求出pattern在text中所有出现的位置。

为了减少骗分的情况,接下来还要输出子串的前缀数组next。

输入格式:
第一行为一个字符串,即为text。

第二行为一个字符串,即为pattern。

输出格式:
若干行,每行包含一个整数,表示pattern在text中出现的位置。

接下来1行,包括length(pattern)个整数,表示前缀数组next[i]的值,数据间以一个空格分隔,行尾无多余空格。

输入样例:
ABABABC
ABA

输出样例:
1
3
-1 0 0

样例说明:
kmp算法匹配示例
【数据结构与算法】字符串匹配(头歌习题)【合集】_第9张图片
提示: 对于100%的数据:text长度<=1000000,pattern长度<=1000000

开始你的任务吧,祝你成功!

完整代码

#include 
#include 
#include 
#include 
#include 
using namespace std;

/**
 * txt: 字符串
 * pat: 模式串
 * 输出pat在txt中出现的位置
 * 输出next[]数组的值
 */
const int N = 1e6 + 1;
int lps[N];
int nextarray[N];


void get_lps(string pat, int * lps) {
    int m = pat.length();
    int j = 0, i = 1;
    lps[0] = 0;
    while (i < m) {
        if (pat[j] == pat[i]) {
            j++;
            lps[i] = j;
            i++;
        } else if (j != 0) {
            j = lps[j - 1];
        } else {
            lps[i] = 0;
            i++;
        }
    }
}

void get_next(string pat,int* lps)
{
    int m = pat.length();
    get_lps(pat,lps);
    int i;
    nextarray[0] = -1;

    for(i = m - 2; i >= 0 ; i--){
    lps[i+1] = lps[i];}
    lps[0]= -1;
    for(i=0;i<m;i++)
    {
    printf("%d",lps[i]);
    if(i<m-1)
        printf(" ");
    }
}

void KMP(string txt, string pat)
{
    //请在下面编写代码
    /*************************Begin*********************/
    int n = txt.length();
    int m = pat.length();
    int i = 0;
    int j = 0;
    get_lps(pat, lps);
    while (i < n && j < m) {
        if (txt[i] == pat[j]) {
            i++; j++;
        } else if (j != 0) {
            j = lps[j - 1];
        } else {
            i++;
        }
        if (j == m) {
            cout << i - j + 1 << endl;
            j = lps[j - 1];
        }
    }
    get_next(pat,lps);
    
    /**************************End**********************/
}

int main(int argc, char const *argv[])
{
    string txt, pat;

    cin >> txt >> pat;  //输入文本串和模式串

    KMP(txt, pat);    //调用kmp算法输出模式串在文本串中的位置以及next[]数组的值

    return 0;
}

【数据结构与算法】字符串匹配(头歌习题)【合集】_第10张图片

第4关:利用kmp算法求子串在主串中不重叠出现的次数

任务描述

本关任务:编写一个程序,利用kmp算法求子串在主串中不重叠出现的次数。

实验目的:深入掌握KMP算法的应用。
实验内容:编写一个程序,利用KMP算法求子串t在主串s中出现的次数,例如:s=“aabbdaabbde”,t=“aabbd”,t在s中出现2次;再例如:s=“aaaaa”,t=“aa”,t在s中出现2次。
实验工具:本关提供顺序串SqString的基本运算及其实现(在头文件sqstring.h中);您也可以直接使用C++ STL提供的string容器。

编程要求

根据提示,在右侧编辑器补充完成代码,计算并输出字符串t在字符串s中不重叠出现的次数。

测试说明
平台会对你编写的代码进行测试:

输入格式
输入包括2行。
第一行为字符串s,长度不超过10^6 。
第二行为字符串t,长度不超过10^6 。

输出格式
在一行中输出t在s中出现的次数。

样例输入1
aaaaa
aa

样例输出1
2

样例输入2
aabbdaabbde
aabbd

样例输出2
2

开始你的任务吧,祝你成功!

完整代码

/* step4/step4.cpp  作答区文件 */

#include 
#include 
#include 
#include 
#include        //C++ STL之string容器
using namespace std;

#include "sqstring.h" //包含顺序串SqString基本运算算法

//利用KMP算法求t在s中出现的次数
int Count(string s, string t); 

//利用KMP算法求t在s中出现的次数
int Count(char* s, char* t); 

//利用KMP算法求t在s中出现的次数
int Count(SqString s, SqString t); 

/**
 * 说明:任选上面三个函数中的一个进行实现。
 */

//请在下面编写代码

const int N =1e6;
int lps[N];
int nt[N];
void get_lps(string t,int *lps)
{
    int m=t.size();
    int i=1,j=0;
    lps[0]=0;
    while(i<m)
    {
        if(t[i]==t[j])
        {
            ++j;
            lps[i]=j;
            i++;
        }
        else if(j !=0)
        {
            j=lps[j-1];
        }
        else
        {
            lps[i]=0;
            i++;
        }
    }
}
void get_next(string t,int *next)
{
    int m=t.length();
    int k= -1,j=0;
    next[0]=-1;
    while(j<m-1)
    {
        if(k==-1||t[k]==t[j])
        {
            k++;
            j++;
            next[j]=k;
        }
        else
            k = next[k];
    }
}
int Count(string s,string t)
{
    int n=s.length();
    int m=t.length();
    int i=0;
    int j=0;
    int tot=0;
    get_lps(t,lps);
    while(i<n&&j<m)
    {
        if(s[i]==t[j])
        {
            i++;
            j++;
        }
        else if(j!=0)
            j=lps[j-1];
        else i++;
        if(j==m)
        {
            tot++;
            j=0;
        }
    }
    return tot;
}
int main()
{
    string s,t;
    cin>>s>>t;
    cout<<Count(s,t)<<endl;
    return 0;
}

/* step4/sqstring.h 头文件 */

#ifndef __SQSTRING_H
#define __SQSTRING_H

//顺序串基本运算的算法
#include 
#define MaxSize 1000001
typedef struct
{
	char data[MaxSize];		//串中字符
	int length;				//串长
} SqString;					//声明顺序串类型
void StrAssign(SqString &s, char cstr[])	//字符串常量赋给串s
{
	int i;
	for (i = 0; cstr[i] != '\0'; i++)
		s.data[i] = cstr[i];
	s.length = i;
}
void DestroyStr(SqString &s)		//销毁串
{  }

void StrCopy(SqString &s, SqString t)	//串复制
{
	for (int i = 0; i < t.length; i++)
		s.data[i] = t.data[i];
	s.length = t.length;
}
bool StrEqual(SqString s, SqString t)	//判串相等
{
	bool same = true;
	if (s.length != t.length)				//长度不相等时返回0
		same = false;
	else
		for (int i = 0; i < s.length; i++)
			if (s.data[i] != t.data[i])	//有一个对应字符不相同时返回假
			{	same = false;
				break;
			}
	return same;
}
int StrLength(SqString s)	//求串长
{
	return s.length;
}
SqString Concat(SqString s, SqString t)	//串连接
{
	SqString str;
	int i;
	str.length = s.length + t.length;
	for (i = 0; i < s.length; i++)	//s.data[0..s.length-1]→str
		str.data[i] = s.data[i];
	for (i = 0; i < t.length; i++)	//t.data[0..t.length-1]→str
		str.data[s.length + i] = t.data[i];
	return str;
}
SqString SubStr(SqString s, int i, int j)	//求子串
{
	SqString str;
	int k;
	str.length = 0;
	if (i <= 0 || i > s.length || j < 0 || i + j - 1 > s.length)
		return str;					//参数不正确时返回空串
	for (k = i - 1; k < i + j - 1; k++)  		//s.data[i..i+j]→str
		str.data[k - i + 1] = s.data[k];
	str.length = j;
	return str;
}
SqString InsStr(SqString s1, int i, SqString s2)	//插入串
{
	int j;
	SqString str;
	str.length = 0;
	if (i <= 0 || i > s1.length + 1)		//参数不正确时返回空串
		return str;
	for (j = 0; j < i - 1; j++)      		//s1.data[0..i-2]→str
		str.data[j] = s1.data[j];
	for (j = 0; j < s2.length; j++)		//s2.data[0..s2.length-1]→str
		str.data[i + j - 1] = s2.data[j];
	for (j = i - 1; j < s1.length; j++)		//s1.data[i-1..s1.length-1]→str
		str.data[s2.length + j] = s1.data[j];
	str.length = s1.length + s2.length;
	return str;
}
SqString DelStr(SqString s, int i, int j)		//串删去
{
	int k;
	SqString str;
	str.length = 0;
	if (i <= 0 || i > s.length || i + j > s.length + 1) //参数不正确时返回空串
		return str;
	for (k = 0; k < i - 1; k++)       		//s.data[0..i-2]→str
		str.data[k] = s.data[k];
	for (k = i + j - 1; k < s.length; k++)	//s.data[i+j-1..s.length-1]→str
		str.data[k - j] = s.data[k];
	str.length = s.length - j;
	return str;
}
SqString RepStr(SqString s, int i, int j, SqString t)	//子串替换
{
	int k;
	SqString str;
	str.length = 0;
	if (i <= 0 || i > s.length || i + j - 1 > s.length) //参数不正确时返回空串
		return str;
	for (k = 0; k < i - 1; k++)				//s.data[0..i-2]→str
		str.data[k] = s.data[k];
	for (k = 0; k < t.length; k++)   		//t.data[0..t.length-1]→str
		str.data[i + k - 1] = t.data[k];
	for (k = i + j - 1; k < s.length; k++)	//s.data[i+j-1..s.length-1]→str
		str.data[t.length + k - j] = s.data[k];
	str.length = s.length - j + t.length;
	return str;
}
void DispStr(SqString s)	//输出串s
{
	if (s.length > 0)
	{	for (int i = 0; i < s.length; i++)
			printf("%c", s.data[i]);
		printf("\n");
	}
}

#endif
 

【数据结构与算法】字符串匹配(头歌习题)【合集】_第11张图片

第5关:利用KMP算法求子串在主串中重叠出现的次数

任务描述

本关任务:编写一个程序,利用kmp算法求子串在主串中不重叠出现的次数。

实验目的:深入掌握KMP算法的应用。
实验内容:编写一个程序,利用KMP算法求子串t在主串s中重叠出现的次数,例如:s=“aabbdaabbde”,t=“aabbd”,t在s中出现2次;再例如:s=“aaaaa”,t=“aa”,t在s中出现4次。
实验工具:本关提供顺序串SqString的基本运算及其实现(在头文件sqstring.h中);您也可以直接使用C++ STL提供的string容器。

编程要求

根据提示,在右侧编辑器补充完成代码,计算并输出字符串t在字符串s中重叠出现的次数。

测试说明
平台会对你编写的代码进行测试:

输入格式
输入包括2行。
第一行为字符串s,长度不超过10^6 。
第二行为字符串t,长度不超过10^6 。

输出格式
在一行中输出t在s中出现的次数。

样例输入1
aaaaa
aa

样例输出1
4

样例输入2
aabbdaabbde
aabbd

样例输出2
2

开始你的任务吧,祝你成功!

完整代码

/* step5/step5.cpp 作答区文件*/

#include 
#include 
#include 
#include 
#include        //C++ STL之string容器
using namespace std;

#include "sqstring.h" //包含顺序串SqString基本运算算法

//利用KMP算法求t在s中重叠出现的次数
int Count(string s, string t); 

//利用KMP算法求t在s中重叠出现的次数
int Count(char* s, char* t); 

//利用KMP算法求t在s中重叠出现的次数
int Count(SqString s, SqString t); 

/**
 * 说明:任选上面三个函数中的一个进行实现。
 */

//请在下面编写代码
const int N =1e6;
int lps[N];
int nt[N];
void get_lps(string t,int *lps)
{
    int m=t.size();
    int i=1,j=0;
    lps[0]=0;
    while(i<m)
    {
        if(t[i]==t[j])
        {
            ++j;
            i++;
        }
        else if(j !=0)
        {
            j=lps[j-1];
        }
        else
        {
            lps[i]=0;
            i++;
        }
    }
}
void get_next(string t,int *next)
{
    int m=t.length();
    int k= -1,j=0;
    next[0]=-1;
    while(j<m-1)
    {
        if(k==-1||t[k]==t[j])
        {
            k++;
            j++;
            next[j]=k;
        }
        else
            k = next[k];
    }
}
int Count(string s,string t)
{
    int n=s.length();
    int m=t.length();
    int i=0;
    int j=0;
    int tot=0;
    get_next(t,nt);
    while(i<n&&j<m)
    {
        if(j==m-1&&s[i]==t[j])
        {
            tot++;
            j=nt[j];
        }
        if(j==-1||s[i]==t[j])
        {
            i++;
            j++;
        }
        else j=nt[j];
    }
    return tot;
}
int main()
{
    string s,t;
    cin>>s>>t;
    cout<<Count(s,t)<<endl;
    return 0;
}

/*step5/sqstring.h 头文件*/
#ifndef __SQSTRING_H
#define __SQSTRING_H

//顺序串基本运算的算法
#include 
#define MaxSize 1000001
typedef struct
{
	char data[MaxSize];		//串中字符
	int length;				//串长
} SqString;					//声明顺序串类型
void StrAssign(SqString &s, char cstr[])	//字符串常量赋给串s
{
	int i;
	for (i = 0; cstr[i] != '\0'; i++)
		s.data[i] = cstr[i];
	s.length = i;
}
void DestroyStr(SqString &s)		//销毁串
{  }

void StrCopy(SqString &s, SqString t)	//串复制
{
	for (int i = 0; i < t.length; i++)
		s.data[i] = t.data[i];
	s.length = t.length;
}
bool StrEqual(SqString s, SqString t)	//判串相等
{
	bool same = true;
	if (s.length != t.length)				//长度不相等时返回0
		same = false;
	else
		for (int i = 0; i < s.length; i++)
			if (s.data[i] != t.data[i])	//有一个对应字符不相同时返回假
			{	same = false;
				break;
			}
	return same;
}
int StrLength(SqString s)	//求串长
{
	return s.length;
}
SqString Concat(SqString s, SqString t)	//串连接
{
	SqString str;
	int i;
	str.length = s.length + t.length;
	for (i = 0; i < s.length; i++)	//s.data[0..s.length-1]→str
		str.data[i] = s.data[i];
	for (i = 0; i < t.length; i++)	//t.data[0..t.length-1]→str
		str.data[s.length + i] = t.data[i];
	return str;
}
SqString SubStr(SqString s, int i, int j)	//求子串
{
	SqString str;
	int k;
	str.length = 0;
	if (i <= 0 || i > s.length || j < 0 || i + j - 1 > s.length)
		return str;					//参数不正确时返回空串
	for (k = i - 1; k < i + j - 1; k++)  		//s.data[i..i+j]→str
		str.data[k - i + 1] = s.data[k];
	str.length = j;
	return str;
}
SqString InsStr(SqString s1, int i, SqString s2)	//插入串
{
	int j;
	SqString str;
	str.length = 0;
	if (i <= 0 || i > s1.length + 1)		//参数不正确时返回空串
		return str;
	for (j = 0; j < i - 1; j++)      		//s1.data[0..i-2]→str
		str.data[j] = s1.data[j];
	for (j = 0; j < s2.length; j++)		//s2.data[0..s2.length-1]→str
		str.data[i + j - 1] = s2.data[j];
	for (j = i - 1; j < s1.length; j++)		//s1.data[i-1..s1.length-1]→str
		str.data[s2.length + j] = s1.data[j];
	str.length = s1.length + s2.length;
	return str;
}
SqString DelStr(SqString s, int i, int j)		//串删去
{
	int k;
	SqString str;
	str.length = 0;
	if (i <= 0 || i > s.length || i + j > s.length + 1) //参数不正确时返回空串
		return str;
	for (k = 0; k < i - 1; k++)       		//s.data[0..i-2]→str
		str.data[k] = s.data[k];
	for (k = i + j - 1; k < s.length; k++)	//s.data[i+j-1..s.length-1]→str
		str.data[k - j] = s.data[k];
	str.length = s.length - j;
	return str;
}
SqString RepStr(SqString s, int i, int j, SqString t)	//子串替换
{
	int k;
	SqString str;
	str.length = 0;
	if (i <= 0 || i > s.length || i + j - 1 > s.length) //参数不正确时返回空串
		return str;
	for (k = 0; k < i - 1; k++)				//s.data[0..i-2]→str
		str.data[k] = s.data[k];
	for (k = 0; k < t.length; k++)   		//t.data[0..t.length-1]→str
		str.data[i + k - 1] = t.data[k];
	for (k = i + j - 1; k < s.length; k++)	//s.data[i+j-1..s.length-1]→str
		str.data[t.length + k - j] = s.data[k];
	str.length = s.length - j + t.length;
	return str;
}
void DispStr(SqString s)	//输出串s
{
	if (s.length > 0)
	{	for (int i = 0; i < s.length; i++)
			printf("%c", s.data[i]);
		printf("\n");
	}
}

#endif
  

【数据结构与算法】字符串匹配(头歌习题)【合集】_第12张图片

你可能感兴趣的:(数据结构(C语言),算法,数据结构,c语言)