这章主要学习了串和数组。围绕串的主要有两种算法,BF算法和晦涩难懂的KMP算法;而围绕数组,则有特殊矩阵的压缩储存问题。我认为,KMP算法和压缩问题是本章的难点,也是本书学习的重中之重。
还记得老师上课时讲KMP算法和BF算法的时候说,想要用好这两个算法,必须先弄清楚算法中的变量是位置还是下标。于是秉承了上一阶段眼看不如用手做的学习方法,我试图将KMP算法的位置和下标两种情况实现,如下图所示:
位置实现:
1 #include2 #include 3 #include 4 #include<string> 5 #include<string.h> 6 using namespace std; 7 8 void get_next(string T, int next[]) {//next函数,第一个参数为模式,第二个参数为next数组 9 int i = 1; next[1] = 0; int j = 0; 10 while (i <T.length()) { 11 if (j == 0 || T[i] == T[j]) { ++i; ++j; next[i] = j;}// 12 else j = next[j];// 13 } 14 }//next数组有点迷.... 15 16 int Index_KMP(string S, string T, int pos,int next[]) {//第一个参数为主串,第二个参数为模式,第三个参数为开始位置,第四的参数为next数组 17 int i = pos; int j = 1; 18 while (i <= S.length() && j <= T.length()) { 19 if (j == 0 || S[i-1] == T[j-1]) { ++i; ++j; }//若匹配成功,则继续比较后面字符 20 else j = next[j];//若匹配不成功,则模式串向右移动 21 } 22 if (j > T.length()) return i - T.length(); 23 else return 0;//返回结果 24 } 25 int main() 26 { 27 string S;//定义主串 28 string T;//定义模式 29 getline(cin, S);//输入主串 30 getline(cin, T);//输入模式 31 int pos, result;//定义模式匹配开始的位置,以及模式匹配返回的结果 32 int len = T.length(); 33 int *next = new int [len];//为next数组分配空间 34 cin >> pos;//输入模式匹配开始的位置 35 get_next(T, next);//初始化next数组 36 result = Index_KMP(S, T, pos, next);//进行模式匹配 37 cout << result;//输出结果 38 delete next;//释放内存 39 system("pause"); 40 return 0; 41 }
下标实现:
1 #include2 #include 3 #include 4 #include<string> 5 #include<string.h> 6 using namespace std;//下标实现,有bug 7 8 void get_next(string T, int next[]) {//next函数,第一个参数为模式,第二个参数为next数组 9 int i = 0; next[0] = -1; int j = -1; 10 while (i < T.length()) { 11 if (j == -1 || T[i] == T[j]) { ++i; ++j; next[i] = j; } 12 else j = next[j]; 13 } 14 }//next数组有点迷.... 15 16 int Index_KMP(string S, string T, int index, int next[]) {//第一个参数为主串,第二个参数为模式,第三个参数为开始下标,第四的参数为next数组 17 int i = index; int j = 0; 18 while (i <= S.length() && j <= T.length()) { 19 if (j == 0 || S[i - 1] == T[j - 1]) { ++i; ++j; }//若匹配成功,则继续比较后面字符 20 else j = next[j];//若匹配不成功,则模式串向右移动 21 } 22 if (j > T.length()) return i - T.length()-1;//返回下标 23 else return -1;//由于下标可以为0,匹配失败返回0会发生冲突,所以返回-1 24 } 25 int main() 26 { 27 string S;//定义主串 28 string T;//定义模式 29 getline(cin, S);//输入主串 30 getline(cin, T);//输入模式 31 int index, result;//定义模式匹配开始的位置,以及模式匹配返回的结果 32 int len = T.length(); 33 int *next = new int[len];//为next数组分配空间 34 cin >> index;//输入模式匹配开始的位置 35 get_next(T, next);//初始化next数组 36 result = Index_KMP(S, T, index, next);//进行模式匹配 37 if (result != -1) cout << result;//输出下标 38 else cout << "匹配失败!";//避免歧义 39 delete next;//释放内存 40 system("pause"); 41 return 0; 42 }
然后讲一下稀疏矩阵的那道题。如果用三元组的方法去做,会相对的简单,代码如下:
#include#include #include<string> #include<string.h> #include using namespace std; struct three_tuple{ int line; int row; int value; }; int main() { int data[501]; int rows, lines, N; cin >> rows >> lines >> N;//输入行,列,以及非零元素个数 if (N > 500) return 0; three_tuple*S = new three_tuple[N];//创建三元组结构体数组 for (int i = 1; i <=N; i++) { int row, line, value; cin >> row >> line >> value;//输入元素的行,列,值 if (row > rows || line > lines) return 0;//排除特殊情况 S[i].row = row; S[i].line = line; S[i].value = value;//将行,列,值存到结构体数组中 }//以三元组形式输入稀疏矩阵 int goal; cin >> goal; if (goal == 0) return 0; for (int i = 1; i <=N; i++) {//对稀疏矩阵进行检索 if (S[i].value == goal) { cout << S[i].row << ' ' << S[i].line; system("pause"); return 0; } } cout << "ERROR" << endl; delete S; system("pause"); return 0; }
然后老师让我们尝试用十字链表的方法。首先让我们分析一下思路,十字链表中的结点有五个数据,行号,列号,非零的值,列结构体指针,行结构体指针。然后开始考虑是否需要头结点的问题。如果没有头结点,十字链表能否正常地进行插入,删除,查询呢?其实分析可以发现,没有头结点对于十字链表的操作没有影响,因为只是简单插入操作。然后就是行结构体指针数组的问题,到底用静态内存还是动态内存去储存呢?如果用动态内存,初始化得到的空间是固定且不可以改变的,而通过new来动态申请数组,大小可以不受那么多限制。因此,我们得使用new来动态申请两个结构体指针数组,并且将他们指空。接下来是插入的工作。因为行和列的操作其实是一样的,所以这里只讨论行的情况。插入的时候,对于行结构体指针数组,有两种情况:第一种情况,行头指针指空,这种情况比较简单,直接将结点插入就可以了;第二种情况,行头指针不为空,这是我们就需要比较即将插入的结点的列号与该行链表中所有元素的列号大小,条件是,当该结点的列号大于前面结点的列号,小于后面结点的列号,然后插入即可。最后是查询操作,这里可以有两种办法:第一种,通过遍历行结构体指针数组;第二种,通过遍历列结构体指针数组。对于两种情况,都是先判断指针数组元素是否为空,然后再进入特定的行或者列链表进行再次遍历。代码实现如下:
1 #include2 #include 3 #include 4 #include<string> 5 #include<string.h> 6 using namespace std; 7 8 struct Linklist{//定义结点 9 int row; 10 int line; 11 int value; 12 Linklist *lhead = NULL;//定义并且初始化列结构体指针 13 Linklist *rhead = NULL;//定义并且初始化行结构体指针 14 }; 15 16 void set_null(Linklist*temp[], int length) {//将结构体指针全置零 17 for (int i = 0; i < length; i++) { 18 temp[i] = NULL; 19 } 20 } 21 22 void push_Linklist(Linklist*&S, Linklist*line_d[], Linklist*row_d[], int row, int line) {//第一个参数为结构体指针,第二个参数为列指针数组,第三个参数为行指针数组,第四个参数为列,第五个参数为行 23 Linklist*temp = row_d[line - 1];//取行特定的行指针 24 if (temp == NULL || line - 1 < temp->line - 1) {//若行指针为空,或小于第一个结点的列数,则直接插入 25 S->rhead = row_d[line - 1];//将当前第一个结点接到该结点之后 26 row_d[line - 1] = S;//将该结点变为第一个结点 27 } 28 else { 29 for (int i = 0; temp->rhead != NULL; i++) { 30 if (line - 1 > temp->line - 1 && line - 1 < temp->rhead->line - 1) {//判断条件:比前面列数大,比后面列数小 31 S->rhead = temp->rhead; 32 temp->rhead = S;//插入结点操作 33 break; 34 } 35 else temp = temp->rhead;//否则结点 36 } 37 if (temp == NULL) temp->rhead = S;//若始终不匹配,则将数据插入到链表末 38 }//行结点接上 39 40 41 temp = line_d[row - 1];//再取行特定的列指针 42 if (line_d[row - 1] == NULL || row - 1 < temp->row - 1) {//若行指针为空,或小于第一个结点的列数,则直接插入 43 S->lhead = line_d[row - 1];//将当前第一个结点接到该结点之后 44 line_d[row - 1] = S;//将该结点变为第一个结点 45 } 46 else { 47 for (int i = 0; temp->lhead != NULL; i++) { 48 if (row - 1 > temp->row- 1 && row - 1 < temp->lhead->row - 1) {//判断条件:比前面列数大,比后面列数小 49 S->lhead = temp->lhead; 50 temp->lhead = S;//插入结点操作 51 break; 52 } 53 else temp = temp->lhead;//否则结点 54 } 55 if (temp == NULL) temp->lhead = S;//若始终不匹配,则将数据插入到链表末 56 }//列结点接上 57 } 58 59 void input_Linklist(int N, Linklist*row_d[], Linklist*line_d[]) { 60 for (int i = 0; i < N; i++) {//利用循环输入 61 int row, line, value; 62 cin >> row >> line >> value;//输入行,列,值 63 Linklist*temp = new Linklist;//创建新结点 64 temp->row = row; temp->line = line; temp->value = value;//将数据储存到结构体中 65 push_Linklist(temp, line_d, row_d, row, line);//将数据存入十字链表中 66 } 67 } 68 69 int search_Linklist(int val, Linklist*line_d[], int rows) {//第一个参数为需要检索的值,第二个参数为行结构体数组 70 int i; 71 for (i = 0; i < rows; i++) { 72 Linklist*temp = line_d[i]; 73 if (temp!= NULL) {//寻找非空列链表 74 for (int j = 0; temp != NULL; j++) { 75 if (temp->value == val) { 76 cout << temp->row << ' ' << temp->line;//若检索成功,则输出结果 77 system("pause"); 78 return 0; 79 } 80 else temp = temp->lhead;//若检索失败,则temp往下继续检索 81 } 82 } 83 } 84 if (i == rows) { 85 cout << "ERROR";//若查找失败,则返回错误 86 system("pause"); 87 return 0; 88 } 89 } 90 91 int main() 92 { 93 int rows, lines, N, val;//其中result为查找的结果,val为需要检索的值 94 cin >> rows >> lines >> N;//输入行,列,非零元素总数 95 if (N > 500) return 0;//设定非零总数 96 Linklist**line_d = new Linklist*[rows];//创建列指针数组 97 Linklist**row_d = new Linklist*[lines];//创建行指针数组 98 set_null(line_d,rows);//列指针数组全置零 99 set_null(row_d, lines);//行指针数组全置零 100 input_Linklist(N, row_d, line_d);//输入元素 101 cin>>val;//输入需要检索的值 102 search_Linklist(val,line_d,rows);//进行检索 103 system("pause"); 104 return 0; 105 }
十字链表做上去还是比较简单的。
下面分享一下一些学习的资料:
1.KMP算法的讲解
https://www.cnblogs.com/yjiyjige/p/3263858.html
上一阶段的学习中,做到了花大量时间去看课本,主要是因为KMP算法太深奥了,然后也每天有打代码,渐渐地找到了打C++题的快感。然后我也阅读了关于C语言的一些书籍,但是时间比较紧,没有把之前章节的实践二做完。
在接下来的学习中,还是得坚持打代码,然后能够做到上课前把上课的内容提前预习好,并且一定得找时间将之前实践二的题目补完,最后还要将网安的题目刷完。