【演算法導論】動態規劃__演算法

來源:互聯網
上載者:User

動態規劃這個演算法,我一直都搞不明白,也許因為我數學能力太差的緣故,總是不得其要領,每次學習這個演算法的時候,總是不知道所謂的狀態轉移方程到底是怎麼樣推匯出來的。其實就在我寫這篇部落格的時候,我依然不清楚。

什麼問題能用動態規劃來解決呢。動態規劃問題的特徵就是 最優子結構,一個遞迴結構: 該問題需要求一個最優解 該問題重複包含著子問題

比如說經典的動態規劃問題,最長公用子序列(如下所述),我們要求一個最長的子序列,首先這是一個最優解,其次,兩個序列的最長公用序列,也同時包含著子序列的最長公用序列。

動態規劃通常按以下4個步驟來設計一個演算法: 刻畫一個最優解的結構特徵 遞迴地定義最優解的值 計算最優解的值(通常採用自底向上的方法) 得用計算出的資訊構造一個最優解

動態規劃往往還結合一些備忘方法,用於記錄中間解,以免重複求解子問題。下面來看幾個問題:

鋼條切割問題 最長公用子序列 最大連續子序列和

鋼條切割問題

給定一個鋼條切割的長度和價格,求出最佳的切割方案。且看下列:

長度(i) 1 2 3 4 5 6 7 8 9 10
價格(pi) 1 5 8 9 10 17 17 20 24 30

假設我們切割一條長度為 4 的鋼條,總共有以下幾種方案:
1. 不切割,價格為 9
2. 1 和 3 :價格為 1 + 8 = 9
3. 2 和 2 :價格為 5 + 5 = 10
4. 3 和 1 :價格為 8 + 1 = 9
5. 1、2、1 :價格為 1 + 5 + 1 = 7
6. 1、1、2 :價格為 1 + 1 + 5 = 7
7. 2、1、1 :價格為 2 + 1 + 1 = 7

可知道分割一條長度為4的鋼條,最優的切割方案為「方案2」。更通俗一點,我們可以令Rn為切割長度為n的鋼條,可以很輕鬆的得到以下公式:

這樣,代碼就容易寫了,如下所示:

#include <stdio.h>#include <stdlib.h>#include <string.h>int rodCut_recursive_down_to_up(int* p, int* s, int* k, int m, int n){    if(s[n] > - 1)        return s[n];    int q = -1;    if(n == 0)        return 0;    for(int i = 1; i <= n; ++i)    {        for(int j = 1; j <= i; ++j)        {            int r = *(p + j) + rodCut_recursive_down_to_up(p, s, k, m, i - j);            if(r > q)            {                q = r;                s[i] = q;                k[i] = j;            }        }    }    return q;}int rodCut_recursive_up_to_down(int* p, int* s, int* k, int m, int n){    if(s[n] > -1)        return s[n];    int q = -1;    if(n == 0)        return 0;    for(int i = n; i >= 1; --i)    {        int r = *(p + i) + rodCut_recursive_up_to_down(p, s, k, m, n - i);        if(r >= q)        {            q = r;            s[n] = q;            k[n] = i;        }    }    return q;}int main(void){    int m, n;    scanf("%d", &m);    if(m != 0)    {        int* p = (int*)malloc(sizeof(int) * (m + 1));        int* s = (int*)malloc(sizeof(int) * (m + 1));        int* k = (int*)malloc(sizeof(int) * (m + 1));        memset(p, 0, sizeof(int) * (m + 1));        memset(k, 0, sizeof(int) * (m + 1));        for(int i = 1; i != m + 1; ++i)            scanf("%d", p + i);        while(scanf("%d", &n) != EOF)        {            if(n <= m)            {                memset(s, -1, sizeof(int) * (m + 1));                printf("%d: ", rodCut_recursive_up_to_down(p, s, k, m, n));                while(n)                    printf("%d ", k[n]), n = n - k[n];                printf("\n");            }        }        free(p);        free(k);        free(s);    }    return 0;}

輸入價格表,然後輸入切割的長度,輸出最優方案,結果如下所示:

以上提供了兩種方案,一種是自頂向下的遞迴,一種是自底向上的遞迴設計,從代碼中我們可以看到兩者的區別,自頂向下,是先求解最後的結果,然後遞迴求解至第一個結果,而自底向上則是先求解第一個結果,直至最後一個結果。 最長公用子序列

最長公用子序列是這樣一個問題:有兩個字串S和T,求出兩者最長的公用子序列,如以下字串:

S:ABCBDAB
T:BDCABA

其中的最長公用子序列是 BDAB、BCBA、BCAB等。

令c[i, j]表示從i到j的最長公用子序列,我們可以推匯出以下狀態轉移式:

