算法之动态规划初步(Java版)

概述:

算法的重要性是不言而喻的。

可能是你会不屑于听这样的话,是因为在我们的实际开发中,用到算法的地方真是太少了。对于这一点我并不否认,因为对于一个初级的开发者而言,算法显得太过高深了。如果我们想去实现一个功能,通常的做法就是百度或是Google。这就是为什么会有那么一句调侃之辞:我们不生产代码,我们只是代码的搬运工。

当我们已经完成了初级开发者的这一过程时,我们应该想着怎么去优化自己的代码,从而让自己的代码更加优美,也更显B格。

动规的使用场景:

动态规划是对回溯算法的一种改进。

我们知道回溯的一个致命缺点是它的重复计算,在后面的例子中我也会通过实例来说明这一点,而动规则规避了这个问题。动规的核心是状态和状态转移方程。

示例例举及过程说明:

1.数字三角形

问题描述:

有一个由非负整数组成的三角形,第一行只有一个数,除了最后一行之外每个数的左下方和右下方各有一个数。

从第一行的数开始,每次可以往下或右下走一格,直到走到最后一行,把沿涂经过的数全部加起来。如何走才能使得这个数尽可能的大?

思路梳理:

对于这样一个问题,可能大家想到的第一个解法就是递归。对于递归,我们不用想太多。因为当我们想要知道第(i, j)处的最大值时,是要依赖第(i + 1, j)和第(i + 1, j + 1)个节点的最大值。以此类推,如是我们就可以使用递归和递推来实现。

递归求解(关键代码)

/**
     * 通过回溯法获得第(i, j)处的最大值
     * @author Aaron
     * 2015年8月2日
     */
    private static int getNodeMaxByRecall(int[][] m, int i, int j) {
        int max = Integer.MIN_VALUE;

        System.out.println("m[" + i + "][" + j + "]");

        max = m[i][j] + (i == m.length - 1 ? 0 : Math.max(getNodeMaxByRecall(m, i + 1, j), getNodeMaxByRecall(m, i + 1, j + 1)));

        return max;
    }

    /**
     * 回溯法求解
     * @author Aaron
     * 2015年8月1日
     */
    public static void calculateByRecall(int[] a) {
        int[][] m = getMatrix(a);

        int max = getNodeMaxByRecall(m, 0, 0);

        System.out.println("max[0][0] = " + max);
    }

可以看到,递归求解时是一种自顶向下的求解方式。它是在按需去计算。

在递归中,比如说我们的意图是去求解max(i, j),当我们知道需要求解max(i, j),就必须知道max(i + 1, j)和max(i + 1, j + 1)时,我们才去求解max(i + 1, j)和max(i + 1, j + 1).

可是,这种按需求解的过程,无法让我们知道,再要计算的点是否已经计算过了。下面是这个程序在递归的过程中计算过的节点过程:

可以看到,这里有一些节点是被重复计算的。

递推法求解(关键代码):

/**
     * 通过递推法获得第(i, j)处的最大值
     * @author Aaron
     * 2015年8月2日
     */
    private static int getNodeMaxByRecursion(int[][] m, int i, int j) {
        int max = Integer.MIN_VALUE;

        System.out.println("m[" + i + "][" + j + "]");

        max = m[i][j] + (i == m.length - 1 ? 0 : Math.max(m[i + 1][j], m[i + 1][j + 1]));

        return max;
    }

    /**
     * 递推法求解
     * @author Aaron
     * 2015年8月2日
     */
    private static void calculateByRecursion(int[] a) {
        int[][] m = getMatrix(a);

        for (int i = m.length - 1; i >= 0; i--) {
            for (int j = 0; j <= i; j++) {
                m[i][j] = getNodeMaxByRecursion(m, i, j);
            }
        }

        int max = m[0][0];

        System.out.println("max[0][0] = " + max);
    }

可以看到,递推求解时是一种自底向上的求解方式。它是在预先计算。

在递推中,比如说我们的意图是去求解max(i, j),当我们知道需要求解max(i, j),就必须知道max(i + 1, j)和max(i + 1, j + 1)时,不过这个时候,我们的max(i + 1, j)和max(i + 1, j + 1)已经计算出来了,这个时候我们就不用再去计算了.

在递推的计算过程中,因为我们是自底向上的求解,所以我们并不知道这个节点是否会被使用到,而如果这个节点需要被使用,我们也不会重复计算这个值,因为已经计算过,并已经保存下来了。不过,这个过程中,每个节点都会被计算一次,不管会不会被使用(虽然这个程序中是都被使用了)。

可以看到,这里每个节点有且仅有一次被调用了。时间复杂度上就有了一些优势。

记忆化求解(关键代码):

