一、基本概念
二、穷举算法的时间复杂度
三、分析子问题
四、递推方程和标记函数
五、算法LCS
1. 伪代码
算法:LCS(X,Y,m,n) 输入:序列X及其长度m,序列Y及其长度n 输出:最长公共子序列的长度 for iß0 to m do C[i,0]ß0 for jß0 to n do C[0,j] for iß1 to m do for jß1 to n do if X[i]=Y[j] then C[i,j]=C[i-1,j-1]+1 B[i,j]=’↖’ else if C[i-1,j]>=C[i,j-1] then C[i,j]ßC[i-1,j] B[i,j]ß’’ else C[i,j]ßC[i,j-1] B[i,j]ß’’ |
2. C/C++实现
/********************************************************
Function:LCS
Description:计算两个字符串的最长公共子序列.
Input:字符串s1,s2
Output:最长公共子序列(LCS)的长度
*********************************************************/
int LCS(string s1,string s2){
int m = s1.size()+1;
int n = s2.size()+1;
/*申请二维数组B和C*/
/*
C是优化函数值矩阵
C[i][j]表示s1的前i个元素和s2的前j个元素的最长公共子序列的长度
*/
int** C = new int* [m];
for(int i=0;i<m;i++)
C[i] = new int [n];
/*
B是标记函数值矩阵,记录当前优化函数值是从那个方向来的
B中元素取值解释--0:左,1:上,2:左上
*/
int** B = new int* [m];
for(int i=0;i<m;i++)
B[i] = new int [n];
/*
初始化B和C矩阵的第0行与第0列
C[i][0]表示s1的前i个字符与s2的前0个字符的
公共子序列的长度,显然其值为0;C[0][j]与C[i][j]相同;
*/
for(int i=0;i<m;i++){
C[i][0] = 0;
B[i][0] = 0;
}
for(int j=0;j<n;j++){
C[0][j] = 0;
B[0][j] = 0;
}
/*
动态规划过程,逐步填表
由小到大逐步计算C[i][j]和B[i][j]
*/
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
if(s1[i-1]==s2[j-1]){
C[i][j] = C[i-1][j-1]+1;
B[i][j] = 2;
}else{
if(C[i-1][j]>=C[i][j-1]){
C[i][j] = C[i-1][j];
B[i][j] = 0;
}else{
C[i][j] = C[i][j-1];
B[i][j] = 1;
}
}
}
}
return C[m-1][n-1];
}
3. Python实现
def LCS(s1,s2):
"""
计算两个字符串的最长公共子序列.
Parameters
----------
s1:字符串
s2:字符串
Returns
-------
B:二维Numpy数组
标记函数值组成的矩阵
C:二维Numpy数组
优化函数值组成的矩阵
"""
m = len(s1)+1
n = len(s2)+1;
# 优化函数值矩阵
# C[i][j]表示s1的前i个元素和s2的前j个元素的最长公共子序列的长度
C = np.zeros((m,n))
# 标记函数值矩阵
# 记录当前优化函数值是从那个方向来的
# B中元素取值解释--0:左,1:上,2:左上
B = np.zeros((m,n))
for i in range(1,m):
for j in range(1,n):
if s1[i-1]==s2[j-1]:
C[i][j] = C[i-1][j-1]+1
B[i][j] = 2
else:
if C[i-1][j]>=C[i][j-1]:
C[i][j] = C[i-1][j]
B[i][j] = 0
else:
C[i][j] = C[i][j-1]
B[i][j] = 1
return B,C
4. 时间复杂度与空间复杂度
算法完成时需要将m*n矩阵C和B填充完,而每填充一项是时间是O(1),因此该算法的时间复杂度是O(mn)。该算法需要的额外空间主要是矩阵B和C,其大小均为m*n,因此空间复杂度为O(mn).
六、反向构造LCS
1. 伪代码
算法:struct_sequence 输入:标记矩阵B,B最后一个元素的坐标(i,j) 输出:最长公共子序列 if i=0 or j=0 return if B[i,j]=’↖’ then struct_sequence(B,i-1,j-1) 输出X[i] else if B[i,j]=’’ then struct_sequence(B,i-1,j) else struct_sequence(B,i,j-1) |
2. C/C++实现
/**********************************************************************
Function:struct_sequece
Description:通过给定标记矩阵B,反向求出LCS
Input:标记矩阵B,B右下角坐标(i,j),字符串s1和s2
Output:LCS
***********************************************************************/
void struct_sequence(int ** B,int i,int j,string s1,string s2){
if(i==0||j==0)
return;
switch(B[i][j]){
case 0://左
struct_sequence(B,i-1,j,s1,s2);
break;
case 1://上
struct_sequence(B,i,j-1,s1,s2);
break;
case 2://左上
struct_sequence(B,i-1,j-1,s1,s2);
cout<<s1[i-1]<<" ";
break;
}
}
3.Python实现
def struct_sequence(B,i,j,s1,s2):
"""
根据标记函数值矩阵输出最长公共子序列
Parameters:
-----------
B:二维Numpy数组
标记函数值组成的矩阵
i,j:B矩阵中当前需要判断值的坐标
s1:字符串
s2:字符串
"""
if i==0 or j==0: #base case
return 0
if B[i,j] == 2:
struct_sequence(B,i-1,j-1,s1,s2)
print(s1[i-1],end=' ')
elif B[i,j] == 1:
struct_sequence(B,i,j-1,s1,s2)
elif B[i,j] == 0:
struct_sequence(B,i-1,j,s1,s2)
4.时间复杂度
该算法会沿着某条路径从B矩阵的右下角走到左边界或上边界,最坏情况是从右下角到左上角,时间复杂度为O(m+n).
七、完整代码
1.C/C++代码
#include <iostream>
#include <string>
using namespace std;
/**********************************************************************
Function:struct_sequece
Description:通过给定标记矩阵B,反向求出LCS
Input:标记矩阵B,B右下角坐标(i,j),字符串s1和s2
Output:LCS
***********************************************************************/
void struct_sequence(int ** B,int i,int j,string s1,string s2){
if(i==0||j==0)
return;
switch(B[i][j]){
case 0://左
struct_sequence(B,i-1,j,s1,s2);
break;
case 1://上
struct_sequence(B,i,j-1,s1,s2);
break;
case 2://左上
struct_sequence(B,i-1,j-1,s1,s2);
cout<<s1[i-1]<<" ";
break;
}
}
/********************************************************
Function:LCS
Description:计算两个字符串的最长公共子序列.
Input:字符串s1,s2
Output:最长公共子序列(LCS)的长度
*********************************************************/
int LCS(string s1,string s2){
int m = s1.size()+1;
int n = s2.size()+1;
/*申请二维数组B和C*/
/*
C是优化函数值矩阵
C[i][j]表示s1的前i个元素和s2的前j个元素的最长公共子序列的长度
*/
int** C = new int* [m];
for(int i=0;i<m;i++)
C[i] = new int [n];
/*
B是标记函数值矩阵,记录当前优化函数值是从那个方向来的
B中元素取值解释--0:左,1:上,2:左上
*/
int** B = new int* [m];
for(int i=0;i<m;i++)
B[i] = new int [n];
/*
初始化B和C矩阵的第0行与第0列
C[i][0]表示s1的前i个字符与s2的前0个字符的
公共子序列的长度,显然其值为0;C[0][j]与C[i][j]相同;
*/
for(int i=0;i<m;i++){
C[i][0] = 0;
B[i][0] = 0;
}
for(int j=0;j<n;j++){
C[0][j] = 0;
B[0][j] = 0;
}
/*
动态规划过程,逐步填表
由小到大逐步计算C[i][j]和B[i][j]
*/
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
if(s1[i-1]==s2[j-1]){
C[i][j] = C[i-1][j-1]+1;
B[i][j] = 2;
}else{
if(C[i-1][j]>=C[i][j-1]){
C[i][j] = C[i-1][j];
B[i][j] = 0;
}else{
C[i][j] = C[i][j-1];
B[i][j] = 1;
}
}
}
}
cout<<"最长公共子序列为:";
struct_sequence(B,m-1,n-1,s1,s2);
return C[m-1][n-1];
}
int main()
{
/*测试*/
string s1 = "ABCBDAB";
string s2 = "BDCABA";
cout<<endl<<"最长公共子序列长度为:"<<LCS(s1,s2);
return 0;
}
2.Python代码
import numpy as np
def LCS(s1,s2):
"""
计算两个字符串的最长公共子序列.
Parameters
----------
s1:字符串
s2:字符串
Returns
-------
B:二维Numpy数组
标记函数值组成的矩阵
C:二维Numpy数组
优化函数值组成的矩阵
"""
m = len(s1)+1
n = len(s2)+1;
# 优化函数值矩阵
# C[i][j]表示s1的前i个元素和s2的前j个元素的最长公共子序列的长度
C = np.zeros((m,n))
# 标记函数值矩阵
# 记录当前优化函数值是从那个方向来的
# B中元素取值解释--0:左,1:上,2:左上
B = np.zeros((m,n))
for i in range(1,m):
for j in range(1,n):
if s1[i-1]==s2[j-1]:
C[i][j] = C[i-1][j-1]+1
B[i][j] = 2
else:
if C[i-1][j]>=C[i][j-1]:
C[i][j] = C[i-1][j]
B[i][j] = 0
else:
C[i][j] = C[i][j-1]
B[i][j] = 1
return B,C
def struct_sequence(B,i,j,s1,s2):
"""
根据标记函数值矩阵输出最长公共子序列
Parameters:
-----------
B:二维Numpy数组
标记函数值组成的矩阵
i,j:B矩阵中当前需要判断值的坐标
s1:字符串
s2:字符串
"""
if i==0 or j==0: #base case
return 0
if B[i,j] == 2:
struct_sequence(B,i-1,j-1,s1,s2)
print(s1[i-1],end=' ')
elif B[i,j] == 1:
struct_sequence(B,i,j-1,s1,s2)
elif B[i,j] == 0:
struct_sequence(B,i-1,j,s1,s2)
'''测试代码'''
s1 = "ABCBDAB"
s2 = "BDCABA"
B,C = LCS(s1,s2)
print("LCS的长度为:%d"%C[-1,-1])
struct_sequence(B,B.shape[0]-1,B.shape[1]-1,s1,s2)