zoukankan      html  css  js  c++  java
  • 高效算法之动态规划(第15章)

    有人说:越炫耀什么,越缺少什么。但我却以为:越缺少什么,越觉得别人炫耀什么。 ——李宫俊《李宫俊的诗》

    0. 前言

    参考图书《算法导论》
      动态规划通常用来解决最优化问题,在这类问题中,我们通常做出一组选择来表达最优解。在做出这个选择的同时,通常会生成与原问题形式相同的子问题。当多于一个选择子集都生成相同的子问题时,动态规划技术通常很有效,其关键技术就是对每一个这样的子问题都保存其解,当其重复出现的时候即可避免重复求解。这种思想可以将指数时间的算法转换为多项式时间的算法。
      动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。动态规划(dynamic programming)中的programming指的是一种表格法,不是编写计算机程序。
      

    1. 动态规划解析

    采用动态规划求解的问题的一般要具有3个性质:

    (1) 最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。
    (2) 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
    (3)有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)。
    

    动态规划求解基本步骤:

      1. 刻画一个最优解的结构特征。
      2. 递归的定义最优解的值。
      3. 计算最优解的值,通常采用自底向上的方法。
      4. 利用计算出的信息构造一个最优解。
    

    2. 动态规划的应用

    2.1 钢条切割

    问题陈述:给定一个长度为n英寸的钢条和一个价格表pii=1,2,3,4....,n,求切割钢条的方案,使得销售收入最大。

    长度i 1 2 3 4 5 6 7 8 9 10
    价格pi 1 5 8 9 10 17 17 20 24 30

    问题分析:从题目中我们可以得出给定的长度为n的钢条有2n1种不同的切割方案,因为在距离钢条左端i(i=1,2,3..,n1)英寸处,我们总是可以选择切割或者不切割。对于上述的价格表样例,我们可以观察所有最优收益值ri(i=1,2,3,4...10) 以及对应的最优切割方案:

    r1=1, 切割方案1=1(无切割)
    r2=5, 切割方案2=2(无切割)
    r3=8, 切割方案3=3(无切割)
    r4=10, 切割方案4=2+2
    r5=13, 切割方案5=2+3
    r6=17, 切割方案6=6(无切割)
    r7=18, 切割方案7=6+1或者7=2+2+3
    r8=22, 切割方案8=2+6
    r9=25, 切割方案9=3+6
    r10=30, 切割方案10=10(无切割)
      更一般的对于rn(n>=1), 我们可以用更短的钢条的最优切割收益来描述它:

    rn=max(pn,r1+rn1,r2+rn2,r3+rn3....rn2+r2,rn1+r1)

    注意,为了求解规模为n的原问题,我们求解形式完全一样(最优解结构特征刻画),完成首次切割之后我们将两段钢条看作两个独立的钢条切割问题,通过组合两个相关子问题的最优解(最优子结构),并且在所有可能的两段切割方案中选取组合收益最大者,构成原问题的最优解。
    除了上述求解方式还有一种递归求解的方式公式如下:
    rn=max(pi+rn1)(1in)

    算法设计:
    自顶向下的普通递归算法设计:

    SteelCut(p[],n){
        //初次判断
        if(n==0){
            return 0;
        }
        int q=-1;
        //不做切割
        if(n<=p.length){
            q=p[n];
        }
        //递归求解
        for(i=1 to n){
            q=max(q,p[i]+SteelCut(p[],n-i));
        }
        return q;
    }

    使用动态规划的算法设计:

    //算法1:带备忘录的自顶向下法
    MemorizedSteelCut(p[],n){
        int r[] = new int[n];
        for(i=0 to n){
            r[i]=-1;
        }
        return MemorizedSteelCutAux(p[],n,r);
    }
    MemorizedSteelCutAux(p[],n,r){
        if(r[n]>=0){
            return r[n];
        }
        if(n==0){
            q=0;
        }
        else{
            q=-1;
            for(i=1 to n){
                q=max(q,p[i]+MemorizedSteelCutAux(p[],n-i,r));      
            }
        }
        r[n]=q;
        return q;
    }
    
    //算法2:自底向上法
    BottomUpSteelCut(p[],n){
        int r[] = new int[n];
        r[0]=0;
        for(j=1 to n){
            q=-1;
            for(i=1 to j){
                q=max(q,p[i]+r[j-i]);
            }
            r[j]=q;
        }
    }
    
    

    Java实现:

    package lbz.ch15.dp.ins1;
    /** 
     * @author LbZhang
     * @version 创建时间:2016年3月4日 下午2:20:33 
     * @description 钢条切割问题
     */
    public class SteelCutting {
    
        public static void main(String[] args) {
            System.out.println("DP 在钢条切割问题中的应用 ");
            int price[] = {1,5,8,9,10,17,17,20,24,30};
            int n = 10;
            //自顶向下的递归实现
            int result = 0;
            result = TopToBottomRecursion(price,n);
            System.out.println("常规的思路:"+result);
    
            //使用动态规划来实现  备忘录
            result=MemorizedSteelCut(price,n);
            System.out.println("备忘录法:"+result);
    
            //使用动态规划的自底向上非递归的实现
            result=BottomToTopSteelCut(price,n);
            System.out.println("自底向上非递归方法:"+result);
    
    
        }
        private static int BottomToTopSteelCut(int[] price, int n) {
            int r[] = new int[n+1];
            r[0]=0;//动态表的开头
            for(int j=1;j<=n;j++){
                int q=-1;
                for(int i=1;i<=j;i++){
                    q=maxOfTwo(q,price[i-1]+r[j-i]);
                }
                r[j]=q;
            }
    
            return r[n];
        }
        /**
         * 备忘录方法
         * @param price
         * @param n
         * @return
         */
        private static int MemorizedSteelCut(int[] price, int n) {
            int r[] = new int[n+1];
            for(int i=0;i<=n;i++){
                r[i]=-1;
            }
            return MemorizedSteelCutAux(price,n,r);
        }
        /**
         * 辅助过程的备忘录核心算法
         * @param price
         * @param n
         * @param r
         * @return
         */
        private static int MemorizedSteelCutAux(int[] price, int n, int[] r) {
            if(r[n]>=0){
                return r[n];
            }
            int q=0;
    
            if(n==0){
                q=0;
            }else{
                q=-1;
                for(int i=1;i<=n;i++){
                    //price[i-1] 应为price的下标是从0开始,
                    q=maxOfTwo(q,price[i-1]+MemorizedSteelCutAux(price,n-i,r));     
                }
            }
    
            r[n]=q;
            return q;
        }
        /**
         * //自顶向下的递归实现 常规思路
         * @param price                                 ``````````````````````````````````````````````````````````````````
         * @param n
         * @return
         */
    
        private static int TopToBottomRecursion(int[] price, int n) {
            if(n==0) return 0;
            int q = -1;
            if(n<=price.length){
                q=price[n-1];
            }
            for(int i=1;i<n;i++){
                q=maxOfTwo(q,price[i-1]+TopToBottomRecursion(price,n-i));
            }
    
            return q;
    
    
        }
        private static int maxOfTwo(int x, int y) {
            return x>y?x:y;//三目运算符的使用
        }
    
    
    
    }
    

    重构解-对源程序进行修改

    private static int BottomToTopSteelCut(int[] price, int n) {
            int r[] = new int[n+1];
            int s[] = new int[n+1];
    
            r[0]=0;//动态表的开头
            for(int j=1;j<=n;j++){
                int q=-1;
                for(int i=1;i<=j;i++){
                    //q=maxOfTwo(q,price[i-1]+r[j-i]);
                    if(q<price[i-1]+r[j-i]){
                        q=price[i-1]+r[j-i];
                        s[j]=i;
                    }
                }
                r[j]=q;
            }
            System.out.println();
            for(int temp=0;temp<=n;temp++){
                System.out.print(s[temp]+"|-"+temp+"-|");
            }
            System.out.println();
    
            //正确的组合输出
            printToFormal(s);
    
    
            return r[n];
        }
        private static void printToFormal(int[] s) {
            int len=s.length-1;
            int temp=s[len];
    
            System.out.print("钢条切割的组合方式: "+temp+" ");
            while(temp!=len){
                len=len-temp;
                temp=s[len];
                System.out.print("+ "+temp+" ");
            }
            System.out.println();
    
        }
    

    2.2 斐波那契数列

    下面直接给出斐波那契数列的Java实现的O(n)的动态规划算法实现。

    package lbz.ch15.dp.ins1;
    
    /**
     * @author LbZhang
     * @version 创建时间:2016年3月7日 下午9:42:15
     * @description 类说明
     */
    public class MemoryAndTable {
        static int MAX = 20;
        static int[] lookUp = new int[MAX];
    
        public static int fibMemory(int n) {
            if (lookUp[n] == 0) {
                if (n <= 1) {
                    lookUp[n] = n;
                } else {
                    lookUp[n] = fibMemory(n - 1) + fibMemory(n - 2);
                }
            }
            return lookUp[n];
        }
    
        // //打表(自下而上)
        public static int fibTable(int n) {
            int[] f = new int[n + 1];
            int i;
            f[0] = 0;
            f[1] = 1;
            for (i = 2; i <= n; i++) {
                f[i] = f[i - 1] + f[i - 2];
            }
            return f[n];
        }
    
        public static void main(String[] args) {
            int n = 5;
            System.out.println(fibMemory(9));
            System.out.println();
            // int res=0;
            // res=fibTable(9);
            System.out.println(fibTable(9));
        }
    }

    注意:在动态规划中,子问题解决方案被存储在一个表中,以便这些不必重新计算。 因此,如果这个问题是没有共同的(重叠)子问题, 动态规划是没有用的。例如,二分查找不具有共同的子问题。

    踏实 踏踏实实~
  • 相关阅读:
    软件工程实践个人编程作业
    实验 2:Mininet 实验——拓扑的命令脚本生成
    软工实践个人总结
    第08组 每周小结 (3/3)
    第08组 每周小结 (2/3)
    第08组 每周小结 (1/3)
    第08组 Beta冲刺 总结
    第08组 Beta冲刺 (5/5)
    第08组 Beta冲刺 (4/5)
    第08组 Beta冲刺 (3/5)
  • 原文地址:https://www.cnblogs.com/mrzhang123/p/5365807.html
Copyright © 2011-2022 走看看