/**
     * 通过记忆化搜索获得第(i, j)处的最大值
     * @author Aaron
     * 2015年8月2日
     */
    private static int getNodeMaxByMemory(int[][] m, int[][] d, int i, int j) {
        if (d[i][j] >= 0) {
            return d[i][j];
        }

        System.out.println("m[" + i + "][" + j + "]");

        d[i][j] = m[i][j] + (i == m.length - 1 ? 0 : Math.max(getNodeMaxByMemory(m, d, i + 1, j), getNodeMaxByMemory(m, d, i + 1, j + 1)));

        return d[i][j];
    }

    /**
     * 记忆化搜索
     * @author Aaron
     * 2015年8月2日
     */
    private static void calculateByMemory(int[] a) {
        int[][] m = getMatrix2(a);

        int[][] d = initMatrix(m.length);

        int max = getNodeMaxByMemory(m, d, 0, 0);

        System.out.println("max[0][0] = " + max);
    }

记忆化搜索是基于递归来进行的。因为我们想做一件事,来避免之前在递归中的重复计算。在学习算法的复杂度的时候,我们知道复杂度分为两种,一种是时间复杂度,一种是空间复杂度。这两种复杂度是有一个平衡的。也就是说我们想要在时间上优化,那么空间上就得做出牺牲。这里也正是使用了牺牲空间来换取时间的优先。

下面是各个节点被计算的过程:

这里可以看到,我们的每个节点也是只被计算了一次。节省了时间。

2.钢条切割

问题描述:

给定一段长度为n英寸的钢条和一个价格表p(i),求切割钢条的方案,使得销售收益r(n)最大。注意,如果长度为n英寸的钢条的价格为p(n)足够大,最优解可能不是完全不需要切割。

价格表:

看到这一个问题,不知道大家是不是也跟我一样,第一感觉是可以使用贪心试一下。可是细想之后又发现行不通,因为这里面如果按不同的方式切割钢条,那么切割成的两份都是可变的量,不好控制。

按照上面的思路,我们可以使用两种方法来试着解决这一问题:

递归(关键代码):

/**
     * 计算长度为n的钢条的最佳切割方案(递归)
     * @author Aaron
     * 2015年8月3日
     */
    private static int getMax(int[] p, int n) {
        System.out.println(n);
        if (n <= 0) {
            return 0;
        }

        int max = Integer.MIN_VALUE;

        for (int i = 1; i <= n; i++) {
            max = Math.max(max, p[i - 1] + getMax(p, n - i));
        }

        return max;
    }

    /**
     * 通过递归计算钢条的切割算法
     * @author Aaron
     * 2015年8月3日
     */
    private static void calculateMaxByRecursive(int n) {
        initPriceList();
        int[] p = {1, 5, 8, 9, 10, 17, 17, 20, 24, 30};

        int max = getMax(p, n);

        System.out.println("max = " + max);
    }

记忆化搜索(关键代码):

private static int[] getRecordArray(int n) {
        if (n <= 0) {
            return null;
        }

        int[] r = new int[n];
        for (int i = 0; i < n; i++) {
            r[i] = Integer.MIN_VALUE;
        }

        return r;
    }

    /**
     * 计算长度为n的钢条的最佳切割方案(记忆化搜索)
     * @author Aaron
     * 2015年8月3日
     */
    private static int getMaxByMemory(int[] p, int n, int[] r) {
        if (n <= 0) {
            return 0;
        }

        if (r[n] >= 0) {
            return r[n];
        }

        System.out.println(n);

        int max = Integer.MIN_VALUE;

        for (int i = 1; i <= n; i++) {
            max = Math.max(max, p[i - 1] + getMaxByMemory(p, n - i, r));
        }

        r[n] = max;

        return max;
    }

    /**
     * 通过记忆化搜索计算钢条的切割算法
     * @author Aaron
     * 2015年8月3日
     */
    private static void calculateMaxByMemory(int n) {
        initPriceList();
        int[] p = {1, 5, 8, 9, 10, 17, 17, 20, 24, 30};

        int[] r = getRecordArray(n + 1);

        int max = getMaxByMemory(p, n, r);

        System.out.println("max = " + max);
    }

完整源代码下载:

http://download.csdn.net/detail/u013761665/8957807

版权声明:本文为博主原创文章,未经博主允许不得转载。http://blog.csdn.net/lemon_tree12138

时间: 2024-10-09 07:40:28

算法之动态规划初步(Java版)的相关文章

微博URL短网址生成算法原理及(java版、php版实现实例)

