數據結構之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 }

 

總結:其實這一章學的東西並不簡單,還是需要花時間去琢磨的,但是收獲還是非常大;
對於上一章定的目標,我覺得十字鏈表我是很生疏的,不是那么熟練,感覺比較吃力;
下一章的目標:能夠吸收算法的主要核心思想,並加以運用,並且多打題,多鍛煉思維;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM