題目:如果字串一的所有字元按其在字串中的順序出現在另外乙個字串二中,則字串一稱之為字串二的子串。注意,並不要求子串(字串一)的字元必須連續出現在字串二中。請編寫乙個函式,輸入兩個字串,求它們的最長公共子串行,並列印出最長公共子串行。
例如:輸入兩個字串bdcaba和abcbdab,字串bcba和bdab都是是它們的最長公共子串行,則輸出它們的長度4,並列印任意乙個子串行。
分析:求最長公共子串行(longest common subsequence, lcs)是一道非常經典的動態規劃題,因此一些重視演算法的公司像microstrategy都把它當作面試題。
考慮最長公共子串行問題如何分解成子問題,設a=「a0,a1,…,am-1」,b=「b0,b1,…,bn-1」,並z=「z0,z1,…,zk-1」為它們的最長公共子串行。不難證明有以下性質:
(1) 如果am-1==bn-1,則zk-1=am-1=bn-1,且「z0,z1,…,zk-2」是「a0,a1,…,am-2」和「b0,b1,…,bn-2」的乙個最長公共子串行;
(2) 如果am-1!=bn-1,則若zk-1!=am-1時,蘊涵「z0,z1,…,zk-1」是「a0,a1,…,am-2」和「b0,b1,…,bn-1」的乙個最長公共子串行;
(3) 如果am-1!=bn-1,則若zk-1!=bn-1時,蘊涵「z0,z1,…,zk-1」是「a0,a1,…,am-1」和「b0,b1,…,bn-2」的乙個最長公共子串行。
這樣,在找a和b的公共子串行時,如果有am-1==bn-1,則進一步解決乙個子問題,找「a0,a1,…,am-2」和「b0,b1,…,bm-2」的乙個最長公共子串行;如果am-1!=bn-1,則要解決兩個子問題,找出「a0,a1,…,am-2」和「b0,b1,…,bn-1」的乙個最長公共子串行和找出「a0,a1,…,am-1」和「b0,b1,…,bn-2」的乙個最長公共子串行,再取兩者中較長者作為a和b的最長公共子串行。
求解:引進乙個二維陣列c,用c[i][j]記錄x[i]與y[j] 的lcs 的長度,b[i][j]記錄c[i][j]是通過哪乙個子問題的值求得的,以決定輸出最長公共字串時搜尋的方向。
我們是自底向上進行遞推計算,那麼在計算c[i,j]之前,c[i-1][j-1],c[i-1][j]與c[i][j-1]均已計算出來。此時我們根據x[i] == y[j]還是x[i] != y[j],就可以計算出c[i][j]。
問題的遞迴式寫成:
回溯輸出最長公共子串行過程:
演算法分析:
由於每次呼叫至少向上或向左(或向上向左同時)移動一步,故最多呼叫(m + n)次就會遇到i = 0或j = 0的情況,此時開始返回。返回時與遞迴呼叫時方向相反,步數相同,故演算法時間複雜度為θ(m + n)。
完整的實現**如下:
[cpp]view plain
copy
/**
找出兩個字串的最長公共子串行的長度
** author :liuzhiwei
** data :2011-08-15
**/#include "stdio.h"
#include "string.h"
#include "stdlib.h"
intlcslength(
char
* str1,
char
* str2,
int**b)
else
if(c[i-1][j]>c[i][j-1])
else
} } /*
for(i= 0; i
*/len=c[length1][length2];
for(i = 0; i
//釋放動態申請的二維陣列
delete
c[i];
delete
c;
return
len;
} void
printlcs(
int**b,
char
*str1,
inti,
intj)
else
if(b[i][j]==1)
printlcs(b, str1, i-1, j);
else
printlcs(b, str1, i, j-1);
} int
main(
void
)
程式的效果圖如下:
第二種方法為:
[cpp]view plain
copy
/**
找出兩個字串的最長公共子串行的長度
** author :liuzhiwei
** data :2011-08-15
**/#include "stdio.h"
#include "string.h"
#include "stdlib.h"
intlcslength(
char
* str1,
char
* str2)
//求得兩個字串的最大公共子串長度並輸出公共子串
} //輸出公共子串
char
s[100];
intlen,k;
len=k=c[length1][length2];
s[k--]='\0'
; i=length1,j=length2;
while
(i>0 && j>0)
else
if(c[i-1][j]
j--;
else
i--;
} printf("最長公共子串為:"
);
puts(s);
for(i = 0; i
//釋放動態申請的二維陣列
delete
c[i];
delete
c;
return
len;
} int
main(
void
)
問題拓展:設a、b、c是三個長為n的字串,它們取自同一常數大小的字母表。設計乙個找出三個串的最長公共子串行的o(n^3)的時間演算法。
思路:跟上面的求2個字串的公共子串行是一樣的思路,只不過這裡需要動態申請乙個三維的陣列,三個字串的尾字元不同的時候,考慮的情況多一些而已。
[cpp]view plain
copy
/**
找出三個字串的最長公共子串行的長度
** author :liuzhiwei
** data :2011-08-15
**/#include "stdio.h"
#include "string.h"
#include "stdlib.h"
intmax1(
intm,
intn)
intmax2(
intx,
inty,
intz,
intk,
intm,
intn)
intlcslength(
char
* str1,
char
* str2,
char
* str3)
//求得三個字串的最大公共子串行長度並輸出公共子串行
for(i = 0; i
for(i = 0; i
for(i = 0; i
for(i = 1; i
} } }
len=c[length1][length2][length3];
for(i = 1; i
//釋放動態申請的三維陣列
delete
c;
return
len;
} int
main(
void
)
程式的效果圖如下:
程式設計演算法練習
題目1 乙個台階總共有n級,如果一次可以跳1級,也可以跳2級。求總共有多少總跳法,並分析演算法的時間複雜度。分析 這道題最近經常出現,包括microstrategy等比較重視演算法的公司都曾先後選用過個這道題作為面試題或者筆試題。首先我們考慮最簡單的情況。如果只有1級台階,那顯然只有一種跳法。如果有...
程式設計演算法練習
題目 寫乙個函式,求兩個整數的之和,要求在函式體內不得使用 分析 這又是一道考察發散思維的很有意思的題目。當我們習以為常的東西被限制使用的時候,如何突破常規去思考,就是解決這個問題的關鍵所在。看到的這個題目,我的第一反應是傻眼了,四則運算都不能用,那還能用什麼啊?可是問題總是要解決的,只能開啟思路去...
程式設計演算法練習 1 12
題目 求1 2 n,要求不能使用乘除法 for while if else switch case等關鍵字以及條件判斷語句 a?b c 分析 這道題沒有多少實際意義,因為在軟體開發中不會有這麼 的限制。但這道題卻能有效地考查發散思維能力,而發散思維能力能反映出對程式設計相關技術理解的深刻程度。通常求...