演算法之動態規劃初步(Java版)

來源:互聯網
上載者:User

標籤:動態規劃   演算法   

概述:

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

  可能是你會不屑於聽這樣的話,是因為在我們的實際開發中,用到演算法的地方真是太少了。對於這一點我並不否認,因為對於一個初級的開發人員而言,演算法顯得太過高深了。如果我們想去實現一個功能,通常的做法就是百度或是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

演算法之動態規劃初步(Java版)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.