1、先科普下最長公共子序列 & 最長公共子串的區別:
找兩個字符串的最長公共子串,這個子串要求在原字符串中是連續的。而最長公共子序列則並不要求連續。
2、最長公共子串
其實這是一個序貫決策問題,可以用動態規划來求解。我們采用一個二維矩陣來記錄中間的結果。這個二維矩陣怎么構造呢?直接舉個例子吧:"bab"和"caba"(當然我們現在一眼就可以看出來最長公共子串是"ba"或"ab")
b a b
c 0 0 0
a 0 1 0
b 1 0 1
a 0 1 0
我們看矩陣的斜對角線最長的那個就能找出最長公共子串。
不過在二維矩陣上找最長的由1組成的斜對角線也是件麻煩費時的事,下面改進:當要在矩陣是填1時讓它等於其左上角元素加1。
b a b
c 0 0 0
a 0 1 0
b 1 0 2
a 0 2 0
這樣矩陣中的最大元素就是 最長公共子串的長度。
在構造這個二維矩陣的過程中由於得出矩陣的某一行后其上一行就沒用了,所以實際上在程序中可以用一維數組來代替這個矩陣。
2.1 代碼如下:
public
class
LCString2 {
public
static
void
getLCString(
char
[] str1,
char
[] str2) {
int
i, j;
int
len1, len2;
len1 = str1.length;
len2 = str2.length;
int
maxLen = len1 > len2 ? len1 : len2;
int
[] max =
new
int
[maxLen];
int
[] maxIndex =
new
int
[maxLen];
int
[] c =
new
int
[maxLen];
// 記錄對角線上的相等值的個數
for
(i =
0
; i < len2; i++) {
for
(j = len1 -
1
; j >=
0
; j--) {
if
(str2[i] == str1[j]) {
if
((i ==
0
) || (j ==
0
))
c[j] =
1
;
else
c[j] = c[j -
1
] +
1
;
}
else
{
c[j] =
0
;
}
if
(c[j] > max[
0
]) {
// 如果是大於那暫時只有一個是最長的,而且要把后面的清0;
max[
0
] = c[j];
// 記錄對角線元素的最大值,之后在遍歷時用作提取子串的長度
maxIndex[
0
] = j;
// 記錄對角線元素最大值的位置
for
(
int
k =
1
; k < maxLen; k++) {
max[k] =
0
;
maxIndex[k] =
0
;
}
}
else
if
(c[j] == max[
0
]) {
// 有多個是相同長度的子串
for
(
int
k =
1
; k < maxLen; k++) {
if
(max[k] ==
0
) {
max[k] = c[j];
maxIndex[k] = j;
break
;
// 在后面加一個就要退出循環了
}
}
}
}
}
for
(j =
0
; j < maxLen; j++) {
if
(max[j] >
0
) {
System.out.println(
"第"
+ (j +
1
) +
"個公共子串:"
);
for
(i = maxIndex[j] - max[j] +
1
; i <= maxIndex[j]; i++)
System.out.print(str1[i]);
System.out.println(
" "
);
}
}
}
public
static
void
main(String[] args) {
String str1 =
new
String(
"123456abcd567"
);
String str2 =
new
String(
"234dddabc45678"
);
// String str1 = new String("aab12345678cde");
// String str2 = new String("ab1234yb1234567");
getLCString(str1.toCharArray(), str2.toCharArray());
}
}
ref:
http://blog.csdn.net/rabbitbug/article/details/1740557
最大子序列、最長遞增子序列、最長公共子串、最長公共子序列、字符串編輯距離
http://www.cnblogs.com/zhangchaoyang/articles/2012070.html
2.2 其實 awk 寫起來也很容易:
echo
"123456abcd567
234dddabc45678
"|awk -vFS="
" 'NR==1{str=$0}NR==2{N=NF;for(n=0;n++<N;){s="
";for(t=n;t<=N;t++){s=s"
"$t;
if
(index(str,s)){a[n]=t-n;b[n]=s;
if
(m<=a[n])m=a[n]}
else
{t=N}}}}END{
for
(n=0;n++<N;)
if
(a[n]==m)print b[n]}'
ref:http://bbs.chinaunix.net/thread-4055834-2-1.html
#!/usr/bin/perl
use
strict;
use
warnings;
my
$str1
=
"123456abcd567"
;
my
$str2
=
"234dddabc45678"
;
my
$str
=
$str1
.
"\n"
.
$str2
;
my
(
@substr
,
@result
);
$str
=~ /(.+)(?=.*\n.*\1)(
*PRUNE
)(?{
push
@substr
,$1})(
*F
)/;
@substr
=
sort
{
length
(
$b
) <=>
length
(
$a
) }
@substr
;
@result
=
grep
{
length
==
length
$substr
[0] }
@substr
;
print
"@result\n"
;
ref: http://bbs.chinaunix.net/thread-1333575-7-1.html
3、最長公共子序列
import
java.util.Random;
public
class
LCS {
public
static
void
main(String[] args) {
// 隨機生成字符串
// String x = GetRandomStrings(substringLength1);
// String y = GetRandomStrings(substringLength2);
String x =
"a1b2c3"
;
String y =
"1a1wbz2c123a1b2c123"
;
// 設置字符串長度
int
substringLength1 = x.length();
int
substringLength2 = y.length();
// 具體大小可自行設置
// 構造二維數組記錄子問題x[i]和y[i]的LCS的長度
int
[][] opt =
new
int
[substringLength1 +
1
][substringLength2 +
1
];
// 從后向前,動態規划計算所有子問題。也可從前到后。
for
(
int
i = substringLength1 -
1
; i >=
0
; i--) {
for
(
int
j = substringLength2 -
1
; j >=
0
; j--) {
if
(x.charAt(i) == y.charAt(j))
opt[i][j] = opt[i +
1
][j +
1
] +
1
;
// 狀態轉移方程
else
opt[i][j] = Math.max(opt[i +
1
][j], opt[i][j +
1
]);
// 狀態轉移方程
}
}
System.out.println(
"substring1:"
+ x);
System.out.println(
"substring2:"
+ y);
System.out.print(
"LCS:"
);
int
i =
0
, j =
0
;
while
(i < substringLength1 && j < substringLength2) {
if
(x.charAt(i) == y.charAt(j)) {
System.out.print(x.charAt(i));
i++;
j++;
}
else
if
(opt[i +
1
][j] >= opt[i][j +
1
])
i++;
else
j++;
}
}
// 取得定長隨機字符串
public
static
String GetRandomStrings(
int
length) {
StringBuffer buffer =
new
StringBuffer(
"abcdefghijklmnopqrstuvwxyz"
);
StringBuffer sb =
new
StringBuffer();
Random r =
new
Random();
int
range = buffer.length();
for
(
int
i =
0
; i < length; i++) {
sb.append(buffer.charAt(r.nextInt(range)));
}
return
sb.toString();
}
}
REF:
字符串最大公共子序列以及最大公共子串問題
http://gongqi.iteye.com/blog/1517447
動態規划算法解最長公共子序列LCS問題
http://blog.csdn.net/v_JULY_v/article/details/6110269
最長公共子序列求解:遞歸與動態規划方法
在做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)圖示

字符串相似度算法 遞歸與動態規划求解分析
1.概念
編輯距離,指的是兩個字符串之間,由一個轉換成另一個所需的最少編輯操作次數。許可的編輯操作包括:(1)將一個字符替換成另一個字符,(2)插入一個字符,(3)刪除一個字符。
相似度,等於“編輯距離+1”的倒數。
2.分析
設有字符串a[0...n],b[0...m]。
(1)當a[i]=b[j]時,說明這時候不需要編輯操作。編輯距離保持,即f(i,j)=f(i-1,j-1)
(2)當a[i]!=b[j]時,可以有三種編輯操作。
其中刪除和插入操作,只對一個下標i或者j產生影響。如在下圖中,當前匹配到(t1,t2)處,如果采用刪除'g',只改變t1的下標。

其中替換操作,會對2個下標都產生影響。如在下圖中,當前匹配到(t1,t2)處,如果將'g'替換成'm',則下次就需要執行(t1+1,t2+1)處。

所以可以推導出下面就是遞推公式。

3.用遞歸求解代碼
#include<stdio.h>
#include<string.h>
char *a="abcgh";
char *b="aecdgh";
int min(int t1,int t2,int t3) ///求三個數的最小值
{
int min;
min=t1<t2?t1:t2;
min=min<t3?min:t3;
return min;
}
int calculate(int i,int enda,int j,int endb)
{
int t1,t2,t3;
if(i>enda) ///i指示超過a[]的范圍時
{
if(j>endb)
return 0;
else
return endb-j+1;
}
if(j>endb) ///j指示超過b[]的范圍時
{
if(i>enda)
return 0;
else
return enda-i+1;
}
if(*(a+i) == *(b+j)) ///如果兩個相等,則直接求下一個位置
return calculate(i+1,enda,j+1,endb);
else
{
t1=calculate(i+1,enda,j,endb); ///刪除a[i]或在b中插入a[i]
t2=calculate(i,enda,j+1,endb); ///刪除b[j]或在a中插入b[j]
t3=calculate(i+1,enda,j+1,endb); ///替換
return 1+min(t1,t2,t3);
}
}
int main()
{
int dis=calculate(0,strlen(a)-1,0,strlen(b)-1);
printf("dis=%d",dis);
return 1;
}
4.用動態規划求解代碼
#include<stdio.h>
#include<string.h>
#define MAX 1000
int dp[MAX][MAX]; ///dp[i][j]表示當前a[0..i-1]與b[0..j-1]的編輯距離
char *a="agbgd";
char *b="ggd";
int min(int t1,int t2,int t3) ///求三個數的最小值
{
int min;
min=t1<t2?t1:t2;
min=min<t3?min:t3;
return min;
}
int main()
{
int i,j;
int lena=strlen(a),lenb=strlen(b);
memset(dp,0,sizeof(dp));
for(i=0;i<=lena;i++) ///a作為行,當b為空串時
dp[0][i]=i;
for(i=0;i<=lenb;i++) ///b作為列,當a為空串時
dp[i][0]=i;
for(i=1;i<=lena;i++)
{
for(j=1;j<=lenb;j++)
{
if(*(a+i)==*(b+j)) ///相等時
dp[i][j]=dp[i-1][j-1];
else
dp[i][j]=1+min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]); ///不相等時,取三種可能操作的最小數值+1
}
}
printf("編輯距離為:dis=%d\n",dp[lena][lenb]);
return ;
}
來源:http://blog.csdn.net/cangchen/article/details/45045541

