• 动态规划算法(2)--最大子段和与最长公共子序列


    目录

    一、最大子段和

    1、什么是最大子段和

    2、暴力枚举

    3、分治法

    4、动态规划

    5、追踪子段

    二、最长公共子序列

    1、什么是最长公共子序列

    2、暴力枚举法

    3、动态规划法

    4、完整代码


    一、最大子段和

    1、什么是最大子段和

            子段和就是数组中任意连续的一段序列的和,而最大子段和就是寻找子段和里最大的一个值。下面的解释中S[l,r]会用来表示l到r的子段和,l和r分别表示左值和右值。

            最大子段和一般有三种解决方案:暴力枚举法分治法动态规划法。下面将逐个介绍。

    758a27854af049a2a71061694f22f443.png

    2、暴力枚举

            暴力枚举就是遍历所有的子段和,寻找最大的子段和,时间复杂度eq?O%28n%5E3%29 。相对无脑,直接贴上代码。

    1. //暴力枚举法
    2. public static int maxsize_violate(ArrayListarr,int left, int right)
    3. {
    4. int max=-99999999;
    5. for(int i=left;i<=right;i++)
    6. {
    7. int sum=0;
    8. for(int j=i;j<=right;j++)
    9. {
    10. for(int k=i;k<=j;k++)
    11. {
    12. sum+=arr.get(k); //最大值来源
    13. }
    14. if(sum>max)
    15. max=sum;
    16. sum=0;
    17. }
    18. }
    19. return max;
    20. }

    3、分治法

            将每个问题分解为三个小问题,左一半的子段和,右一半的子段和,(必须)跨区域的子段和。

            伪代码如下,可以看到左子段和与右子段和都是递归求解(3、4),跨区域的一定是左右两个子段和最大值的和(5、6、7),最后选择左子段和、右子段和、跨域子段和中最大的子段和(8、9)。

    52b99b166ac14860a4ebc9626c2891af.png

            完整代码:

    1. //分治法
    2. public static int maxsize(ArrayListarr, int left, int right){
    3. int sum=0,midSum=0,leftSum=0,rightSum=0;
    4. int center,s1,s2,lefts,rights;
    5. //左右相等,返回左值
    6. if (left==right){
    7. sum=arr.get(left);
    8. }
    9. //否则,分治法
    10. else {
    11. center=(left+right)/2;
    12. leftSum=maxsize(arr,left,center); //left,l+r/2 //左区间最大值
    13. rightSum=maxsize(arr,center+1,right); //l+r/2+1,right //右区间最大值
    14. //后面都是在计算跨区域最大值(必须跨区域),一定是左区间贴近边界的最大值加右区间贴近边界的最大值相加。
    15. s1=0;lefts=0; //s1存左侧区间最大值,lefts作为temp
    16. for (int i=center;i>=left;i--){
    17. lefts+=arr.get(i);
    18. if (lefts>s1){
    19. s1=lefts;
    20. }
    21. }
    22. s2=0;rights=0; //s2存右侧区间最大值,rights作为temp
    23. for (int j=center+1;j<=right;j++){
    24. rights+=arr.get(j);
    25. if (rights>s2){
    26. s2=rights;
    27. }
    28. }
    29. midSum=s1+s2; //中间跨域的等于左侧加右侧的
    30. if (midSum
    31. sum=leftSum;
    32. }
    33. else {
    34. sum=midSum;
    35. }
    36. if (sum
    37. sum=rightSum;
    38. }
    39. }
    40. return sum;
    41. }

    4、动态规划

            动态规划法是自底向上推导,假设eq?a_i为第i个数,eq?b_i为包含最后一个数eq?a_i的连续子段和,sum为最大子段和。

            建立于下面图这个关系,假设已经有eq?a_ieq?a_%7Bj-1%7D的子段和eq?b_%7Bj-1%7D,那么加入后一个eq?a_j生成eq?b_j只有两种可能:

    (1)eq?b_%7Bj-1%7D%5Cleqslant%200,那么eq?b_j%3Da_j

    (2)eq?b_%7Bj-1%7D%3E0,那么eq?b_j%3Db_%7Bj-1%7D+a_j

            对于eq?1%5Cleqslant%20j%5Cleqslant%20n的每一个eq?b_j,都要与sum取最大值,保证sum为eq?b_1eq?b_j中最大的值,返回sum。

    5728b43fe8a94a65a0115e6a832184b9.png

            完整代码: 

    1. //动态规划法
    2. public static int maxsum(ArrayListarr, int n)
    3. {
    4. int sum=-999999;int b=0;
    5. for(int i=0;i<=n;i++)
    6. {
    7. if(b>0)
    8. b+=arr.get(i);
    9. else
    10. b=arr.get(i);
    11. if(b>sum)
    12. sum=b;
    13. }
    14. return sum;
    15. }

    5、追踪子段

            arr数组:初始数组

            back数组:返回最大子段和段首尾索引数组,初始化为0,0。

            当前子段满足eq?b_%7Bj-1%7D%3E0,则段尾索引后移,若满足eq?b_%7Bj-1%7D%5Cleqslant%200,则段首段尾索引都等于j。当b>sum时,即当前子段和大于最大子段和时,back数组修改。

            end的更替没有问题,一定是往后改变的,整个代码由于只有一层循环,所以不会出现end向前修改的问题。

    1. //追踪子段和数组
    2. public static void track_maxsum(ArrayListarr, ArrayListback,int n)
    3. {
    4. int sum=-999999;int b=0; //sum是最大子段和,b是当前最大子段和。
    5. int begin=0;int end=0;
    6. for(int i=0;i<=n;i++)
    7. {
    8. if(b>0)
    9. {
    10. b+=arr.get(i);
    11. end+=1;
    12. }
    13. else
    14. {
    15. b=arr.get(i);
    16. begin=end=i;
    17. }
    18. if(b>sum)
    19. {
    20. sum=b;
    21. back.set(0,begin);
    22. back.set(1,end);
    23. }
    24. }
    25. }
    26. //子段输出
    27. public static void show(ArrayListarr,ArrayListback)
    28. {
    29. for(int i=back.get(0);i<=back.get(1);i++)
    30. {
    31. System.out.print(arr.get(i)+" ");
    32. }
    33. }

    二、最长公共子序列

    1、什么是最长公共子序列

            子序列是指序列中任意不一定连续但顺序的若干个字符组成的序列。如下图中Z1={B,C,A}为X的子序列,B,C,A三个字符在X中顺序出现,且不一定连续。

            公共子序列就是指两个序列之间存在一个共同的子序列,而我们就是要找到最长的一个公共子序列。

    9afd433e7cc5406bb74eed774fad9691.png

    2、暴力枚举法

            暴力枚举法,不仅占用了相当大的内存存放所有子序列,和所有公共子序列,而且浪费了巨大的时间,时间复杂度指数级eq?O%28n2%5Em%29

    fd264a432b4541b988ab67a2838ec25e.png

    3、动态规划法

            动态规划法仍然是这种自底向上的算法,讨论前一项的最长公共子序列通过比较两个序列下一个值,判定是否进入子序列。动态规划法的时间复杂度为O(mn)

    554b804b5a8344cbb0c62269cd2029cf.png

            使用c[i][j]数组记录eq?x_jeq?y_j的最长公共子序列长度, b[i][j]数组记录子序列的产生情况。c数组存在下面的递归结构成立,与b数组的关系如下,根据这个递推式,可以写出c和b数组的生成函数。

    9e797ea0c8e84be5a0f1caeffb7a2336.png

    c[i][j]=c[i-1][j-1]+1

    b[i][j]=1               

    ↖                  

    c[i][j]=c[i-1][j]

    b[i][j]=2

    c[i][j]=c[i][j-1]

    b[i][j]=3

    如何构造最长子序列?

             就是根据b数组的指引,倒推子序列,所有b[i][j]=1,也就是b数组指引为左上箭头的,都是公共序列的值,将他们按顺序串接就得到了最大子序列。

    cdd4e6e80b68495583118cc6db6aeb6c.png

            注意一个问题,X序列是y轴方向的,Y序列是x轴方向的。 

    4、完整代码

    1. //最长公共子序列
    2. import java.util.Scanner;
    3. public class LCS {
    4. public static void main(String [] args)
    5. {
    6. String x=new Scanner(System.in).nextLine();
    7. String y=new Scanner(System.in).nextLine();
    8. int m=x.length();int n=y.length();
    9. int [][]c=new int[m+1][n+1];
    10. int [][]b=new int[m+1][n+1];
    11. LCSLength(x, y, c, b);
    12. for(int i=0;i1;i++)
    13. {
    14. for(int j=0;j1;j++)
    15. {
    16. System.out.print(c[i][j]);
    17. System.out.print("\t");
    18. }
    19. System.out.println("");
    20. }
    21. BuildLCS(m,n,x,b);
    22. }
    23. //最长公共子序列生成c和b数组
    24. public static void LCSLength(String x,String y,int [][]c,int [][]b)
    25. {
    26. int i,j;
    27. int m=x.length();int n=y.length();
    28. for(i=0;i1;i++)
    29. c[i][0]=0;
    30. for(i=0;i1;i++)
    31. c[0][i]=0;
    32. for(i=1;i1;i++)
    33. {
    34. for(j=1;j1;j++)
    35. {
    36. if(x.charAt(i-1)==y.charAt(j-1))
    37. {
    38. c[i][j]=c[i-1][j-1]+1;
    39. b[i][j]=1;
    40. }
    41. else if(c[i-1][j]>=c[i][j-1])
    42. {
    43. c[i][j]=c[i-1][j];
    44. b[i][j]=2;
    45. }
    46. else
    47. {
    48. c[i][j]=c[i][j-1];
    49. b[i][j]=3;
    50. }
    51. }
    52. }
    53. }
    54. //构造最长公共子序列
    55. public static void BuildLCS(int i,int j,String x,int[][]b)
    56. {
    57. if(i==0|j==0)
    58. {
    59. return;
    60. }
    61. if(b[i][j]==1)
    62. {
    63. BuildLCS(i-1, j-1, x, b);
    64. System.out.print(x.charAt(i-1));
    65. }
    66. else if(b[i][j]==2)
    67. {
    68. BuildLCS(i-1,j,x,b);
    69. }
    70. else
    71. { BuildLCS(i, j-1, x, b);
    72. }
    73. }
    74. }

  • 相关阅读:
    网络安全之CSRF漏洞原理和实战,以及CSRF漏洞防护方法
    jQuery基础----常用的选择器
    PyTorch入门教学——简介与环境配置
    一文教你普罗米修斯Prometheus的基础应用
    专业145+总分410+西工大西北工业大学827信号与系统考研经验电子信息与通信工程,海航,真题,大纲,参考书。
    IT创业项目 - 跟淘宝商城合作网赚项目,赚多少你说了算!
    L1&L2,范数&损失
    二手物品交易管理系统
    压缩包里的文件名可以这样隐藏起来
    c++常用函数所在头文件一览
  • 原文地址:https://blog.csdn.net/m0_60177079/article/details/133493047