1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > 动态规划算法下的序列问题:最长公共子序列问题和最大子段和问题

动态规划算法下的序列问题:最长公共子序列问题和最大子段和问题

时间:2019-05-09 22:14:21

相关推荐

动态规划算法下的序列问题:最长公共子序列问题和最大子段和问题

本篇主要介绍最长公共子序列问题和最大子段和问题

1、最长公共子序列问题

什么是最长公共子序列

给定一个序列X=<x1,x2,x3,x4…,xm>,另一个序列Z=<z1,z2,z3,z4…,zk>,若存在一个严格递增的X的下标序列<i1,i2,i3,…,ik>对所有的1,2,3,…,k,都满足x(ik)=zk,则称Z是X的子序列

比如说:比如Z=<B,C,D,B>是X=<A,B,C,B,D,A,B>的子序列

如果Z既是X的子序列,又是Y的子序列,则称Z为X和Y的公共子序列

比如说

X:A B C B D A B

Y:B D C A B A

形如这样的就是最长公共子序列问题

那我们接下来用动态规划算法解最长公共子序列问题

用动态规划算法解最长公共子序列问题

我们可以把x1,x2,x3,…,xi和y1,y2,y3,…,yi共同组合一个子问题

然后我们看一下子问题间的依赖关系

假设大长方形框是一个原问题,左边是x序列,上边是y序列

若xm=yn,我们可以把原问题归结成xm-1和yn-1组合的子问题。

若xm!=yn,我们可以把原问题归结成xm-1和yn组合的子问题

或者归结成xm和yn-1组合的子问题

我们看到啊,在xi和yi上,如果能找到一个最长的公共子序列,xi+1和yi+1就可以在它的基础上继续寻找。对于原问题而言,子问题的最优解是包含在了原问题当中。因此这种算法是满足依赖关系。

接下来我们来看下递推方程

令C[i,j]:xi与yi的公共子序列长度

如果xi=yi,那么C[i,j]=C[i-1,j-1]+1

如果x1!=y1,那么我们就在C[i,j-1]和C[i-1,j]中找到最大值赋给C[i,j]

当然若i=0或j=0,C[i,j]=0。因为此时有一个序列没有长度,所以就没有公共子序列

下面来看一下追踪解:

首先我们先考虑标记函数

设B[i,j]是一个标记函数

我们需要从c[0][0]开始填表,填到c[m-1][n-1],所得到的c[m-1][n-1]就是LCS的长度

在对应字符相等的时候,用↖标记

在p1 < p2的时候,用←标记

在p1 >= p2的时候,用↑标记

我们看一个例子:

比如说求ABCBDAB和BDCABA的最长公共子序列长度:

我们分析下。

首先C[0,j]和C[i,0]=0

xi和yj进行比较,如果xi=yj。那么C[i,j]=C[i-1,j-1]+1,我们看一下C[1,4]=C[0,3]+1,然后标记斜向上的箭头

x1和y1进行比较,因为C[i-1,j]>=C[i,j-1],则让C[i,j]=C[i-1,j],然后标记向上的箭头

x1和y5进行比较,因为C[i,j-1]>C[i-1,j],则让C[i,j]=C[i,j-1]=1,此时标记向左的箭头

循环往复

i=0,i<字符串1的长度,i++

j=0,j<字符串2的长度,j++

在这个循环遵照上边的规则把表填出来就可以了

这个标记函数的指向就是怎么推出来的这个值

对于追踪解的过程,就是从最后一个值自底向上开始追踪,按照箭头的走向开始求解。如果遇到斜向上的箭头,那就说明找到了一个公共子序列。一直到0为止,图中画圆圈的就是问题的解即:BCBA。当然我们也要知道,这个解不是唯一的,我们能看到BCAB也是一个解。所以我们只需要找到一个最长子序列即可。

对于求解过程啊,因为原问题是要划成子问题来解的。那么原问题是需要依赖子问题的解,也就是说通过子问题的解和递推方程可以计算出原问题的解。所以解的箭头指向就是指向上一个子问题的最优解。因此这个方法是有效的。

通过分析,我们直接上代码:

public static void main(String[] args) {// TODO Auto-generated method stubString str1="ABCBDAB";String str2="BDCABA";int B[][]=new int[str1.length()+1][str2.length()+1];int temp[][]=LcsLength(str1, str2, B);System.out.println("最长公共子序列的个数是:"+temp[str1.length()][str2.length()]);System.out.print("最长公共子序列是:");Tracingsolution(str1.length(), str2.length(), B, str1);}public static int[][] LcsLength(String str1,String str2,int B[][]) {int [][]C=new int[str1.length()+1][str2.length()+1];for(int i=0;i<str1.length();i++) {for(int j=0;j<str2.length();j++) {if(str1.charAt(i)==str2.charAt(j)) {C[i+1][j+1]=C[i][j]+1;B[i+1][j+1]=0; //0表示斜向上}else {if(C[i][j+1]>=C[i+1][j]) {C[i+1][j+1]=C[i][j+1]; //将最大的值存放在C中B[i+1][j+1]=1; //1表示竖直方向}else {C[i+1][j+1]=C[i+1][j];B[i+1][j+1]=-1; //-1表示向左}}}}return C;}//递归追踪解public static void Tracingsolution(int a,int b,int B[][],String str1) {if(a==0||b==0) return;//箭头斜向上if(B[a][b]==0) {Tracingsolution(a-1, b-1, B, str1);System.out.print(str1.charAt(a-1));}//箭头向上else if(B[a][b]==1)Tracingsolution(a-1, b, B, str1);//箭头向左elseTracingsolution(a, b-1, B, str1);}

下面,我们来进行时间复杂度分析:

动态规划算法来解最长公共子序列问题分析

我们看到动态规划算法里面是两层for循环的

那么时间复杂度就是O(mn)

追踪解的代码是逐步缩小的过程,要么是m-1,要么是n-1或者是m-1和n-1同时执行。那么在最坏情况下的时间复杂度就是O(m+n)。最后我们能得出时间复杂度就是O(mn)

而空间复杂度是一个备忘录的表格。所以是O(mn)

这就是动态规划解最长公共子序列问题的内容

2、最大子段和问题

什么是最大子段和问题

给定n个数(可以是负数)的序列(a1,a2,…,an)

求在这个序列中,某一连续段中的最大和

例如:(-2,11,-4,13,-5,-2)

则最大子段和就是a2+a3+a4=20

我们可以考虑用蛮力法来解

蛮力法解最大子段和问题

我们可以求这个序列的子集(连续段),然后对这个子集进行求和。直至找到最大值

譬如说:a1,a1+a2,a1+a2+a3,a2,a3…

我们对蛮力法进行代码分析:

用锁定范围来求解

让i=0,i<n;i++

j=i,j<n,j++

我们令k在i和j之间游走。即,k=i,k<=j,k++

遍寻max(sum+a[k])的值。如果我们找到了最大值,我们就用m,n记录下。m=i,n=j。然后

最大值,m,n就是问题的解

好,我们来上代码:

public static int m; //记最大子段和的初始位置public static int n; //记最大子段和的末尾位置public static void main(String[] args) {// TODO Auto-generated method stubint a[]=new int[] {-2,11,-4,13,-5,-2};int maxsum=MaxsubsegmentsMax(a);Tracingsolution(a, maxsum);System.out.print("最大子段和是:");for(int i=m;i<=n;i++){if(i!=n)System.out.print("a"+i+"+");else System.out.print("a"+i);}System.out.print("="+maxsum);}//蛮力法解最大子段和public static int MaxsubsegmentsMax(int a[]) {int maxsum=-0x3f3f3f3f,tempsum=0;for(int i=0;i<a.length;i++) //子序列初始位置for(int j=i;j<a.length;j++) {//子序列末尾位置tempsum=0;for(int k=i;k<=j;k++) tempsum+=a[k];if(tempsum>maxsum){maxsum=tempsum;m=i+1;n=j+1;}}return maxsum;}

蛮力法解最大子段和问题的分析

我们看到,这是有三层循环的,所以蛮力法的时间复杂度就是O(n^3)

当然我们也可以用分治法来解最大子段和问题

分治法解最大子段和问题

我们可以在中间点将序列一分为二,通过递归将左右序列求解。对于中间部分。一定记住是从中间往两遍算。因为是要以中间为基准向两遍计算。从而得到跨边界的最大值。不然就会变成零零散散的区域,再合成就不是中间跨边界的值了。最后将左半部分的和,右半部分的和,跨边界的和,三者进行比较找到最大的和。

对于解的追踪,我们是要分开考虑的,跨边界的问题中记下最大和的时候左右的下标,而在左半部分和右半部分中,left和right的跨度就是最大和的左右下标。

然后我们要在三者之间进行比较。将对应的情况赋给m和n。

具体代码如下:

public static int m; //记最大子段和的初始位置public static int n; //记最大子段和的末尾位置public static int max=-0x3f3f3f3f; //递归法求解最大值public static void main(String[] args) {// TODO Auto-generated method stubint a[]=new int[] {-2,11,-4,13,-5,-2};int maxsum=MaxsubsegmentsMax2(a, 0, a.length-1);Tracingsolution(a, maxsum);System.out.print("最大子段和是:");for(int i=m;i<=n;i++){if(i!=n)System.out.print("a"+i+"+");else System.out.print("a"+i);}System.out.print("="+maxsum);}//递归解最大子段和public static int MaxsubsegmentsMax2(int a[],int left,int right) {int x = 0,y = 0;if(left==right)return a[left];int mid=(left+right)>>1;int leftMaxSum=MaxsubsegmentsMax2(a,left,mid); //划成左半部部分int rightMaxSum=MaxsubsegmentsMax2(a, mid+1, right); //划成右半部部分//处理跨边界问题int leftmax=-0x3f3f3f3f,rightmax=-0x3f3f3f3f,tempmax=0;//计算中间向左部分的最大值//一定记住是从中间往两遍算。因为是要以中间为基准向两遍计算。从而得到跨边界的最大值。不然就会变成零零散散的区域,再合成就不是中间跨边界的值了for(int i=mid;i>=left;i--) {tempmax=a[i]+tempmax;if(tempmax>leftmax) {leftmax=tempmax;x=i;}}tempmax=0;//计算中间向右部分的最大值for(int i=mid+1;i<=right;i++) {tempmax=a[i]+tempmax;if(tempmax>rightmax) {rightmax=tempmax;y=i;}}//将跨边界的左右值加起来int midMaxSum=leftmax+rightmax;//将左半部分,中间边界,右半部分中挑选一个最大值if(leftMaxSum>Math.max(midMaxSum, rightMaxSum)) {max=leftMaxSum;m=left+1;n=right+1;}else if (midMaxSum > Math.max(leftMaxSum, rightMaxSum)) {max = midMaxSum;m=x+1;n=y+1;}else {max = rightMaxSum;m=left+1;n=right+1;}return max;}

下面我们来分析时间复杂度

分治法解最大子段和问题的分析

我们想一下,从中间往左算最大和的时候,是不是A[k],A[k]+A[k-1],A[k]+A[k-1]+A[k-2],…,A[k]+A[k-1]+A[k-2]+…+A[1]

因此我们就能分析出,时间复杂度就是左半部分的时间复杂度就是O(n),同理右半部分的时间复杂度也是O(n)。所以左右一加还是O(n)

因此我们列出递推方程:

T(n)=2T(n/2)+O(n)

根据主定理得出

T(n)=O(nlogn)

分析得出分治法比蛮力法降到了O(nlogn)量级的时间复杂度

下面考虑动态规划解最大子段和问题

动态规划解最大子段和问题

我们这么分析,动态规划算法可以进行试探算法。

我们可以i=0,i<n;i++

tempsum+a[i]如果大于此时的a[i]。那我们就加,否则就tempsum=a[i]。一轮循环之后让tempsum和sum比较。

这个是将前i个最优解和第i个值进行比较。如果C[i-1]+A[i]大,就进行计算,否则就是A[i]的值。这样总是能获得一个最大的C[i]。然后再用最优解去和下一个子问题进行计算。因此是满足依赖关系的。

至于追踪解,我们通过比较是只能获取最大和的那个末位置的边界c。要自底向上的去求初位置的边界。怎么求呢?

将计算好的最大值max,以此减去A[c],得到的新max然后判断是否为0且max!=A[- -c]。如果是这样就结束循环,这个c就是初位置下标。否则继续计算直到条件满足。最后就得到初位置下标

好,下面直接上代码

public static int m; //记最大子段和的初始位置public static int n; //记最大子段和的末尾位置public static void main(String[] args) {// TODO Auto-generated method stubint a[]=new int[] {-2,11,-4,13,-5,-2};int maxsum=MaxsubsegmentsMax3(a);Tracingsolution(a, maxsum);System.out.print("最大子段和是:");for(int i=m;i<=n;i++){if(i!=n)System.out.print("a"+i+"+");else System.out.print("a"+i);}System.out.print("="+maxsum);}//用动态规划解最大子段和public static int MaxsubsegmentsMax3(int a[]) {int tempsum=0,maxsum=-0x3f3f3f3f;for(int i=0;i<a.length;i++) {if(tempsum+a[i]>a[i])tempsum=tempsum+a[i];elsetempsum=a[i];if(tempsum>maxsum) {maxsum=tempsum;n=i+1;}}return maxsum;}//追踪解public static void Tracingsolution(int a[],int maxsum) {int k=n-1;while(maxsum!=0||maxsum==a[k]) {maxsum=maxsum-a[k];k--;}m=k+2;}

下面我们进行时间复杂度分析

动态规划算法来解最大子段和问题分析

我们看到动态规划算法里面只有一层循环,因此是O(n)

而对于追踪解,最坏时间复杂度也是O(n)

所以动态规划算法来解最大子段和问题的时间复杂度就是O(n)

而空间复杂度我们自始至终就只用一个数组,因此空间复杂度就是O(n)

以上就是最大子段和问题的内容

###### 物情无巨细,自适固其常。 (杜甫《夏夜叹》)

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。