短网址(Short URL),顾名思义就是在形式上比较短的网址.通常用的是asp或者php转向,在Web 2.0的今天,不得不说,这是一个潮流.目前已经有许多类似服务,借助短网址您可以用简短的网址替代原来冗长的网址,让使用者可以更容易的分享链接. 例如:http://t.cn/SzjPjA 短网址服务,可能很多朋友都已经不再陌生,现在大部分微博.手机邮件提醒等地方已经有很多应用模式了,并占据了一定的市场.估计很多朋友现在也正在使用. 看过新浪的短连接服务,发现后面主要有6个字符串组成,于是第一个

算法研究之快速排序java版

很早之前就已经接触过快速排序算法了,面试当中也屡屡被问到,虽然明白其原理,但从未真正的用代码敲出来. 写关于算法的代码之前一定要原理想明白,不然就是盲目,在参考有关资料及自己的沉思之后,写出如下代码,中间出现了一些bug,但都很快解决了 如果有更好的优化算法,还请不吝赐教!!!! 源代码: package com.zken.test; /** * @author iamzken * 排序算法 * 使用快速排序算法对一个数组从小到大排序 * 2015-8-27 13:40 */ public cl

常见排序算法代码总结(Java版)

学习自菜鸟教程,自己加以总结希望可以多多重复!!! 冒泡排序 选择排序 插入排序 希尔排序 快速排序 归并排序 堆排序 基数排序 1 //冒泡排序 O(n2) 2 public static void BubbleSort(int[] arr){ 3 int temp; 4 for(int i=0;i<arr.length-1;i++){ 5 for(int j=0;j<arr.length-1-i;j++){ 6 if(arr[j]>arr[j+1]){ 7 temp = arr[j]

排序算法Java版,以及各自的复杂度,以及由堆排序产生的top K问题

常用的排序算法包括: 冒泡排序:每次在无序队列里将相邻两个数依次进行比较,将小数调换到前面, 逐次比较,直至将最大的数移到最后.最将剩下的N-1个数继续比较,将次大数移至倒数第二.依此规律,直至比较结束.时间复杂度:O(n^2) 选择排序:每次在无序队列中"选择"出最大值,放到有序队列的最后,并从无序队列中去除该值(具体实现略有区别).时间复杂度:O(n^2) 直接插入排序:始终定义第一个元素为有序的,将元素逐个插入到有序排列之中,其特点是要不断的 移动数据,空出一个适当的位置,把待插

回溯算法解八皇后问题(java版)

八皇后问题是学习回溯算法时不得不提的一个问题,用回溯算法解决该问题逻辑比较简单. 下面用java版的回溯算法来解决八皇后问题. 八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例.该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同一列或同一斜线上,问有多少种摆法. 思路是按行来规定皇后,第一行放第一个皇后,第二行放第二个,然后通过遍历所有列,来判断下一个皇后能否放在该列.直到所有皇后都放完,或者放哪

70. Climbing Stairs【leetcode】递归,动态规划,java,算法

You are climbing a stair case. It takes n steps to reach to the top. Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top? Note: Given n will be a positive integer. 题目分析:每次只能走1或2步,问n步的话有多少中走法???? 可以用动态规划和递归解

MapReduce原理——PageRank算法Java版

Page Rank就是MapReduce的来源,下文是一个简单的计算PageRank的示例. import java.text.DecimalFormat; /**  * Created by jinsong.sun on 2014/7/15.  */ public class PageRankCaculator {     public static void main(String[] args) {         double[][] g = calcG(genS(), 0.85);  

扎金花大小比较算法(Java版)

注:以下算法说明仅限一副牌(不包含大小王)的情况 1.扎金花规则说明(大家都懂的,这里做简单描述): 1)玩家每人3张牌: 2)牌面大小2.3.4.5.6.7.8.9.10(用T表示),J.Q.K.A,大小依次递增: 3)牌的花色有黑桃(用H表示).红心(用X表示).梅花(用M表示).方块(用F表示),大小依次递减: 4)牌有豹子(3张牌数字大小相同).同花顺.同花(此种未实现,有兴趣的玩家可以自己加上,或者欢迎和我交流).顺子.对子.散牌几种类型,大小依次递减: 5)玩家先比牌的类型,如先按照

经典排序算法(Java版)

经典排序算法(Java版)  转载 1.冒泡排序 Bubble Sort最简单的排序方法是冒泡排序方法.这种方法的基本思想是,将待排序的元素看作是竖着排列的“气泡”,较小的元素比较轻,从而要往上浮.在冒泡排序算法中我们要对这个“气泡”序列处理若干遍.所谓一遍处理,就是自底向上检查一遍这个序列,并时刻注意两个相邻的元素的顺序是否正确.如果发现两个相邻元素的顺序不对,即“轻”的元素在下面,就交换它们的位置.显然,处理一遍之后,“最轻”的元素就浮到了最高位置:处理二遍之后,“次轻”的元素就浮到了次高位