在做OJ題目的時候,經常會用到字符串的處理。例如,比較二個字符串相似度。這篇文章介紹一下求兩個字符串的最長公共子序列。
一個字符串的子序列,是指從該字符串中去掉任意多個字符后剩下的字符在不改變順序的情況下組成的新字符串。
最長公共子序列,是指多個字符串可具有的長度最大的公共的子序列。
(1)遞歸方法求最長公共子序列的長度
1)設有字符串a[0...n],b[0...m],下面就是遞推公式。
當數組a和b對應位置字符相同時,則直接求解下一個位置;當不同時取兩種情況中的較大數值。
2)代碼如下:
#include<stdio.h> #include<string.h> char a[30],b[30]; int lena,lenb; int LCS(int,int); ///兩個參數分別表示數組a的下標和數組b的下標 int main() { strcpy(a,"ABCBDAB"); strcpy(b,"BDCABA"); lena=strlen(a); lenb=strlen(b); printf("%d\n",LCS(0,0)); return 0; } int LCS(int i,int j) { if(i>=lena || j>=lenb) return 0; if(a[i]==b[j]) return 1+LCS(i+1,j+1); else return LCS(i+1,j)>LCS(i,j+1)? LCS(i+1,j):LCS(i,j+1); }
用遞歸的方法優點是編程簡單,容易理解。缺點是效率不高,有大量的重復執行遞歸調用,而且只能求出最大公共子序列的長度,求不出具體的最大公共子序列。
(2)動態規划求最長公共子序列的長度
動態規划采用二維數組來標識中間計算結果,避免重復的計算來提高效率。
1)最長公共子序列的長度的動態規划方程
設有字符串a[0...n],b[0...m],下面就是遞推公式。字符串a對應的是二維數組num的行,字符串b對應的是二維數組num的列。
另外,采用二維數組flag來記錄下標i和j的走向。數字"1"表示,斜向下;數字"2"表示,水平向右;數字"3"表示,豎直向下。這樣便於以后的求解最長公共子序列。
(2)求解公共子序列代碼
#include<stdio.h> #include<string.h> char a[500],b[500]; char num[501][501]; ///記錄中間結果的數組 char flag[501][501]; ///標記數組,用於標識下標的走向,構造出公共子序列 void LCS(); ///動態規划求解 void getLCS(); ///采用倒推方式求最長公共子序列 int main() { int i; strcpy(a,"ABCBDAB"); strcpy(b,"BDCABA"); memset(num,0,sizeof(num)); memset(flag,0,sizeof(flag)); LCS(); printf("%d\n",num[strlen(a)][strlen(b)]); getLCS(); return 0; } void LCS() { int i,j; for(i=1;i<=strlen(a);i++) { for(j=1;j<=strlen(b);j++) { if(a[i-1]==b[j-1]) ///注意這里的下標是i-1與j-1 { num[i][j]=num[i-1][j-1]+1; flag[i][j]=1; ///斜向下標記 } else if(num[i][j-1]>num[i-1][j]) { num[i][j]=num[i][j-1]; flag[i][j]=2; ///向右標記 } else { num[i][j]=num[i-1][j]; flag[i][j]=3; ///向下標記 } } } } void getLCS() { char res[500]; int i=strlen(a); int j=strlen(b); int k=0; ///用於保存結果的數組標志位 while(i>0 && j>0) { if(flag[i][j]==1) ///如果是斜向下標記 { res[k]=a[i-1]; k++; i--; j--; } else if(flag[i][j]==2) ///如果是斜向右標記 j--; else if(flag[i][j]==3) ///如果是斜向下標記 i--; } for(i=k-1;i>=0;i--) printf("%c",res[i]); }
(3)圖示
類似有: 字符串相似度算法 遞歸與動態規划求解分析
參考:
http://www.cnblogs.com/huangxincheng/archive/2012/11/11/2764625.html