数据结构之BF算法,kmp算法,三元组,十字链表总结

在这一章中,老师教了我们四种数据结构:BF算法,kmp算法,三元组和十字链表;还给我们讲了2019年团体天体赛中T1-8的AI题

1、对于BF和kmp算法,老师除了在课堂上讲解算法的主要核心思想外,还给了我们一道作业题去巩固;

这道题如下:

7-1 串的模式匹配 (30 分)
 

给定一个主串S(长度<=10^6)和一个模式T(长度<=10^5),要求在主串S中找出与模式T相匹配的子串,返回相匹配的子串中的第一个字符在主串S中出现的位置。

输入格式:

输入有两行: 第一行是主串S; 第二行是模式T.

输出格式:

输出相匹配的子串中的第一个字符在主串S中出现的位置。若匹配失败,输出0.

输入样例:

在这里给出一组输入。例如:

aaaaaba
ba

输出样例:

在这里给出相应的输出。例如:

6

首先,可以用BF算法去实现,但是BF算法是不能通过所有测试点的;BF算法实际上是一种暴力算法,而题目给的测试样例会有一个卡住而造成超时;
比如主串为aaaaaaaaaaaaaaaab 模式串为aaaaab ,这样的话每次都得去比较到最后一个才发现不匹配,而题目给的最大数据是1000000,所以一定是超时的;

BF算法代码如下:
我这里写的跟课本不太一样,我的i,j下标是从0开始,而课本的i,j表示的是位置,从1开始,所以当不匹配是课本回溯到的是i-j+2;而我这里是i-j+1;
 1 #include<iostream>
 2 #include<string.h>
 3 using namespace std;
 4 
 5 string s , t;
 6 int ssize ,tsize;
 7 int i = 0 ;
 8 int j = 0 ;
 9 int main()
10 {
11     cin>>s;      //输入主串
12     cin>>t;      //输入模式串
13     ssize = s.size();   //主串的长度;
14     tsize = t.size();    //模式串的长度;
15     while(i<ssize&&j<tsize)     
16     {
17         if(s[i]==t[j])    //如果匹配,则i++,j++;主串和模式串都向前移一位;
18         {
19             i++;
20             j++;
21         }else
22         {
23             i = i-j+1;    否则主串回溯到i-j+1;
24             j = 0;           模式串回溯到0;
25         }
26     }
27     if(j>=tsize)
28     {
29         cout<<i-tsize+1;
30     }else
31     cout<<0;
32     return 0;
33 }

 

 

这道题还能用kmp算法去写,kmp的核心是next数值;

解题思路:串的模式匹配有两种:一种是BF算法,一种是KMP算法
基于这道题给的数据,若用BF算法便会超时,所以我们这道题用KMP算法;
那么问题来了,KMP算法到底怎么用的;简单来讲,就是有两个步骤:
1、求模式串的next数组;
2、进行主串与模式串的匹配;
假设主串和模式串分别为

 

第一个问题:如何求next数组
?next数组求的是模式串的!!!
下面就以上面给的模式串为例;
next数组便是前缀中的最长相同前后缀,说起来比较绕,什么意思呢,模拟一遍就清楚了;



所以对于模式串对应的next数组为




这样我们就求出了next数组;
接下来进行模式匹配,其实这样就会有个问题,所以实际上next数组这样是需要改进的;

 

假设我们不改进的话,进行匹配会出现什么问题呢;

进行模式匹配的大概代码如下:

1、即匹配,则i++;j++;

2、不匹配,根据刚刚求出的next数组,进行跳next数组;

下面代码中ssize为主串s的长度,tsize为模式串t的长度;

下面我们就根据代码模拟一遍;

上面我们求出来的next数组为:

现在我们把它们的下面也写上:

 

 

现在开始模拟一遍:

 

 

 

 我们发现匹配到c的时候不匹配了,跳next数组,则跳到下标为0处,变成:

 

 

 

 

 此时也不匹配,变成应该跳next数组,跳到下标为0处,但是这样就变成死循环了,所以我们应该退一步,将next数组的第0个赋值为-1,且将整个next数组向后移;就不会变成死循环了;

再模拟一次:

此时不匹配跳next数组;

 

 

 

变成:

发现a不匹配,跳next数组:

 

继续模拟:

发现不匹配,所以此时next应该跳到下标为-1处,但是这里没有下标为-1的,所以实际上就是整体向后移;变成:

 

 

 

 发现完全匹配了;

那么基于上面的改进,next数组应该怎么写呢:

 1 string s;
 2 string t;
 3 int ssize;
 4 int tsize;
 5 int next1[2000000];
 6         void nextsz(string t,int tsize)
 7         {
 8             next1[0] = -1;     //防止进入死循环,而且到不能匹配时能整体后移
 9             int k = -1;     //是为了调节next数组;
10             int j = 0 ;
11             while(j < tsize-1)
12             {
13             if(k==-1||t[j]==t[k])   //k=-1进入这个循环是为了整体向后移;
14                 {
15                 ++k;         k实际上也是记录了相同的个数;
16                 ++j;
17                 
18                     next1[j] = k;   找到next数组;
19         
20                 }
21                else 
22                 k = next1[k];    //不相同则更新k;
23             }
24 
25         }

现在会了next数组,我们则可以进行模式匹配了;

利用上面求的next数组来进行模式匹配;过程原理和上面画的图是一模一样的;

代码如下:

 

1     int  kmp(string s,string t,int sszie,int tsize)
 2         {
 3             int j = 0;
 4             int i = 0;
 5             while(i<ssize&&j<tsize)
 6         {
 7             
 8                 if(j==-1||s[i]==t[j])  j=-1是为了调节到跳无可跳时,整体向后移;
 9             {
10                 i++;      //匹配整体向前移;
11                 j++;
12                 
13             }
14             else 
15             {
16                  j = next1[j];    不断跳next数组;
17             } 
18 
19             
20         }
21             
22             
23             if(j==tsize)
24             {
25                 return  i-j+1;  //返回模式串在主串的第一个下标;
26             }
27             else return -1//不匹配,则返回-1;
28         }

所以这道题的完整代码如下:

1 #include<iostream>
 2 #include<string.h>
 3 using namespace std ;
 4 
 5 string s;
 6 string t;
 7 int ssize;
 8 int tsize;
 9 int next1[2000000];
10         void nextsz(string t,int tsize)
11         {
12             next1[0] = -1;
13             int k = -1;
14             int j = 0 ;
15             while(j < tsize-1)
16             {
17             if(k==-1||t[j]==t[k])
18                 {
19                 ++k;
20                 ++j;
21                 
22                     next1[j] = k;
23         
24                 }
25                else 
26                 k = next1[k];
27             }
28 
29         }
30         
31         int  kmp(string s,string t,int sszie,int tsize)
32         {
33             int j = 0;
34             int i = 0;
35             while(i<ssize&&j<tsize)
36         {
37             
38                 if(j==-1||s[i]==t[j])
39             {
40                 i++;
41                 j++;
42                 
43             }
44             else 
45             {
46                  j = next1[j];
47             }
48 
49             
50         }
51             
52             
53             if(j==tsize)
54             {
55                 return  i-j+1;
56             }
57             else return 0;
58         }
59         
60         
61 int main()
62 {
63     cin>>s;
64     cin>>t;
65 
66     ssize =  s.size();
67     tsize = t.size();
68         nextsz(t,tsize);
69         cout<<kmp(s,t,ssize,tsize)<<endl;
70 
71         
72     
73 }

 

 

2、对于三元组和十字链表;

老师也是同样讲了核心思想加上给我们布置了稀疏矩阵的实践题;

7-1 稀疏矩阵 (30 分)
 

如果一个矩阵中,0元素占据了矩阵的大部分,那么这个矩阵称为“稀疏矩阵”。对于稀疏矩阵,传统的二维数组存储方式,会使用大量的内存来存储0,从而浪费大量内存。为此,可以用三元组的方式来存放一个稀疏矩阵。

对于一个给定的稀疏矩阵,设第r行、第c列值为v,且v不等于0,则这个值可以表示为 <r,v,c>。这个表示方法就称为三元组。那么,对于一个包含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



(1)利用三元组,实际上利用三元组是挺简单的,看如下代码:
 1 #include<iostream>
 2 using namespace std;
 3 
 4 int yr , yc ,num; //定义原来矩阵的行数yr,原来矩阵的列数yc,非零元素num; 
 5 struct syz{
 6     int i ;
 7     int j ;
 8     int value;
 9 }sanyz[505];    //定义一个三元组数组;
10 int index;     //定义一个要查找的数字; 
11 int flag = 0;   //用来标记是否有要查找的数字; 
12 int main()
13 {
14     cin>>yr>>yc>>num;
15     for(int k = 0 ; k < num ;k++)
16     {
17         cin>>sanyz[k].i>>sanyz[k].j>>sanyz[k].value;  //输入三元组的行、列和数组; 
18     }
19     cin>>index;    //输入要检索的数字; 
20     for(int k = 0 ; k < num ;k++)
21     {
22         if(index==sanyz[k].value)
23         {
24             flag = 1;     //如果可以找到我们要的数字,将 flag置为1; 
25         }
26     }
27     if(flag==0)    //如果找不到; 
28     {
29         cout<<"ERROR\n";
30     }else
31     {
32         for(int k = 0 ; k < num ;k++)
33         {
34             if(index==sanyz[k].value)
35             {
36                 cout<<sanyz[k].i<<" "<<sanyz[k].j<<endl;
37             }
38         }
39     }
40 }

(2)利用十字链表,这是一个很难的地方,实际上我也是理解了好久才似懂非懂;

代码如下:

  1 #include<iostream>
  2 #include<stdio.h>
  3 using namespace std;
  4 
  5 struct OLNod{
  6     int i ;   //该非零元的行下标; 
  7     int j ;   //该非零元 的列下标; 
  8     int value ;   //该非零元的数值;
  9     struct OLNod *right ,*down ;//该非零元所在的行表和列表的后继链域; 
 10 };
 11 struct CrossL{
 12     OLNod **rhead, **sead; //十字链表的行头指针和列头指针; 
 13     int row;     //稀疏矩阵的行数; 
 14     int col;     //稀疏矩阵的列数; 
 15     int num;     //稀疏矩阵的非零个数; 
 16 };
 17 
 18 int InitSMatrix(CrossL *M)  //初始化M(CrossList类型的变量必须初始化; 
 19 {
 20     (*M).rhead = (*M).sead = NULL;
 21     (*M).row = (*M).col = (*M).num = 0;
 22     return 1;
 23  } 
 24  
 25 int DestroysMatrix(CrossL *M)  //销毁稀疏矩阵M; 
 26  {
 27      int i ;
 28      OLNod *p,*q;
 29      for( i = 1 ; i <= (*M).row;i++)
 30      {
 31          p = *((*M).rhead+i);  //p指针不断向右移;
 32          while(p!=NULL)
 33          {
 34              q = p ;
 35              p = p ->right;
 36              delete q;   //删除q;
 37          }
 38      }
 39      delete((*M).rhead);  //释放行指针空间; 
 40      delete((*M).sead);  //释放列指针空间; 
 41      (*M).rhead = (*M).sead = NULL;   //并将行、列头指针置为空;
 42      (*M).num = (*M).row = (*M).col = 0;   //将非零元素,行数和列数置为0;
 43      return 1;
 44   } 
 45 int CreatSMatrix(CrossL *M)
 46 {
 47     int i , j  , m , n , t;
 48     int value;
 49     OLNod *p,*q;
 50     if((*M).rhead!=NULL)   
 51     DestroysMatrix(M);
 52     cin>>m>>n>>t;  //输入稀疏矩阵的行数、列数和非零元个数; 
 53     (*M).row = m; 
 54     (*M).col = n ;
 55     (*M).num = t;
 56     //初始化行链表头; 
 57     (*M).rhead = new  OLNod*[m+1];//为行头指针申请一个空间; 
 58     if(!(*M).rhead)    //如果申请不成功,则退出程序;
 59        exit(0);
 60     //初始化列链表头;
 61     (*M).sead = new OLNod*[n+1];//为列表头申请一个空间;
 62     if(!(*M).sead)    //如果申请不成功,则退出程序;
 63     exit(0);
 64     for(int k = 1 ; k <= m ; k++)
 65     {
 66         (*M).rhead[k] = NULL;//初始化行头指针向量;各行链表为空链表; 
 67      } 
 68      for(int k = 1 ; k <= n ;k++)
 69      {
 70          (*M).sead[k] = NULL;//初始化列头指针向量;各列链表为空链表; 
 71      }
 72      for(int k = 0 ; k < t ;k++)  //输入非零元素的信息; 
 73      {
 74          cin>>i>>j>>value;//输入非零元的行、列、数值; 
 75      p =  new OLNod();//为p指针申请一个空间;
 76      if(!p)    //e如果申请不成功;
 77        exit(0);  //退出程序;
 78       p->i = i;
 79       p->j = j;
 80       p->value = value;
 81       if((*M).rhead[i]==NULL)   //如果行头指针指向的为空;
 82       {
 83           //p插在该行的第一个结点处;
 84           p->right = (*M).rhead[i];  
 85           (*M).rhead[i] = p; 
 86        }else   //如果不指向空
 87        {
 88            for(q = (*M).rhead[i];q->right; q = q->right);
 89            p->right = q->right;
 90            q->right = p;
 91            
 92        }
 93        if((*M).sead[j]==NULL)//如果列头指针指向的为空;
 94        {
 95         //p插在该行的第一个结点处;
 96            p->down = (*M).sead[j];
 97            (*M).sead[j] = p;
 98        }else//如果不指向空
 99        {
100            for(q = (*M).sead[j];q->down;q = q->down);
101            p->down = q->down;
102            q->down = p;
103        }
104     }
105     return 1;
106 }
107 int PrintSMatrix(CrossL *M)
108 {
109     int flag = 0;
110     int val ;//要查找的元素的值; 
111     cin>>val;  //输入要查找的s值;
112     OLNod *p;   
113     for(int i = 1 ; i <= (*M).row ;i++)   
114     {
115         for(p = (*M).rhead[i];p;p = p->right)  //从行头指针开始找,不断向右找
116         {
117             if(p->value==val)    //如果能找到
118             {
119                 cout<<p->i<<" "<<p->j;  //输出行下标和列下标
120                 flag = 1;   //标记找到该元素;
121             }
122         }
123     }
124     
125     
126     if(flag==0)    //如果找不懂
127     {
128         cout<<"ERROR\n";
129     }
130         
131 }
132 int main()
133 {
134     CrossL A;   //定义一个十字链表; 
135     InitSMatrix(&A);  //初始化; 
136     CreatSMatrix(&A);  //创建; 
137     PrintSMatrix(&A); //输出; 
138     DestroysMatrix(&A); //销毁; 
139     return 0;
140 }

 


最后老师便是在周四的实验课中交了我们AI的代码;跟着老师的步伐觉得其实这道题也不是很难,主要是心要细而且静的下心来去写,它有太多要考虑的细节;这里我看了老师的博客,再去完善这道题;
代码如下:这其中我也知道了 tolower这个函数,是直接将大写字母转化成小写;

  1 #include<iostream>
  2 #include<cstring>
  3 #include<cstdio>
  4 #include<string.h>
  5 using namespace std;
  6 
  7 //1、删除空格(删除前后空格,删除中间连续的空格保留一个,删除符号后的空格 
  8 //后面的条件依靠空格标准化 
  9 bool isIndepent(char ch)
 10 {
 11     ch = tolower(ch);
 12     if(ch>='0'&&ch<='9'||ch>='a'&&ch<='z'||ch=='I')
 13     {
 14         return false;
 15     }else
 16     return true;
 17 }
 18 bool isPunctuation(char ch)
 19 {
 20     if(ch>='0'&&ch<='9'||ch>='a'&&ch<='z'||ch=='I'||ch==' ')
 21     return false;
 22     else
 23     return true;
 24 }
 25 
 26 void go(string s)
 27  {// 根据s输出AI的回答 
 28      //定义辅助变量t,来copy s的字符串 
 29      char t[3001];
 30      int i , j; //定义两个下表; i :定义到s的第一个非空; j : 定义t 
 31     for(i = 0 ; s[i]!= '\0' && s[i] == ' ' ; i++);  //全为空格的时候会将s的最后一个字符‘\0’存储 
 32     //保证每次往后扫一次用for,往后扫的次数要跳跃无规律用while
 33     j = 0;
 34     while(s[i] != '\0'){ //把s串copy到t,但是连续的空格只copy一次 
 35         if(s[i] == ' ' && s[i-1] ==' ')//连续的空格第一个要,后面的都不要 
 36         { //一个一个copy到t字符串同时判断 
 37             i++;
 38             continue;
 39         }
 40         
 41         if(s[i] == '?')
 42         {
 43             t[j] = '!';
 44             j++;
 45             i++;
 46             continue; //回到循环开头 
 47          } 
 48         if( s[i] != 'I')
 49         {
 50             t[j] = tolower(s[i]);
 51             i++;
 52             j++;
 53             continue;
 54         }
 55             
 56         
 57            
 58            t[j] = s[i];
 59            i++;
 60            j++;
 61         
 62          //不能copy连续的空格 ,先读变量值,为下一轮循环做准备 
 63         //此时t没有处理'\0' 后面读出字符串会出错 
 64     } 
 65     t[j] = '\0';//给t补上结尾符; 
 66    
 67     j = 0 ;
 68     while(t[j]!='\0')
 69     {
 70         if(t[j]=='I'&&(j==0||isIndepent(t[j-1]))&&isIndepent(t[j+1]))
 71         {
 72             cout<<"you";
 73             j++;
 74             continue;
 75         }else
 76         if(t[j]=='m'&&t[j+1]=='e'&&(j==0||isIndepent(t[j-1]))&&isIndepent(t[j+2]))
 77         {
 78             cout<<"you";
 79             j += 2;
 80             continue;
 81         }else
 82         if(t[j]==' '&&isPunctuation(t[j+1]))
 83         {
 84             j++;
 85         }else
 86         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'&&(j==0||isIndepent(t[j-1])&&isIndepent(t[j+7])))
 87         {
 88              cout<<"I can";
 89              j += 7;
 90         }
 91         else
 92         {
 93             cout<<t[j];
 94             j++;
 95             
 96         }
 97         
 98     }
 99     cout<<endl;
100  }
101  
102 //主函数
103 int main()
104 {
105     int n;
106     string s;
107     cin >>n;
108     getchar();// 吸收回车 《cstdio> 
109     for ( int i=0; i<n ; i++)
110     {
111         getline(cin , s);
112         cout << s << endl ;
113         cout << "AI: ";
114         go(s); // 根据s输出AI的回答 
115     }
116     
117     return 0; 
118  } 
119  

 另外,我还把实践二给做了,实际上实践二便是kmp的应用;

题目如下:

7-1 串的模式匹配 (30 分)
 