如果i到達字串S的長度或j到達字串T的長度,則最長公用子序列的長度為0 如果S[i] == T[j],那麼最長公用子序列即為後續子序列的最長公用子序列+當前字元長度(1) 如果S[i] != T[j],那麼最長公用子序列即為取S[i]的後續最長公用子序列與取T[i]的後續最長公用子序列的較大者(最後結果中未必有S[i]或T[j])

根據上述說明,寫出的代碼如下所示:

#include <stdio.h>#include <stdlib.h>#include <string.h>int LongestCommonSubSequence(char* S, char* T, char** U, int ss, int ts){    if(ss == strlen(S) || ts == strlen(T))        return 0;    else if(U[ss][ts] != 0)        return U[ss][ts];    else if(S[ss] == T[ts])    {        int r = LongestCommonSubSequence(S, T, U, ss + 1, ts + 1);        U[ss][ts] = r + 1;        return U[ss][ts];    }    else    {        int l1 = LongestCommonSubSequence(S, T, U, ss, ts + 1);        int l2 = LongestCommonSubSequence(S, T, U, ss + 1, ts);        U[ss][ts] = (l1 >= l2 ? l1: l2);        return U[ss][ts];    }    return 0;}int main (void){    int m, n;    while(scanf("%d %d", &m, &n) == 2)    {        char* S = (char*)malloc(sizeof(char) * (m + 1));        char* T = (char*)malloc(sizeof(char) * (n + 1));        char** U = (char**)malloc(sizeof(char*) * m);        for(int i = 0; i != m + 1; ++i)        {            U[i] = (char*)malloc(sizeof(char) * (n + 1));            memset(U[i], (char)0, sizeof(char) * (n + 1));        }        scanf("%s", S);        scanf("%s", T);        printf("%d\n\n", LongestCommonSubSequence(S, T, U,  0, 0));        for(int i = 0; i <= m; ++i)        {            for(int j = 0; j <= n; ++j)                printf("%d ", U[i][j]);            printf("\n");        }        int l = 0, r = 0;        while(U[l][r])        {            if(S[l] == T[r])            {                printf("%c", S[l]);                ++l, ++r;            }else if(U[l][r] == U[l][r + 1])                ++r;            else                ++l;        }        printf("\n");        free(S);        free(T);        for(int i = 0; i != m + 1; ++i)            free(U[i]);        free(U);    }    return 0;}

輸入字串S和T,ss表示S串的起點,ts表示T串的起點,U記錄當前位置的最長公用子序列長度。輸出的結果如下所示:

從以上圖表可以看到,我們要拿到最長的公用子序列,則需要從【0, 0】點開始遍曆表,我們只能往右或往下遍曆,如果【i, j】點的兩個字元S[i]和T[j]相等,那麼我們應該走對角線,如果【i, j】== 【i, j + 1】那麼我們應該往右走,否則應該往下走。如以上代碼所示。 最大連續子序列和

給定一個整數序列,求其中的一個連續子序列,使其和最大。如給定以下序列:

-2 11 -4 13 -5 -2, 其最大連續子序列和為: 11 -4 + 13 = 20

定義sum[i]為最大子序列和,可以推匯出以下狀態轉移方程:

其實,這個方程,我並不知道是怎麼樣推匯出來的,前面一個好理解,不過為什麼要和當前值做比較取較大者,我真的不明白。希望有人能給我指點迷津。根據以上方程,我寫出的代碼如下所示:

#include <stdio.h>#include <string.h>#include <stdlib.h>int maxSum = 0;int maxSequenceSum(int* num, int s, int e){    if(s == e)        return 0;    int sum = maxSequenceSum(num, s + 1, e) + num[s];    if(sum > 0)    {        if(sum > maxSum)        {            maxSum = sum;            return sum;        }        return sum;    }    return 0;}int main(void){    int m;    while(scanf("%d", &m) != EOF)    {        int* n = (int*)malloc(sizeof(int) * m);        for(int i = 0; i != m; ++i)            scanf("%d", n + i);        maxSum = 0;        maxSequenceSum(n, 0, m);        printf("%d\n", maxSum);        free(n);    }    return 0;}

程式的輸出結果,如下圖所示:

還有一種更簡單的寫法:

int MaxSumSubSequence(const int A[], int N){    int ThisSum,MaxSum,j;    ThisSum = MaxSum =0;    for(j = 0;j < N;j++)    {        ThisSum += A[j];        if(ThisSum > MaxSum)            MaxSum = ThisSum;        else if(ThisSum < 0)            ThisSum = 0;     }    return MaxSum; }

這段代碼是網上抄的,我一開始真沒想到這麼寫。我的水平還有很大的提升空間。

以上代碼本人親自編寫,經本人測試基本無誤,也許編寫方式和網上流傳有所不同,如有錯漏,請批評指正。

動態規劃這個演算法真的實用性很大,可惜我依然是半知半解,之所以厚臉皮寫出這篇文章,也是希望有一日能被某個高人碰巧看到這篇拙劣的博文,指點一下我這個苦苦自學,身邊沒朋友諮詢的小弟,在此感謝先。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.