动态规划法求解最长公共子序列(含Java代码)
公共子序列问题徐康123183
一.算法设计
假设有两个序列X和Y,假设X和Y分别有m和n个元素,则建立一个二维数组C[(m+1)*(n+1)],记录X i与Y j的LCS的长度。将C[i,j]分为三种情况:
若i =0 或j =0时,C[i,j]=0;
若i,j>0且X[i]=Y[j],C[i,j]=C[i-1,j-1]+1;
若i,j>0且X[i] Y[j],C[i,j]=max{C[i-1,j],C[i,j-1]}。
再使用一个m*n的二维数组b,b[i,j]记录C[i,j]的来向:
若X[i]=Y[j],则B[i,j]中记入“↖”,记此时b[i,j] = 1;
若X[i] Y[j]且C[i-1,j] > C[i,j-1],则b[i,j]中记入“↑”,记此时B[i,j] = 2;
若X[i] Y[j]且C[i-1,j] < C[i,j-1],则b[i,j]中记入“←”,记此时B[i,j] = 3;
若X[i]Y[j]且C[i-1,j] = C[i,j-1],则b[i,j]中记入“↑”或“←”,记此时B[i,j] = 4;
得到了两个数组C[]和B[],设计递归输出LCS(X,Y)的算法:
LCS_Output(Direction[][], X[], i, j, len,LCS[]){
If i=0 or j=0 将LCS[]保存至集合LCS_SET中
then return;
If b[i,j]=1 then /*X[i]=Y[j]*/
{LCS_Output(b,X,i-1,j-1);
将X[i]保存至LCS[len-i];}
else if b[i,j]=2 then /*X[i]≠Y[j]且C[i-1,j]>C[i,j-1]*/
LCS_Output(b,X,i-1,j)
else if b[i,j]=3 then /*X[i]≠Y[j]且C[i-1,j] else if b[i,j]=4 then /*X[i]≠Y[j]且C[i-1,j]=C[i,j-1]*/ LCS_Output(b,X,i-1,j) LCS_Output(b,X,i,j-1) } 二.算法时间复杂度分析 由上述对算法的分析得知,求辅助数组C 和B 所消耗的时间复杂度为O (mn ),而查找所有的公共子序列的时间复杂度取决于所遍历的路径,而路径是由算法递归的方向决定的。显然,最好的情况是m=n 并且B 中的所有值都为1(按斜对角线方向搜索),此时时间复杂度为O (n )。当X 和Y 序列不存在公共子序列时为算法的最坏情况,因为此时C 数组的所有元素都为0,B 在每一个节点都要沿着两个不同的方向搜索,即每次都要调用两次LCS_Output,当调用到i=0或j=0时返回,直到搜索完整个m*n 数组才结束。 该时间复杂度就是计算从点(m,n )到i=0或j=0的所有路径。建立如上图的直角坐标系,设点S (m ,n ),x 轴上的坐标点P 1(1,0) 到Pm(m,0),y 轴上的系列坐标点Q 1(0,1) 到Qn(0,n)。 因为j i Q P 和是搜索路径的边界上的点,点1+i P 不能直接到达点i P ,点1+j Q 也不能直接到达j Q ,所以点),(n m S 到m i P P P P ,...,,...,,21和n j Q Q Q Q ,...,,...,,21的路径数等价于),(n m S 到点 )1,('),...,1,('),...,1,2('),1,1('21m P i P P P m i 和点),1('),...,,1('),...,2,1('),1,1('21n Q j Q Q Q n j 的路径数,又因为 点),(n m 到),(j i 路径数为i n j i n m C ---+,设总路径数为t ,则有 ()() m m n n m n n m n n m n m m n n m n n n m m n m m n m m n m n n m n m j j m j m n n i i n i n m C C C C C C C C C C C C C C C C t ++-+--+--+--+---+--+---+--+=--+-=--+-===+=+=+++++++++=+=∑∑11111110 11231201123121 11 1...... 三.程序流程图如下所示 四.程序源码 package Homework2; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.HashSet; import java.util.Set; public class LCS { int C[][]; int B[][]; Set LCS_SET = new HashSet(); //使用集合去重 public int LCSLength(char X[], char Y[]) { //返回公共子序列长度int m = X.length; int n = Y.length; C = new int[X.length][Y.length]; B = new int[X.length][Y.length]; 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; } for (int i = 1; i < m; i++) { for (int j = 1; j < n; j++) { if (X[i] == Y[j]) { C[i][j] = C[i - 1][j - 1] + 1; B[i][j] = 1; } else if (C[i - 1][j] > C[i][j - 1]) { C[i][j] = C[i - 1][j]; B[i][j] = 2; } else if (C[i - 1][j] < C[i][j - 1]) { C[i][j] = C[i][j - 1]; B[i][j] = 3; } else { C[i][j] = C[i - 1][j]; B[i][j] = 4; } } } return C[m - 1][n - 1]; } public void LCS_Output(int Direction[][], char X[], int i, int j, int len, char LCS[]) { int lcslen = len; if (i == 0 || j == 0) { LCS_SET.add(String.valueOf(LCS)); return; } if (B[i][j] == 1) { LCS[len - 1] = X[i]; len--; LCS_Output(B, X, i - 1, j - 1, len, LCS); } else if (B[i][j] == 2) { LCS_Output(B, X, i - 1, j, len, LCS); } else if (B[i][j] == 3) { LCS_Output(B, X, i, j - 1, len, LCS); } else if (B[i][j] == 4) { LCS_Output(B, X, i - 1, j, len, LCS); LCS_Output(B, X, i, j - 1, len, LCS); } } /** *@param args */ public static void main(String[] args) throws IOException { // TODO Auto-generated method stub char X[], Y[]; BufferedReader buf = null; buf = new BufferedReader(new InputStreamReader(System.in)); System.out.println("请输入一个序列"); X = buf.readLine().toCharArray(); char X_temp[] = new char[X.length + 1]; for (int i = 0; i < X.length; i++) { X_temp[0] = ' '; X_temp[i + 1] = X[i]; } System.out.println("请输入一个序列"); Y = buf.readLine().toCharArray(); char Y_temp[] = new char[Y.length + 1]; for (int i = 0; i < Y.length; i++) { Y_temp[0] = ' '; Y_temp[i + 1] = Y[i]; } System.out.print("X="); for (char x : X) { System.out.print(x); } System.out.println(); System.out.print("Y="); for (char y : Y) { System.out.print(y); } System.out.println(); LCS lcs = new LCS(); int len = lcs.LCSLength(X_temp, Y_temp); char LCS[] = new char[len]; int m = X.length; int n = Y.length; System.out.println("最长公共子序列长度为:" + len); // 输出最长子序列长度System.out.print("最长公共子序列有:"); lcs.LCS_Output(lcs.B, X_temp, m, n, len, LCS); System.out.print(lcs.LCS_SET); // 输出子序列集合中的元素} } 五.运行结果截图 1.输入abc和acb 2.输入asd和xcv 3.输入ABCBDAB 和BDCABA 经检查输出结果均正确! 算法作业: LCS 问 题 作业要求:设计一个算法求出两个序列的所有LCS ,分析最坏情况,用“会计方法”证明利用b[i][j]求出 所有LCS 的算法在最坏情况下时间复杂度为)(m m n C O + 1、 算法思路: 根据最长公共子序列问题的性质,即经过分解后的子问题具有高度重复性,并且具有最优子结构性质,采用动态规划法求解问题。设X={x 1, x 2, … , x n }, Y={y 1, y 2, … , y m }, 首先引入二维数组C[i][j]记录X i 和Y j 的LCS 的长度,定义C[i][j]如下: { j i j y i 且x ,i,j ]][j C[i j y i x j i j i C j i C j i C 00001110,]},1][[],][1[max{]][[===>+--≠>--=或,且 为了构造出LCS ,还需要使用一个二维数组b[m][n],b[i][j]记录C[i][j]是通过哪个子问题的值求得 的,以决定搜索的方向,欲求出所有的LCS ,定义数组b 如下: 设1-对角线方向;2-向上;3-向左;4-向上或向左 若X[i]=Y[j],b[i][j] = 1, 若C[i-1][j][i][j-1], 则b[i][j] = 3, 若C[i-1][j]=[i][j-1], 则b[i][j] = 4, 根据以上辅助数组C 和b 的定义,算法首先需要求出这两个数组, C[m][n]中记录的最长公共子序列的长度,b 中记录了查找子序列元素的搜索方向。 利用C 和b 的信息,Find_All_LCS 可以采用回溯法求出所有的LCS 。基本思路如下:使用一个辅助数组记录每次调用Find_All_LCS 得到的LCS 中的元素,每次递归调用一次Find_All_LCS ,进入一个新的执行层,首先要判断当前处理的两个子序列长度是否大于等于0 ,若不满足,则该层的递归结束,返回上一层;然后再判断当前得到的子序列是否等于数组C 中求出的最长公共子序列长度,若等于,则说明算法执行到此已经得到一个LCS ,按序输出;若不等于,此时根据数组b 中记录的搜索方向继续搜索,特别要说明的是,当b[i][j]=4时,即要向上或向左,需要对这两个方向分别调用Find_All_LCS ,保证沿着这两个方向上LCS 元素不被漏掉,都可以搜索到;若b[i][j]=1,即沿对角线方向搜索前进时,此时元素X[i]为LCS 中的元素,存放至辅助数组中去,同时将当前已经求得的LCS 长度增1,当递归调用Find_All_LCS 从b[i][j]=1处时,需要回溯一步,搜索其它路径上可能为LCS 中的元素。当所有的可能路径都已经搜索完,算法结束。 对于某些情况会输出重复的LCS ,这是因为算法在沿不同路径搜索时可能会出现相同的LCS 序列。 2、 时间复杂度分析 由上述对Find_All_LCS 算法的分析可知,求出所有的LCS 实际上是根据搜索的方向信息遍历所有的路径找出满足条件的元素集合。因此,除求解辅助数组C 和b 所用的O(mn+m+n)的执行时间外,Find_All_LCS 的时间复杂度取决于所遍历路径数。而路径数是由搜索方向决定的。显然算法在最好的情况下,即m=n 并且b 中所有的值都指示沿着对角线方向搜索,时间复杂度为O(n). 相反,当X 和Y 序列不存在公共子序列时为算法的最坏情况,此时C 中所有值都等于0,数组b 中所有的值都指示要分别沿两个不同的方向(向左或向上)搜索,这种情况下每处理一次X[i],Y[j]时总是要沿两个方向分别调用Find_All_LCS ,遇到i=0或j=0时返回,直到搜索完所有的可能路径才结束,最坏情况下的搜索矩阵如下图所示: 第四章动态规划 §1 引言 1.1 动态规划的发展及研究内容 动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。20 世纪50 年代初R. E. Bellman 等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优性原理(principle of optimality),把多阶段过程转化为一系列单阶段问题,逐个求解,创立了解决这类过程优化问题的新方法—动态规划。1957 年出版了他的名著《Dynamic Programming》,这是该领域的第一本著作。 动态规划问世以来,在经济管理、生产调度、工程技术和最优控制等方面得到了广泛的应用。例如最短路线、库存管理、资源分配、设备更新、排序、装载等问题,用动态规划方法比用其它方法求解更为方便。 虽然动态规划主要用于求解以时间划分阶段的动态过程的优化问题,但是一些与时间无关的静态规划(如线性规划、非线性规划),只要人为地引进时间因素,把它视为多阶段决策过程,也可以用动态规划方法方便地求解。 应指出,动态规划是求解某类问题的一种方法,是考察问题的一种途径,而不是一种特殊算法(如线性规划是一种算法)。因而,它不象线性规划那样有一个标准的数学表达式和明确定义的一组规则,而必须对具体问题进行具体分析处理。因此,在学习时,除了要对基本概念和方法正确理解外,应以丰富的想象力去建立模型,用创造性的技巧去求解。 例1 最短路线问题 图1 是一个线路网,连线上的数字表示两点之间的距离(或费用)。试寻求一条由A 到G距离最短(或费用最省)的路线。 图1 最短路线问题 例2 生产计划问题 工厂生产某种产品,每单位(千件)的成本为1(千元),每次开工的固定成本为3 (千元),工厂每季度的最大生产能力为6(千件)。经调查,市场对该产品的需求量第一、二、三、四季度分别为2,3,2,4(千件)。如果工厂在第一、二季度将全年的需求都生产出来,自然可以降低成本(少付固定成本费),但是对于第三、四季度才能上市的产品需付存储费,每季每千件的存储费为0.5(千元)。还规定年初和年末这种产品均无库存。试制定一个生产计划,即安排每个季度的产量,使一年的总费用(生产成本和存储费)最少。 1.2 决策过程的分类根据过程的时间变量是离散的还是连续的,分为离散时间 决策过程(discrete-time -56-最长公共子序列问题(最)
(数学建模教材)4第四章动态规划
求最长子序列的长度