给定两个由英文字母组成的字符串 String 和 Pattern,要求找到 Pattern 在 String 中第一次出现的位置,并将此位置后的 String 的子串输出。如果找不到,则输出“Not Found”。

本题旨在测试各种不同的匹配算法在各种数据情况下的表现。各组测试数据特点如下:

  • 数据0:小规模字符串,测试基本正确性;
  • 数据1:随机数据,String 长度为 10510^5105​​,Pattern 长度为 101010
  • 数据2:随机数据,String 长度为 10510^5105​​,Pattern 长度为 10210^2102​​
  • 数据3:随机数据,String 长度为 10510^5105​​,Pattern 长度为 10310^3103​​
  • 数据4:随机数据,String 长度为 10510^5105​​,Pattern 长度为 10410^4104​​
  • 数据5:String 长度为 10610^6106​​,Pattern 长度为 10510^5105​​;测试尾字符不匹配的情形;
  • 数据6:String 长度为 10610^6106​​,Pattern 长度为 10510^5105​​;测试首字符不匹配的情形。

输入格式:

输入第一行给出 String,为由英文字母组成的、长度不超过 10610^6106​​ 的字符串。第二行给出一个正整数 NNN≤10\le 1010),为待匹配的模式串的个数。随后 NNN 行,每行给出一个 Pattern,为由英文字母组成的、长度不超过 10510^5105​​ 的字符串。每个字符串都非空,以回车结束。

输出格式:

对每个 Pattern,按照题面要求输出匹配结果。

输入样例:

abcabcabcabcacabxy
3
abcabcacab
cabcabcd
abcabcabcabcacabxyz

输出样例:

abcabcacabxy
Not Found
Not Found

 代码如下:实际上就是kmp,代码一模一样,这里便不赘述了,主要这里要多一个ans记录一下就好了;

 1 #include<iostream>
 2 #include<stdio.h>
 3 using namespace std;
 4 
 5 int next1[1000005];
 6 void getnext(string t ,int tsize)   //求next数组
 7 {
 8     int k = -1 ;
 9     int j = 0;
10     next1[0] = -1;
11     while(j<tsize-1)
12     {
13         if(k==-1||t[j] == t[k])
14         {
15             ++j;
16             ++k;
17             next1[j] = k;
18         }else
19         k = next1[k];
20     
21     }
22 } 
23 
24 int kmp(string s ,string t ,int ssize,int tsize)   //kmp匹配
25 {
26     int i = 0 ; 
27     int j = 0 ;
28     while(i<ssize&&j<tsize)
29     {
30         if(j==-1||s[i]==t[j])
31         {
32             i++;
33             j++;
34         }else
35         {
36             j = next1[j];
37         }
38     }
39     if(j==tsize)
40     {
41         return i-j+1;
42     }else
43     return -1;
44 }
45 string s ;
46 string t ;
47 int n ;
48 int ssize;
49 int tsize;
50 int ans ;
51 int main()
52 {
53     cin>>s;
54     cin>>n;
55     ssize = s.size();
56     while(n--)
57     {
58         cin>>t;
59         tsize = t.size();
60         getnext(t,tsize);
61         ans = kmp(s,t,ssize,tsize);    //记录ans,看是否能找到匹配
62         if(ans==-1)   //无找到匹配
63         {
64             printf("Not Found\n");
65         }else    //找到匹配
66         {
67             for(int i = ans-1 ;i < ssize; i++)   //输出匹配后面的所有字符;
68             {
69                 cout<<s[i];
70             }
71             cout<<endl;
72         }
73         
74     }
75     return 0;
76 }

 

总结:其实这一章学的东西并不简单,还是需要花时间去琢磨的,但是收获还是非常大;
对于上一章定的目标,我觉得十字链表我是很生疏的,不是那么熟练,感觉比较吃力;
下一章的目标:能够吸收算法的主要核心思想,并加以运用,并且多打题,多锻炼思维;

转载于:https://www.cnblogs.com/yewanting/p/10706552.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值