1、公共子序列問題
網上有很多關於公共子序列問題,說的大同小異,看了很多不明白,很多都是晦澀難懂,這里分享一個連接,個人覺得講述的比較明白,易懂。
http://blog.csdn.net/v_july_v/article/details/6695482
我這里也簡單的把自己的理解說一下,求公共子序列問題是一個非常常見的問題,最差的方法就是暴力匹配,暴力匹配算法第一步求去短字符串的所有序列組合,然后從長到短一個一個的去匹配時候有公共序列相同,即使使用了這樣的剪枝,該算法效率任然很低。
比較受人青睞的算法當然莫過於動態規划了,動態規划的核心是找到轉移方程。把復雜的問題通過轉移方程轉移到子問題。
- 動態規划算法
事實上,最長公共子序列問題也有最優子結構性質。
記:
Xi=﹤x1,⋯,xi﹥即X序列的前i個字符 (1≤i≤m)(前綴)
Yj=﹤y1,⋯,yj﹥即Y序列的前j個字符 (1≤j≤n)(前綴)
假定Z=﹤z1,⋯,zk﹥∈LCS(X , Y)。
-
若xm=yn(最后一個字符相同),則不難用反證法證明:該字符必是X與Y的任一最長公共子序列Z(設長度為k)的最后一個字符,即有zk = xm = yn 且顯然有Zk-1∈LCS(Xm-1 , Yn-1)即Z的前綴Zk-1是Xm-1與Yn-1的最長公共子序列。此時,問題化歸成求Xm-1與Yn-1的LCS(LCS(X , Y)的長度等於LCS(Xm-1 , Yn-1)的長度加1)。
-
若xm≠yn,則亦不難用反證法證明:要么Z∈LCS(Xm-1, Y),要么Z∈LCS(X , Yn-1)。由於zk≠xm與zk≠yn其中至少有一個必成立,若zk≠xm則有Z∈LCS(Xm-1 , Y),類似的,若zk≠yn 則有Z∈LCS(X , Yn-1)。此時,問題化歸成求Xm-1與Y的LCS及X與Yn-1的LCS。LCS(X , Y)的長度為:max{LCS(Xm-1 , Y)的長度, LCS(X , Yn-1)的長度}。
由於上述當xm≠yn的情況中,求LCS(Xm-1 , Y)的長度與LCS(X , Yn-1)的長度,這兩個問題不是相互獨立的:兩者都需要求LCS(Xm-1,Yn-1)的長度。另外兩個序列的LCS中包含了兩個序列的前綴的LCS,故問題具有最優子結構性質考慮用動態規划法。
也就是說,解決這個LCS問題,你要求三個方面的東西:1、LCS(Xm-1,Yn-1)+1;2、LCS(Xm-1,Y),LCS(X,Yn-1);3、max{LCS(Xm-1,Y),LCS(X,Yn-1)}。
所以解決這個問題的動態轉移方程即:
if xm==yn LCS(Xm,Yn)= LCS(Xm-1,Yn-1)+1;
if xm!=yn LCS(Xm,Yn)= max{LCS(Xm-1,Yn),LCS(Xm,Yn-1)};
代碼如下:
#include <stdio.h> #include <string.h> /* c[i][j]存儲的是字串1到i位置,字串2到j位置時公共子序列的最大長度 if str1[i] == str2[j] c[i][j] = c[i-1][j-1]+1; if str1[i] != str2[j] c[i][j] = max{c[i-1][j],c[i][j-1]} */ int lcs(char *str1,char *str2,int len1,int len2,int c[100][100]) { if (str1 == NULL || str2 ==NULL) { return -1;//輸入字符串錯誤 } //初始化記錄dp的二維數組 for (int i = 0; i <= len1; i++) { for (int j = 0; j <= len2; j++) { c[i][j] = 0; } } //dp運算 for (int i = 1; i <= len1; i++) { for (int j = 1; j <= len2; j++) { if(str1[i-1] == str2[j-1]) { c[i][j]=c[i-1][j-1]+1; } else { c[i][j] = c[i-1][j]>c[i][j-1]?c[i-1][j]:c[i][j-1]; } } } //打印出dp數組存儲的內容 for (int i = 0; i <= len1; i++) { for (int j = 0; j <= len2; j++) { printf("%d ",c[i][j]); } printf("\n"); } //打印出公共子序列 char str[100]={0}; int index = c[len1][len2]-1; for (int i = len1,j = len2; i>0&&j>0;) { if(str1[i-1] == str2[j-1]) { str[index--] = str1[i-1]; i--; j--; } else { if(c[i][j-1]>c[i-1][j]) { j--; }else { i--; } } } printf("公共子序列為:%s\n",str); return c[len1][len2]; } int main(int argc, char **argv) { char str1[] = {"ABCBDAB"}; char str2[] = {"BDCABA"}; int c[100][100]; int len1 = strlen(str1); int len2 = strlen(str2); int num = lcs(str1,str2,len1,len2,c); printf("公共子序列的長度:%d\n",num); return 0; }
運行結果
2、最大公共子串
首先區分下公共字串和公共子序列的區別,公共子序列是在整個字符串中只要按照順序可以不用連續的,但是公共子串是指必須連續的字符串,舉個例子
ABCBDAB
BDCABA
公共子序列是 BCBA
公共字串是 AB
求公共字串比公共子序列稍微簡單了一些,如果上邊所述,公共子串也可以用暴力匹配方法,求出較短的字符串的所有子串,然后可以從長到短利用kmp字符串匹配算法求出公共子串,同時還添加了剪枝,但是字樣的暴力匹配效率始終是比較差的,最好的方法還是使用動態規划。
根據上邊公共子序列動態規划的方法分析,其實我們可以發現公共子串和公共子序列非常類似
只是在狀態轉移方程是稍有不同,
事實上,最長公共子串問題也有最優子結構性質。
記:
Xi=﹤x1,⋯,xi﹥即X序列的前i個字符 (1≤i≤m)(前綴)
Yj=﹤y1,⋯,yj﹥即Y序列的前j個字符 (1≤j≤n)(前綴)
假定Z=﹤z1,⋯,zk﹥∈LCS(X , Y)。
-
若xm=yn(最后一個字符相同),則不難用反證法證明:該字符必是X與Y的任一最長公共子串Z(設長度為k)的最后一個字符,即有zk = xm = yn 且顯然有Zk-1∈LCS(Xm-1 , Yn-1)即Z的前綴Zk-1是Xm-1與Yn-1的最長公共子串。此時,問題化歸成求Xm-1與Yn-1的LCS(LCS(X , Y)的長度等於LCS(Xm-1 , Yn-1)的長度加1)。
- 重要的是這里的不同:
-
若xm≠yn,由於zk≠xm與zk≠yn 那么說明之前相同的字符串也不能連接起來,此時的LCS(X,Y) 的長度回歸到0重新找最長的公共子串。
-
所以:關於最長公共子串的動態轉移方程為:
if xm==yn LCS(Xm,Yn)= LCS(Xm-1,Yn-1)+1;
if xm!=yn LCS(Xm,Yn)= 0;
代碼如下:
#include <stdio.h> #include <string.h> /* c[i][j]存儲的是字串1到i位置,字串2到j位置時公共字串的最大長度 if str1[i] == str2[j] c[i][j] = c[i-1][j-1]+1; if str1[i] != str2[j] c[i][j] = 0 */ int lcs(char *str1,char *str2,int len1,int len2,int c[100][100]) { if (str1 == NULL || str2 ==NULL) { return -1;//輸入字符串錯誤 } //初始化記錄dp的二維數組 for (int i = 0; i <= len1; i++) { for (int j = 0; j <= len2; j++) { c[i][j] = 0; } } //dp運算 int max = -1; int col=0,row=0; for (int i = 1; i <= len1; i++) { for (int j = 1; j <= len2; j++) { if(str1[i-1] == str2[j-1]) { c[i][j]=c[i-1][j-1]+1; if(c[i][j]>max) { row = i; col = j; max = c[i][j]; } } else { c[i][j] = 0; } } } //打印出dp數組存儲的內容 for (int i = 0; i <= len1; i++) { for (int j = 0; j <= len2; j++) { printf("%d ",c[i][j]); } printf("\n"); } //打印出公共子串 printf("最長公共子串:"); for (int i = row-max; i<row;i++) { printf("%c",str1[i]); } printf("\n"); return max; } int main(int argc, char **argv) { char str1[] = {"ABCBDAB"}; char str2[] = {"BDCABA"}; int c[100][100]; int len1 = strlen(str1); int len2 = strlen(str2); printf("字符串1:%s\n",str1); printf("字符串2:%s\n",str2); int num = lcs(str1,str2,len1,len2,c); printf("公共子序列的長度:%d\n",num); return 0; }
結果: