zoukankan      html  css  js  c++  java
  • 动态规划算法

    ---恢复内容开始---

    钢条切割

    有钢条收益

    钢条长度  1, 2, 3, 4,   5,   6,   7,  8,   9, 10
    钢条收益  1, 5, 8, 9, 10, 17, 17, 20, 24, 30

    求长度为n的钢条最优切割方案?

    自顶向下递归实现

    static void Main(string[] args)
    {
        //分别表示长度为0,1..10的收益
        var prices = new int[] { 0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30 };
        int receipt = CutRod(prices, 4);
        Console.WriteLine(receipt);
    }
    //表示长度为n的最大收益
    static int CutRod(int[] p, int n)
    {
        if (n == 0)
            return 0;
        int q1 = int.MinValue;
        //表示先切割长度i
        for (int i = 1; i <= n; i++)
        {
            //剩余部分的收益
            int q2 = CutRod(p, n - i);
            q1 = q1 > p[i] + q2 ? q1 : p[i] + q2;
        }
        Console.Write(n + " ");
        Console.WriteLine(q1);
        return q1;
    }

    自顶向下会遍历每一条求解路径:

     

    其中每一个路径表示一个求解过程。比如求f4=f1+f3,f3又有3个求解方法等等。
    这种求解方法需要求解路径上的所有项,即使有些已经求过的,也会重复求解。

    动态规划方法求解这个问题,有两种等价的方法

    1. 带备忘的自顶向下法:存储已经求解的子问题,下次用到时直接使用
    2. 自地上向:求解最小的子问题,然后次小的子问题通过已经求解的子问题求解,直到得到原问题。

    备忘的自顶向下法

    static void Main(string[] args)
    {
        //分别表示长度为1..10的收益
        var prices = new int[] { 0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30 };
        int receipt = MemoizedCutRod(prices, 10);
        Console.WriteLine(receipt);
    }
    static int MemoizedCutRod(int[] p, int n)
    {
        //构造备忘存储位置
        int[] r = new int[n + 1];
        for (int i = 0; i <= n; i++)
            r[i] = int.MinValue;
        return MemoizedCutRodAux(p, n, r);
    }
    //r中表示下标为长度的最大收益
    private static int MemoizedCutRodAux(int[] p, int n, int[] r)
    {
        //使用已经备忘(memoirization)的结果
        if (r[n] >= 0)
            return r[n];
        int q1 = int.MinValue;
        if (n == 0)
            q1 = 0;
        else
        {
            for (int i = 1; i <= n; i++)
            {
                int q2 = MemoizedCutRodAux(p, n - i, r);
                q1 = q1 > p[i] + q2 ? q1 : p[i] + q2;
            }
        }
        r[n] = q1;
        return q1;
    }

    自底向下

    static void Main(string[] args)
    {
        //分别表示长度为1..10的收益
        var prices = new int[] { 0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30 };
        int receipt = BottomUpCutRod(prices, 10);
        Console.WriteLine(receipt);
    }
    static int BottomUpCutRod(int[] p, int n)
    {
        //构造子问题存储位置
        int[] r = new int[n + 1];
        r[0] = 0;//最小子问题
        for (int j = 1; j <= n; j++)
        {
            int q = int.MinValue;
            //求解长度为j的子问题,分解为比j小的子问题
            for (int i = 1; i <= j; i++)
                q = q > p[i] + r[j - i]? q : p[i] + r[j - i];
            r[j] = q;
        }
        return r[n];
    }

    动态规划原理:

    适合应用动态规划方法求解的最优化问题具备两个要素,最优子结构和子问题重叠。

    1. 如果一个问题最优解包含子问题的最优解,则这个问题具有最优子结构性质。
    2. 子问题空间必须足够小,也就是递归算法会反复求解子问题,而不是生成新的子问题。

    最后将子问题进行重构得到重构后的最优解。

    最长子序(longest common subsequence LCS)问题

    如果有x=ABCBDAB,z=BCDB,其中z对应于x递增的下标2 3 5 7,则z是x的一个子序。对于x和y两个序列,可以使用动态规划求解其最长的公共子序。

    X=x1...xm,Y=y1..yn,任意子序列Z=z1..zk

    1. 如果xm=yn,则zk=xm=yn,且zk-1是xm-1和yn-1的一个LCS
    2. 如果xm != yn,则zk != xm意味着Z是Xm-1和Y的一个LCS
    3. 如果xm != yn,则zk != yn意味着Z是X和Yn-1的一个LCS

     由此得到递推公式,其中c[i,j]表示Xi和Yj的LCS长度
             | 0                           if i==0 || j==0
    c[i,j]=| c[i-1,j-1]+1            if i>0 && j>0 && xi==yi
             | max(c[i,j-1],c[i-1])  if i>0 && j>0 && xi!=yi

    不用迭代求解方法,也可以使用一个自底向上的计算。
    可以通过子问题保存,最后求得原问题。其中使用存储表c[0..m, 0..n]中的c[i,j]表示表示Xi和Yj的LCS长度

    static void Main(string[] args)
    {
        //第一个字符无意义
        var X = new char[] { '', 'A', 'B', 'C', 'B', 'D', 'A', 'B' };
        var Y = new char[] { '', 'B', 'D', 'C', 'B', 'A' };
        int[,] c;
        string[,] b;
        LcsLength(X, Y, out c, out b);
        for (int i = 1; i < X.Length; i++)
        {
            for (int j = 1; j < Y.Length; j++)
            {
                Console.Write(c[i, j] + "  ");
            }
            Console.WriteLine();
        }
        for (int i = 1; i < X.Length; i++)
        {
            for (int j = 1; j < Y.Length; j++)
            {
                Console.Write(b[i, j] + " ");
            }
            Console.WriteLine();
        }
        Console.WriteLine();
    }
    //X,Y的第一个字符没有用
    static void LcsLength(char[] X, char[] Y,out int[,] c,out string[,] b)
    {
        //构造子问题存储位置
        c = new int[X.Length, Y.Length];
        b = new string[X.Length, Y.Length];
    
        //最小子问题结果
        for (int i = 0; i < X.Length; i++)
        {
            c[i, 0] = 0;
        }
    
        for (int i = 1; i < X.Length; i++)
        {
            for (int j = 1; j < Y.Length; j++)
            {
                if (X[i] == Y[j])
                {
                    c[i, j] = c[i - 1, j - 1] + 1;
                    b[i, j] = "";
                }
                else if (c[i - 1, j] >= c[i, j - 1])
                {
                    c[i, j] = c[i - 1, j];
                    b[i, j] = "";
                }
                else
                {
                    c[i, j] = c[i, j - 1];
                    b[i, j] = "";
                }
            }
        }
    }

    看出算法会占用两块i*j大小的内存,通过优化,可以省去b。一个节点的下一走向只有三个方向,我可以判断出来。

    *最优二叉搜索树

  • 相关阅读:
    常用 JS代码
    java中的变量各占得字节数
    70种简单好用的js代码
    JavaScript window.document的属性、方法和事件小结
    JS的event对象使用总结
    PHP魔术函数执行时间和顺序解析
    javascript event详解
    JS获取鼠标点击对象
    ThinkPHP实例化模型的四种方法
    实践:服务器编写/系统架构/cache
  • 原文地址:https://www.cnblogs.com/qiusuo/p/5225904.html
Copyright © 2011-2022 走看看