动态规划的应用

7 篇文章 2 订阅

动态规划的基本思想:

在用分治法求解时,有些问题被重复计算了好多次。如果能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,就可以避免大量重复计算。为达到这个目的,可以用一个表来记录所有已解决的子问题的答案,只要他被计算过,就将其填入表中。

矩阵连乘问题

这种计算次序可以用加括号的方式来确定。若一个矩阵连乘积的计算次序完全确定,也就是说该连乘积已完全加括号,则可以依此次序反复调用2个矩阵相乘的标准算法计算出矩阵连乘积。


 

2矩阵乘积的标准算法:

public static void matrixMultiply(int [][]a,int [][]b,int [][]c,int ra,int ca,int rb,int cb){
        for (int i = 0;i<ra;i++){
            for (int j = 0;j<cb;j++){
                int sum = 0;
                for (int k = 0;k<ca;k++){
                    sum = sum + a[i][k]*b[k][j];
                }
                c[i][j] = sum;
            }
        }
    }

计算3个矩阵{A1,A2,A3 }连乘积的例子。设这3个矩阵的维数分别为10 X 100,100 X 5和5 X 50。若按第一种加括号方式((A1,A2)A3)计算,3个矩阵连乘积需要的数乘次数为10 X 100 X 5 + 10  X 5 X 50 = 7500。若按第二种加括号方式(A1(A2A3))计算,3个矩阵连乘积总共需要10X5X50+ 10 X 100 X 50 = 75 000次数乘。第二种加括号方式的计算量是第一种加括号方式计算量的10倍。

 数组p(n)表示矩阵的维数,上述数组可表示为

p(0)=30,p(1)=35,p(2)=15,p(3)=5,p(4)=10,p(5)=20,p(6)=25

数组m[i][j]表示最少乘的次数

s[i][j]表示m[i][j]最少乘次数时断开点k的值

获取最优值数组与记录最优断开位置的数组s 

输入参数{p0,p1,p2,p3....}存储于数组p中

特别注意m数组的构造顺序问题,不是一行一行的构造。

public static void maxtrixChain(int []p,int [][]m,int [][]s) {
        int len = p.length - 1;
        for (int i = 1;i<=len;i++){
            m[i][i] = 0;//m数组的第一次构造(对角线)
        }
        //对m数组的赋值从对角线开始向顶点移动,越来越少
        for (int r = 2;r <= len;r++){//对m数组的第r次赋值及相应的顺序。
            for (int i =1;i <= len - r + 1;i++){
                int j = i + r - 1;
                m[i][j] =m[i][i]+ m[i+1][j] + p[i-1]*p[i]*p[j];
                s[i][j] = i;
                for (int k = i + 1;k < j;k++){
                    int t =m[i][k]+ m[k+1][j] + p[i-1]*p[k]*p[j];
                    if (t < m[i][j]){
                        m[i][j] = t;
                        s[i][j] = k;
                    }
                }
            }
        }
    }

算法Trackback利用算法matrixChain计算出的断点矩阵指示的加括号方式输出计算矩阵连乘的最优计算次序。

public static void Traceback(int [][]s,int i,int j){
        //m[i][j]的最优断点s[i][j],将其分为m[i][ s[i][j] ]与 m[ s[i][j]+1 ][j]
        //按其规律递归,无限利用断点划分
        if (i==j) return;
        Traceback(s,i,s[i][j]);//m[i][ s[i][j] ]部分的递归
        Traceback(s,s[i][j]+1,j);//m[ s[i][j]+1 ][j]部分的递归

        System.out.println("A"+i+","+s[i][j]+"and A"+(s[i][j]+1)+","+j);
    }

单词划分问题

给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。

输入: s = “leetcode”, wordDict = [“leet”, “code”] 输出: true 解释: 返回 true 因为 “leetcode” 可以被拆分成 “leet code”。

将大问题分解为规模小一点的子问题:

  • 前 i 个字符的子串,能否分解成单词
  • 剩余子串,是否为单个单词。

dp[i]:长度为is[0:i-1]子串是否能拆分成单词。题目求:dp[s.length]

再利用j对s[0,i]进行分割,s[0,i]能否被划分为字典里存在的词取决于:

  • 前缀部分s[0,j-1]的dp[j]是否为true
  • 剩余部分s[j,i]是否能拆分为单词(原问题)

最长公共子序列

设序列X={x_{1},x_{2},x_{3},x_{4},x_{5}....x_{m}},Y={y_{1},y_{2},y_{3},y_{4}....y_{n}}的最长公共子序列Z={z_{1},z_{2},z_{3}.....z_{k}},则

解释(1),若Xm=Yn,即表示序列X与序列Y的公共子序列的最后一项相同,即z的最后一项也是这个,因此求Xm与Yn的公共子序列Zk变成求 

 用c[i][j]来表示序列Xi与Yi的最长公共子序列的长度。

c[i-1][j-1]+1表示Xm=Yn=Zk

max{c[i][j-1],c[i-1][j]}表示Z是Xi-1与Yi的最长子序列或者Z是Xi与Yi-1的最长子序列

b[i][j]记录c[i][j]的值是由哪一个子问题的解得到的。

计算最优值(计算最优值的数组c[i][j])

public static int lcsLength(char []x,char []y,int[][]b){
        int m = x.length-1;
        int n = y.length-1;
        int [][]c = new int[m+1][n+1];
        for (int i=1;i<=m;i++) c[i][0]=0;//y序列为空
        for (int i=1;i<=n;i++) c[0][i]=0;//x序列为空
        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;//子问题1
                }else {
                    if (c[i-1][j]>=c[i][j-1]){
                        c[i][j] = c[i-1][j];
                        b[i][j] = 2;
                    }else {
                        c[i][j] = c[i][j-1];
                        b[i][j] = 3;
                    }
                }
            }
        }
        return c[m][n];
    }

构造最长公共子序列(打印输出最长子序列)

public static void lcs(int i,int j,char []x,int [][]b){
        if (i==0||j==0) return;
        if (b[i][j]==1){
            lcs(i-1,j-1,x,b);
            System.out.println(x[i]);//此时当b[i][j]==1,第i项必为公共子序列的元素
        }
        if (b[i][j]==2) lcs(i-1,j,x,b);
        if (b[i][j]==3) lcs(i,j-j,x,b);
    }

凸多边形最优三角剖分

给定凸多边形P={V0,V1,V2,,,,Vn-1},以及定义在由多边形的边和弦组成的三角形上的权函数w。要求确定该凸多边形的三角剖分,使得该三角剖分所对应的权,即该三角剖分中诸三角形上权之和为最小。

权函数W    w(Vi,Vj,Vk) = Vj*Vk+Vi*Vj+Vk*Vj

数组t[i][j]表示凸多边形{Vi-1,Vi,,,,Vj}的最优三角剖分对应的权函数值。即最优值。

设多边形{Vi-1,Vi}的权值为0

最优子结构

实现与矩阵连乘类似

数组t[i][j]表示凸多边形{Vi-1,Vi,,,,Vj}的最优三角剖分对应的权函数值。

s[i][j]表示t[i][j]对应的k的位置

t[i][j]的构建顺序

public static void minWeightTriangulation(int n,int[][]t,int [][]s){//t[][]表示
        for (int i=1;i<=n;i++) t[i][i]=0;
        for (int r=2;r<=2;r++){//赋值是一个三角形状
            for (int i=1;i<=n-r+1;i++){
                int j = i+r-1;
                t[i][j] = t[i][i]+t[i+1][j]+w(i-1,i,j);//给一个初值便于比较大小
                s[i][j] = i;
                for (int k=i+1;k<i+r-1;k++){
                    int u =t[i][k]+t[k+1][j]+w(i-1,k,j);
                    if (u<t[i][j]){
                        t[i][j] = u;
                        s[i][j] = k;
                    }
                }
            }
        }
    }

电路布线问题

    public  static void mns(int []c,int[][]size) {
        int n=c.length-1;
        for(int j=0;j<c[1];j++)
            size[1][j]=0;
        for(int j=c[1];j<=n;j++) {
            size[1][j]=1;
        }
        for(int i=2;i<n;i++) {
            for(int j=0;j<c[i];j++) {
                size[i][j]=size[i-1][j];
            }
            for(int j=c[i];j<=n;j++) {
                size[i][j]=Math.max(size[i-1][j], size[i-1][c[i]-1]+1);
            }
            size[n][n]=Math.max(size[n-1][n], size[n-1][c[n]-1]+1);

        }
    }

    //构造最优解
    public static int traceback(int []c,int [][]size,int []net) {
        int n=c.length-1;
        int j=n;
        int m=0;
        for(int i=n;i>1;i--) {
            if(size[i][j]!=size[i-1][j]) {
                net[m++]=i;
                j=c[i]-1;
            }}

        if(j>=c[1])
        {
            net[m++]=1;
        }


        return m;
    }

0-1背包问题

/*
    v[]  价值
    W[]  质量
    c  总质量
    m 存储m(i,j)的值
     */
    public static void knapack(int []v,int []w,int c,int [][]m){
        int n = v.length-1;//物品种类数

        // i=n
        int Wn = Math.min(w[n],c);//判断w(n)与c的大小
        for (int j=0;j<=c;j++){
            if (j<Wn) m[n][j]=0;
            if (j>=Wn) m[n][j]=v[n];
        }
        //i=n-1,n-2,,,,,1
        for (int i=n-1;i>=1;i--){
            int Wi = Math.min(w[i],c);
            for (int j=1;j<=c;j++){
                if (j>=Wi) m[i][j]=Math.max(m[i+1][j],m[i+1][j-w[i]]+v[i]);
                if (j<Wi) m[i][j]=m[i+1][j];

            }
        }
    }

m[1][c]则是背包问题的最优值

x[i]判断物品i是否放入背包,1放入,0不放入

 根据次可得,

若m( i , j ) = m( i+1 , j )可以得知i没有放入背包

若m( i , j ) = m( i + 1 , j - Wi ) + Vi可以得知将物品i放入了背包,此时背包容量为j-Wi

若m(n,j) = 0 得知物品n没有放入背包

若m(n,j) > 0 得知物品n放入背包

//m[1][c]则是背包问题的最优值
     /*
    m 存储m(i,j)的值
    W[]  质量
    c  总质量
    x[i]判断物品i是否放入背包,1放入,0不放入
     */
    public static void traceback(int[][]m,int[]w,int c,int []x){
        int n = w.length-1;
        for (int i=1;i<n;i++){
            if (m[i][c]==m[i+1][c]) x[i]=0;//物品i没有放入背包
            else {//物品i放入背包
                x[i] = 1;
                c=c-w[i];
            }
        }
        //最后一个特殊讨论
        if (m[n][c]>0) x[n]=1;
        else x[n]=0;
    }

  

二维背包问题

给定n种物品和一个背包。物品i的重量是wi,体积是bi,其价值是vi,背包的容量为c,容积为d。问:应该如何选择装入背包中的物品,使得装入背包中的物品的总价值最大?在选择装入背包额物品时,对每种物品i只有两种选择,即装入背包或不装入背包。不能将物品i装入背包多次,也不能只装入部分的物品i。

输入的第一行包含三个整数n, c ,d,分别表示物品的个数,背包能装重量和背包的容积。w[],v[],b[]分别表示各物品的重量、价值、体积。

与0-1问题类似,在此基础上再加上一个体积限制。

m[1][c][d]则是背包问题的最优值

x[i]判断物品i是否放入背包,1放入,0不放入

若m( i , j , k) = m( i+1 , j , k )可以得知i没有放入背包

若m( i , j , k) = m( i + 1 , j - Wi , k - Di) + Vi可以得知将物品i放入了背包,此时背包容量为j-Wi ,体积为k-Di

若m(n,j,k) = 0 得知物品n没有放入背包

若m(n,j,k) > 0 得知物品n放入背包

//调用部分
输入 5 8 8
v[]={2,1,4,3,5}
w[]={1,4,2,3,5}
b[]={2,3,5,2,4}

public static void main(String[] args) {
        int c = 8;
        int d = 8;
        int []v = {0,2,1,4,3,5};//0用来占位,程序从下标1开始
        int []w = {0,1,4,2,3,5};
        int []b = {0,2,3,5,2,4};
        int [][][]m = new int[v.length][c+1][d+1];
        knapack(v,w,b,c,d,m);
        System.out.println("装入背包中物品总价值最大为:"+m[1][8][8]);
        int x[] = new int[v.length];
        traceback(m,w,b,c,d,x);
        System.out.print("装入的物品的序号为:");
        for (int i=0;i<v.length;i++){
            if (x[i]==1) System.out.print(i+" ");
        }
    }



    public static void knapack(int []v,int []w,int []b,int c,int d,int [][][]m){
        int n = v.length-1;
        int Wn = Math.min(w[n],c);
        int Dn = Math.min(b[n],d);
        for (int j=0;j<=c;j++){
            for (int k=0;k<=d;k++){
                if (j<Wn || k<Dn) m[n][j][k]=0;
                else m[n][j][k]=v[n];
            }
        }
        //i=n-1,n-2,,,,,1
        for (int i=n-1;i>=1;i--){
            int Wi = Math.min(w[i],c);
            int Di = Math.min(b[i],d);
            for (int j=1;j<=c;j++){
                for (int k=1;k<=d;k++){
                    if (j>=Wi&&k>=Di) m[i][j][k]=Math.max(m[i+1][j][k],m[i+1][j-w[i]][k-b[i]]+v[i]);
                    if (j<Wi||k<Di) m[i][j][k]=m[i+1][j][k];
                }
            }
        }
    }



    public static void traceback(int[][][]m,int[]w,int b[],int c,int d,int []x){
        int n = w.length-1;
        for (int i=1;i<n;i++){
            if (m[i][c][d]==m[i+1][c][d]) x[i]=0;//物品i没有放入背包
            else {//物品i放入背包
                x[i] = 1;
                c=c-w[i];
                d=d-b[i];
            }
        }
        //最后一个特殊讨论
        if (m[n][c][d]>0) x[n]=1;
        else x[n]=0;
    }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yoin.

感谢各位打赏!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值