数据结构第四章学习的是串,数组和广义表(广义表课程中没讲,问题不大)
串的定义其实在c++学习中就有所接触,所以这里不详说, 重点说的是两个串模式匹配算法
1.BF算法
BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;
若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种蛮力算法。
2.KMP算法
KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。
具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)。
应用到PTA上的是
7-1 串的模式匹配 (30 分)
给定一个主串S(长度<=10^6)和一个模式T(长度<=10^5),要求在主串S中找出与模式T相匹配的子串,返回相匹配的子串中的第一个字符在主串S中出现的位置。
输入格式:
输入有两行: 第一行是主串S; 第二行是模式T.
输出格式:
输出相匹配的子串中的第一个字符在主串S中出现的位置。若匹配失败,输出0.
输入样例:
在这里给出一组输入。例如:
aaaaaba
ba
输出样例:
在这里给出相应的输出。例如:
6
KMP算法的代码实现运用到此题
int Index_KMP(SString S, SString T, int next[]) { //S主串,T模式串 int i = 1, j = 1; //i指主串,j指模式串 int result = 0; while (i <= S.length&&j <= T.length) { if (j == 0 || S.ch[i] == T.ch[j]) { /如果匹配接着往下比较,不匹配就右移T i++; j++; } else { j = next[j]; } } if (j > T.length) { //当j到最后仍与主串相等+1时匹配成功 result = i - T.length; } return result; }
主要还是getnext函数
void getNext(SString T, int next[]) { int i = 1, j = 0; next[1] = 0; while (i < T.length) { if (j == 0 || T.ch[i] == T.ch[j]) { i++; j++; if (T.ch[i] != T.ch[j]) { //i和j都右移后如果不同,把最长串最后一个的位置给next next[i] = j; } else { //但是如果相同,就让next变为next[j] next[i] = next[j]; } } else { //如果j不为0且ch[i] 与 ch[j]不等,表示之前有相同串,但右移后又不同了 j = next[j]; } } }
最后则是学习到特殊矩阵的压缩存储
运用到例题有
7-1 稀疏矩阵 (30 分)
如果一个矩阵中,0元素占据了矩阵的大部分,那么这个矩阵称为“稀疏矩阵”。对于稀疏矩阵,传统的二维数组存储方式,会使用大量的内存来存储0,从而浪费大量内存。为此,可以用三元组的方式来存放一个稀疏矩阵。
对于一个给定的稀疏矩阵,设第r行、第c列值为v,且v不等于0,则这个值可以表示为 <。这个表示方法就称为三元组。那么,对于一个包含N个非零元素的稀疏矩阵,就可以用一个由N个三元组组成的表来存储了。
如:{<1, 1, 9>, <2, 3, 5>, <10, 20, 3>}就表示这样一个矩阵A:A[1,1]=9,A[2,3]=5,A[10,20]=3。其余元素为0。
要求查找某个非零数据是否在稀疏矩阵中,如果存在则输出其所在的行列号,不存在则输出ERROR。
输入格式:
共有N+2行输入: 第一行是三个整数m, n, N(N<=500),分别表示稀疏矩阵的行数、列数和矩阵中非零元素的个数,数据之间用空格间隔; 随后N行,输入稀疏矩阵的非零元素所在的行、列号和非零元素的值; 最后一行输入要查询的非0数据k。
输出格式:
如果存在则输出其行列号,不存在则输出ERROR。
输入样例:
在这里给出一组输入。例如:
10 29 3
2 18 -10
7 1 98
8 10 2
2
输出样例:
在这里给出相应的输出。例如:
8 10
代码如下:
#include<iostream> using namespace std; typedef struct { int i;//第i行 int j;//第j行 int v;//第i行第j列的值 }Mnode; typedef struct { Mnode a[520]; int length;//矩阵中非零元素的个数 }Tuple; int main() { Tuple T; int m,n,N;//m,矩阵行数,n,矩阵列数,N,矩阵的非零元素个数 cin>>m>>n>>N; T.length=N; for(int i=0;i<T.length;i++) { cin>>T.a[i].i; //输入非零元素所在行,列号和非零元素的值 cin>>T.a[i].j; cin>>T.a[i].v; } int d; cin>>d;//输入查询数据 if(d!=0) { for(int i=0;i<T.length;i++) { if(T.a[i].v == d) { cout<<T.a[i].i<<" "; cout<<T.a[i].j; } if( (T.a[i].v != d) && (i == T.length-1)) cout<<"ERROR"; else i++; } } }
收获:
本章内容较多,尤其是学习到两个新接触的算法,KMP算法带来的新思想对理解能力是一个挑战,
还有的是老师上课时带我们一步步去完成AI核心代码这道实践题,一步一步的编程思路可谓将问题
需要注意的细节都能展现在代码上,滴水不漏。也是一个编程基本功的体现,下来之后又重新按照老师的
算法思路一步步去完成,并加上老师还没讲到的can you 的改写,实现的代码:
7-2 AI核心代码 (30 分) 本题要求你实现一个简易版的 AI 英文问答程序,规则是: 无论用户说什么,首先把对方说的话在一行中原样打印出来; 消除原文中多余空格:把相邻单词间的多个空格换成 1 个空格,把行首尾的空格全部删掉,把标点符号前面的空格删掉; 把原文中所有大写英文字母变成小写,除了 I; 把原文中所有独立的 I 和 me 换成 you; 把原文中所有的问号 ? 换成惊叹号 !; 把原文中所有独立的 can you 换成 I can —— 这里“独立”是指被空格或标点符号分隔开的单词; 在一行中输出替换后的句子作为 AI 的回答。 输入格式: 输入首先在第一行给出不超过 10 的正整数 N,随后 N 行,每行给出一句不超过 1000 个字符的、以回车结尾的用户的对话,对话为非空字符串,仅包括字母、数字、空格、可见的半角标点符号。 输出格式: 按题面要求输出,每个 AI 的回答前要加上 AI: 和一个空格。 输入样例: 在这里给出一组输入。例如: 6 Hello ? Good to chat with you can you speak Chinese? Really? Could you show me 5 What Is this prime? I,don ‘t know 输出样例: 在这里给出相应的输出。例如: Hello ? AI: hello! Good to chat with you AI: good to chat with you can you speak Chinese? AI: I can speak chinese! Really? AI: really! Could you show me 5 AI: could you show you 5 What Is this prime? I,don ‘t know AI: what Is this prime! you,don‘t know #include<iostream> #include<cstring> #include<cstdio> using namespace std; string s; void go(string s); bool isSeparator(char ch); bool isPunctuation(char eh); bool iscanyou(char t[],int j) ; int main() { string s; int n, i; cin >> n; getchar(); //吸收回车符 for(i=1; i<=n; ++i){ getline(cin, s); cout << s << endl; cout << "AI: "; go(s);//AI根据s输出对话 } return 0; } void go(string s) { char t[3004];//输入全部是I的时候,输出长度是输入的三倍 //string t; int i, j;//i,j分别为s,t的下标 //i定位到s的第一个非空字符 for(i=0; s[i]==‘ ‘; ++i);//循环体为空 j = 0; //j的初值为0 //从s的第一个非空字符开始,逐个扫描,分情况复制到t while(s[i]!=‘\0‘){ if(s[i]==‘ ‘ && s[i-1]==‘ ‘){ ++i; //如果漏了这句,有连续空格时会死循环 continue; } if(s[i]==‘?‘){ t[j++] = ‘!‘; ++i; continue; } if(s[i]!=‘I‘){ t[j++] = tolower(s[i++]); continue; } t[j++] = s[i++]; //s[i]=‘I‘的情况 } t[j] = ‘\0‘; //给t补上结尾符 j=0; while(t[j]!=‘\0‘){//处理单独I变成you,最主要还是isSeparator函数的编写 if(t[j]==‘I‘ && (j==0 || isSeparator(t[j-1])) && isSeparator(t[j+1])){ cout << "you"; ++j; continue; } //同理,me与I一样做法 if(t[j]==‘m‘ && t[j+1]==‘e‘ && (j==0 || isSeparator(t[j-1])) && isSeparator(t[j+2])){ cout << "you"; j += 2; continue; } if(t[j]==‘ ‘ && isPunctuation(t[j+1])){//空格的下一个是标点,不输出 ++j; continue; } if(iscanyou(t,j)) { cout<<"I can"; j+=7; continue; } if(s[j]==‘ ‘&&s[j+1]==‘\0‘) {//处理结尾剩余得一个空格 ++j; continue; } cout<<t[j++]; } cout<<endl; } bool isSeparator(char ch) { //判断ch是否分隔符 //ch可能是:数字、字母、标点、空格、\0 //分隔符:!(数字、小写字母、I) ch = tolower(ch); if(ch>=‘0‘ && ch<=‘9‘ || ch>=‘a‘ && ch<=‘z‘ || ch==‘I‘) return false; else return true; } bool isPunctuation(char eh) {//判断空格下一个是标点,不输出 eh=tolower(eh); if(eh>=‘0‘&&eh<=‘9‘||eh>=‘a‘&&eh<=‘z‘|| eh==‘I‘ || eh==‘ ‘) return false; else return true; } bool iscanyou(char t[],int j) { //处理can you的函数 if(t[j]==‘c‘&&t[j+1]==‘a‘&&t[j+2]==‘n‘&&t[j+3]==‘ ‘&&t[j+4]==‘y‘&&t[j+5]==‘o‘&&t[j+6]==‘u‘) { if((j==0||isSeparator(t[j-1]) )&&isSeparator(t[j+7])) return true; } else return false; }
自己的编程能力有了一定的提高,但还要继续努力,达到自己理想中的状态。
希望自己能多点实践上机,好好巩固一下第四章学到的东西,而第五章将要学的树和二叉树
也要努力地跟上呀!
最后还是推荐一下学者网上老师发布的KMP算法的解析,看完之后其实也颇有收获的
原文地址:https://www.cnblogs.com/fengwanthousand/p/10708000.html