1. 程式人生 > >這個動態規劃分析的很詳細(轉載)

這個動態規劃分析的很詳細(轉載)

動態規劃

通過把原問題分解為相對簡單的子問題的方式求解複雜問題的方法。動態規劃常常適用於有重疊子問題和最優子結構性質的問題。

基本思想

若要解一個給定問題,我們需要解其不同部分(即子問題),再合併子問題的解以得出原問題的解。 通常許多子問題非常相似,為此動態規劃法試圖僅僅解決每個子問題一次,從而減少計算量: 一旦某個給定子問題的解已經算出,則將其記憶化儲存,以便下次需要同一個子問題解之時直接查表。 這種做法在重複子問題的數目關於輸入的規模呈指數增長時特別有用。

分治與動態規劃

共同點:二者都要求原問題具有最優子結構性質,都是將原問題分而治之,分解成若干個規模較小(小到很容易解決的程式)的子問題.然後將子問題的解合併,形成原問題的解.

不同點:分治法將分解後的子問題看成相互獨立的,通過用遞迴來做。

     動態規劃將分解後的子問題理解為相互間有聯絡,有重疊部分,需要記憶,通常用迭代來做。

問題特徵

最優子結構:當問題的最優解包含了其子問題的最優解時,稱該問題具有最優子結構性質。

重疊子問題:在用遞迴演算法自頂向下解問題時,每次產生的子問題並不總是新問題,有些子問題被反覆計算多次。動態規劃演算法正是利用了這種子問題的重疊性質,對每一個子問題只解一次,而後將其解儲存在一個表格中,在以後儘可能多地利用這些子問題的解。

步驟

描述最優解的結構

遞迴定義最優解的值

按自底向上的方式計算最優解的值

由計算出的結果構造一個最優解

注意需要需要二維陣列用容器,C++動態分配二維陣列太坑爹

典型問題

01揹包問題

揹包九講:http://www.cnblogs.com/jbelial/articles/2116074.html

其狀態轉移方程便是:f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}。

這個方程非常重要,基本上所有跟揹包相關的問題的方程都是由它衍生出來的。所以有必要將它詳細解釋一下:“將前i件物品放入容量為v的揹包中”這個子問題,若只考慮第i件物品的策略(放或不放),那麼就可以轉化為一個只牽扯前i-1件物品的問題。如果不放第i件物品,那麼問題就轉化為“前i-1件物品放入容量為v的揹包中”;如果放第i件物品,那麼問題就轉化為“前i-1件物品放入剩下的容量為v-c[i]的揹包中”,此時能獲得的最大價值就是f [i-1][v-c[i]]再加上通過放入第i件物品獲得的價值w[i]。

複製程式碼
int main()
{
    //int m = 120;
    //int n = 5;
    //vector<int> w = { 0, 40, 50, 70, 40, 20 };
    //vector<int> v = { 0, 10, 25, 40, 20, 10 };

    int m, n;    //m重量,n數量
    while (cin >> m >> n)
    {
        vector<int> w(n + 1, 0);
        vector<int> v(n + 1, 0);
        for (int i = 1; i <= n; i++)
        {
            int tmp;
            cin >> tmp;
            w[i] = tmp;
        }
        for (int i = 1; i <= n; i++)
        {
            int tmp;
            cin >> tmp;
            v[i] = tmp;
        }
        vector< vector<int> > vec(n + 1, vector<int>(m + 1, 0));
        for (int i = 1; i <= n; i++)
        {
            for (int j = 1; j <= m; j++)
            {
                if (w[i] > j)
                    vec[i][j] = vec[i - 1][j];
                else
                {
                    int tmp1 = v[i] + vec[i - 1][j - w[i]];
                    int tmp2 = vec[i - 1][j];
                    vec[i][j] = tmp1 > tmp2 ? tmp1 : tmp2;
                }
            }
        }
        double val = vec[n][m] * 0.1;
        cout << val << endl;
    }

    system("pause");
}
複製程式碼

最長公共子序列(不連續) LCS  Longest Common Subsequence

找兩個字串的最長公共子串,這個子串要求在原字串中是連續的。而最長公共子序列則並不要求連續。

cnblogs與belong,最長公共子序列為blog(cnblogs, belong),最長公共子串為lo(cnblogs, belong)

這兩個問題都是用空間換空間,建立一個二維陣列來記錄之前的每個狀態

狀態轉移方程:

用i,j遍歷兩個子串x,y,如果兩個元素相等就+1 ,不等就用上一個狀態最大的元素

複製程式碼
 1 public static int lcs(String str1, String str2) {
 2     int len1 = str1.length();
 3     int len2 = str2.length();
 4     int c[][] = new int[len1+1][len2+1];
 5     for (int i = 0; i <= len1; i++) {
 6         for( int j = 0; j <= len2; j++) {
 7             if(i == 0 || j == 0) {
 8                 c[i][j] = 0;
 9             } else if (str1.charAt(i-1) == str2.charAt(j-1)) {
10                 c[i][j] = c[i-1][j-1] + 1;
11             } else {
12                 c[i][j] = max(c[i - 1][j], c[i][j - 1]);
13             }
14         }
15     }
16     return c[len1][len2];
17 }
複製程式碼

最長公共子串(連續)

狀態轉移方程:

區別就是因為是連續的,如果兩個元素不等,那麼就要=0了而不能用之前一個狀態的最大元素

複製程式碼
 1 public static int lcs(String str1, String str2) {
 2     int len1 = str1.length();
 3     int len2 = str2.length();
 4     int result = 0;     //記錄最長公共子串長度
 5     int c[][] = new int[len1+1][len2+1];
 6     for (int i = 0; i <= len1; i++) {
 7         for( int j = 0; j <= len2; j++) {
 8             if(i == 0 || j == 0) {
 9                 c[i][j] = 0;
10             } else if (str1.charAt(i-1) == str2.charAt(j-1)) {
11                 c[i][j] = c[i-1][j-1] + 1;
12                 result = max(c[i][j], result);
13             } else {
14                 c[i][j] = 0;
15             }
16         }
17     }
18     return result;
19 }
複製程式碼

KMP

KMP演算法

硬幣找零問題

假設有幾種硬幣,如1 5 10 20 50 100,並且數量無限。請找出能夠組成某個數目的找零所使用最少的硬幣數。

解法:

用待找零的數值k描述子結構/狀態,記作sum[k],其值為所需的最小硬幣數。對於不同的硬幣面值coin[0...n],有sum[k] = min(sum[k-coin[0]] , sum[k-coin[1]], ...)+1。對應於給定數目的找零total,需要求解sum[total]的值。

注意要從前往後算,從後往前算無法儲存狀態,需要遞迴,效率很低,就不是動態規劃了

 View Code

類似硬幣的問題找平方個數最小

題目:

給一個正整數 n, 找到若干個完全平方數(比如1, 4, 9, ... )使得他們的和等於 n。你需要讓平方數的個數最少。
給出 n = 12, 返回 3 因為 12 = 4 + 4 + 4。
給出 n = 13, 返回 2 因為 13 = 4 + 9。  View Code

最長迴文字串

迴文字串的子串也是迴文,比如P[i,j](表示以i開始以j結束的子串)是迴文字串,那麼P[i+1,j-1]也是迴文字串。這樣最長迴文子串就能分解成一系列子問題了。這樣需要額外的空間O(N^2),演算法複雜度也是O(N^2)。

首先定義狀態方程和轉移方程:

P[i,j]=0表示子串[i,j]不是迴文串。P[i,j]=1表示子串[i,j]是迴文串。

P[i+1][j-1]&&s.at(i)==s.at(j)

初始化是準備兩個元素是迴文的情況aa,bb

複製程式碼
 1 string findLongestPalindrome(string &s)
 2 {
 3     const int length=s.size();
 4     int maxlength=0;
 5     int start;
 6     bool P[50][50]={false};
 7     for(int i=0;i<length;i++)//初始化準備
 8     {
 9         P[i][i]=true;
10         if(i<length-1&&s.at(i)==s.at(i+1))
11         {
12             P[i][i+1]=true;
13             start=i;
14             maxlength=2;
15         }
16     }
17     for(int len=3;len<length;len++)//子串長度
18         for(int i=0;i<=length-len;i++)//子串起始地址
19         {
20             int j=i+len-1;//子串結束地址
21             if(P[i+1][j-1]&&s.at(i)==s.at(j))
22             {
23                 P[i][j]=true;
24                 maxlength=len;
25                 start=i;
26             }
27         }
28     if(maxlength>=2)
29         return s.substr(start,maxlength);
30     return NULL;
31 }
複製程式碼

最長遞增序列

問題:設L=<a1,a2,…,an>是n個不同的實數的序列,L的遞增子序列是這樣一個子序列Lin=<aK1,ak2,…,akm>,其中k1<k2<…<km且aK1<ak2<…<akm。求最大的m值。

第一種方法,排序,然後用LCS來解決:設序列X=<b1,b2,…,bn>是對序列L=<a1,a2,…,an>按遞增排好序的序列。那麼顯然X與L的最長公共子序列即為L的最長遞增子序列。這樣就把求最長遞增子序列的問題轉化為求最長公共子序列問題LCS了。

第二種:時間複雜度O(N^2)的演算法:

LIS[i]:表示陣列前i個元素中(包括第i個),最長遞增子序列的長度

LIS[i] = max{ 1, LIS[k]+1 }, 0 <= k < i, a[i]>a[k]

LIS陣列的值表示前i個元素的最長子序列。i從第一個元素到最後一個元素遍歷一遍,j從第一個元素到第i個元素遍歷,如果第i個元素大於j,並且LIS[J] + 1比LIS[I]還大就更新,相當於把j加入到這個遞增序列了

複製程式碼
 1 int LIS(int a[], int length)
 2 {
 3     int *LIS = new int[length];
 4     for(int i = 0; i < length; ++i)
 5     {
 6         LIS[i] = 1; //初始化預設長度
 7         for(int j = 0; j < i; ++j) //前面最長的序列
 8             if(a[i] > a[j] && LIS[j]+1 > LIS[i])
 9                 LIS[i] = LIS[j]+1;  
10     }
11     int max_lis = LIS[0];
12     for(int i = 1; i < length; ++i)
13         if(LIS[i] > max_lis)
14             max_lis = LIS[i];
15     return max_lis;  //取LIS的最大值
16 }
複製程式碼

字串相似度/編輯距離(edit distance)

N皇后問題

其他問題

1.某幢大樓有100層。你手裡有兩顆一模一樣的玻璃珠。當你拿著玻璃珠在某一層往下扔的時候,一定會有兩個結果,玻璃珠碎了或者沒碎。這幢大樓有個臨界樓層。低於它的樓層,往下扔玻璃珠,玻璃珠不會碎,等於或高於它的樓層,扔下玻璃珠,玻璃珠一定會碎。玻璃珠碎了就不能再扔。現在讓你設計一種方式,使得在該方式下,最壞的情況扔的次數比其他任何方式最壞的次數都少。也就是設計一種最有效的方式。

例如:有這樣一種方式,第一次選擇在60層扔,若碎了,說明臨界點在60層及以下樓層,這時只有一顆珠子,剩下的只能是從第一層,一層一層往上實驗,最壞的情況,要實驗59次,加上之前的第一次,一共60次。若沒碎,則只要從61層往上試即可,最多隻要試40次,加上之前一共需41次。兩種情況取最多的那種。故這種方式最壞的情況要試60次。仔細分析一下。如果不碎,我還有兩顆珠子,第二顆珠子會從N+1層開始試嗎?很顯然不會,此時大樓還剩100-N層,問題就轉化為100-N的問題了。

那該如何設計方式呢?

根據題意很容易寫出狀態轉移方程:N層樓如果從n層投下玻璃珠,最壞的嘗試次數是:clip_image002[6]

那麼所有層投下的最壞嘗試次數的最小值即為問題的解:clip_image002[8]。其中F(1)=1.

複製程式碼
 1 /*
 2 *侯凱,2014-9-15
 3 *功能:100樓層拋珠問題
 4 */
 5 #include<iostream>
 6 using namespace std;
 7 
 8 int max(int a, int b)
 9 {
10     return (a > b)? a : b;
11 }
12 
13 int dp[101];
14 //N<=100;
15 int floorThr(int N)
16 {
17     for (int i = 2; i <= N; i++)
18     {
19         dp[i] = i;
20         for (int j = 1; j<i; j++)
21         {
22             int tmp = max(j, 1 + dp[i - j]);    //j的遍歷相當於把每層都試一遍
23             if (tmp<dp[i])
24                 dp[i] = tmp;
25         }
26     }
27     return dp[N];
28 }
29 
30 int main()
31 {
32     dp[0] = 0;
33     dp[1] = 1;
34     int dis = floorThr(100);
35     cout << dis << endl;
36     system("Pause");
37 }
複製程式碼

輸出為14,說明在合適的樓層拋玻璃珠,最差情況下只需14次可找到臨界層。

答案是先從14樓開始拋第一次;如果沒碎,再從27樓拋第二次;如果還沒碎,再從39樓拋第三次;如果還沒碎,再從50樓拋第四次;如此,每次間隔的樓層少一層。這樣,任何一次拋棋子碎時,都能確保最多拋14次可以找出臨界樓層。

N*N方格內的走法問題

複製程式碼
 1 #include<iostream>
 2 #include<vector>
 3 using namespace std;
 4 
 5 int main()
 6 {
 7     int n;
 8     while (cin >> n)
 9     {
10         vector<vector<int>> dp(n+1, vector<int>(n+1, 1));
11         for (int i = 1; i <= n;i++)
12         {
13             for (int j = 1; j <= n;j++)
14             {
15                 dp[i][j] = dp[i][j - 1] + dp[i - 1][j];
16             }
17         }
18         cout << dp[n][n] << endl;
19     }
20 }
複製程式碼

其他問題參考:

http://www.cnblogs.com/wuyuegb2312/p/3281264.html#q1a1

http://www.cnblogs.com/luxiaoxun/archive/2012/11/15/2771605.html

相關推薦

這個動態規劃分析詳細轉載

動態規劃 通過把原問題分解為相對簡單的子問題的方式求解複雜問題的方法。動態規劃常常適用於有重疊子問題和最優子結構性質的問題。 基本思想 若要解一個給定問題,我們需要解其不同部分(即子問題),再合併子問題的解以得出原問題的解。 通常許多子問題非常相似,為此動態規劃法試圖僅僅解決每個子問題一次,從而減少計算量

26.動態規劃-打家劫舍-Leetcode 198python

題目描述 你是一個專業的小偷,計劃偷竊沿街的房屋。每間房內都藏有一定的現金,影響你偷竊的唯一制約因素就是相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警。 給定一個代表每個房屋存放金額的非負整數陣列,計算你在不觸動警報裝置的情況下,能夠偷竊到

動態規劃 -- 01揹包案例python

01揹包 m個物品價值為 p[m],體積為 w[m],現有一個容量為 v 的揹包,怎麼裝物品價值最大? 我們嘗試用動態規劃來解決這個問題 一般來說,只要問題可以劃分成規模更小的子問題,並且原問題的最優解中包含了子問題的最優解(即滿足最優子化原理),則可以

詳解AbstractRoutingDataSource(動態資料來源切換)實現原理轉載

轉載地址:詳解AbstractRoutingDataSource(動態資料來源切換)實現原理 多資料來源讓人最頭痛的,不是配置多個數據源,而是如何能靈活動態的切換資料來源。例如在一個spring和hibernate的框架的專案中,我們在spring配置中往往是配置一個dataSou

動態規劃 —— 迴文串

今天是週日,看著身邊的人都去踏青了,而我泡在實驗室刷了將近 10 題迴文數(串)相關的題目,這腦袋,也不知道被啥踢了。。但願苦心人,天不負吧。。。迴文串的相關題目,變化還是不少的。本部落格一點點呈現。題目包括:(1)判斷迴文串(數)(2)統計迴文個數(將兩個字串混合)(3)迴

NOI 4.5 動態規劃 6047:分蛋糕列舉

6047:分蛋糕總時間限制: 1000ms 記憶體限制: 65536kB描述有一塊矩形大蛋糕,長和寬分別是整數w、h。現要將其切成m塊小蛋糕,每個小蛋糕都必須是矩形、且長和寬均為整數。切蛋糕時,每次切一塊蛋糕,將其分成兩個矩形蛋糕。請計算:最後得到的m塊小蛋糕中,最大的那塊蛋

動態規劃題目--數塔HDU

因為之前沒有怎麼接觸過演算法,在賽碼網上刷題,碰上了動態規劃類的題目。正好藉此機會系統的學習一下。 在嗶哩嗶哩上看演算法視訊,裡面講解了一道HDU上的題目,下面貼下題目描述: ime Limit: 1000/1000 MS (Java/Others)    Memory

動態規劃 java 石子問題直線

一.石子問題與動態規劃的矩陣相乘問題類似,剛開始我在陣列邊界上出現了小問題,讓我知道了,陣列的邊界一定到精確,<還是<=,n還是n+1,都要精益求精; 二 .還有就是在i到j石子之和處出現問題,1.sum[j]-sum[i-1] 2.sum[j

快速冪C語言實現詳細 轉載

快速冪取模演算法 在網站上一直沒有找到有關於快速冪演算法的一個詳細的描述和解釋,這裡,我給出快速冪演算法的完整解釋,用的是C語言,不同語言的讀者只好換個位啦,畢竟讀C的人較多~ 所謂的快速冪,實際上是快速冪取模的縮寫,簡單的說,就是快速的求一個冪式的模(餘)。在程式設計過程中,經常要去求一些大數對於某個數的

動態規劃——有趣的數ccf

題目描述: 我們把一個數稱為有趣的,當且僅當: 1. 它的數字只包含0, 1, 2, 3,且這四個數字都出現過至少一次。 2. 所有的0都出現在所有的1之前,而所有的2都出現在所有的3之前。 3. 最高位數字不為0。 因此,符合我們定義的最小的有趣的數是2013。除此以外,

由淺入深分析mybatis通過動態代理實現攔截器外掛的原理 !值得一看

最近在用mybatis做專案,需要用到mybatis的攔截器功能,就順便把mybatis的攔截器原始碼大致的看了一遍,為了溫故而知新,在此就按照自己的理解由淺入深的理解一下它的設計。  和大家分享一下,不足和謬誤之處歡迎交流。直接入正題。  首先,先不管mybatis的原始碼

動態規劃:從新手到專家當然不可能看完就成專家轉載

動態規劃簡直有讓人說不出的魅力........不多說,上乾貨,各位轉載註明作者和出處哈 前言_ 我們遇到的問題中,有很大一部分可以用動態規劃(簡稱DP)來解。解決這類問題可以很大地提升你的能力與技巧,我會試著幫助你理解如何使用DP來解題。這篇文章是基於例項展開來講的,因

演算法分析與設計動態規劃

動態規劃的概念複習 每次決策依賴於當前狀態,又隨即引起狀態的轉移。一個決策序列就是在變化的狀態中產生的,所以,這種多階段最優化決策解決問題的過程就稱為動態規劃。 動態規劃的思想和策略 將待求解的問題分解為若干個子問題,按順序求解子階段,前一子問題的解,為後

動態規劃分析總結——怎樣設計和實現動態規劃算法

基於 進一步 使用 sdn 能夠 疑惑 樓梯 -1 們的 進行算法設計的時候,時常有這種體會:假設已經知道一道題目能夠用動態規劃求解,那麽非常easy找到對應的動態規劃算法並實現;動態規劃算法的難度不在於實現,而在於分析和設計—— 首先你得知道這道題目須要用動態規劃來求

SQLServer · BUG分析 · Agent 鏈接泄露分析轉載

空閑 doc ucc object bsp line existing rds 成功 背景 SQLServer Agent作為Windows服務提供給用戶定期執行管理任務,這些任務被稱為Job;考慮應用鏡像的場景如何解決Job同步問題,AWS RDS的做法是不予理會,由

主成分分析PCA原理詳解轉載

增加 信息 什麽 之前 repl 神奇 cto gmail 協方差 一、PCA簡介 1. 相關背景 上完陳恩紅老師的《機器學習與知識發現》和季海波老師的《矩陣代數》兩門課之後,頗有體會。最近在做主成分分析和奇異值分解方面的項目,所以記錄一下心得體會。

解釋一下核主成分分析(Kernel Principal Component Analysis, KPCA)的公式推導過程轉載

線性不可分 itl 專註 out center forest 測試 重要 原因 KPCA,中文名稱”核主成分分析“,是對PCA算法的非線性擴展,言外之意,PCA是線性的,其對於非線性數據往往顯得無能為力,例如,不同人之間的人臉圖像,肯定存在非線性關系,自己做的基於ORL數據

PCA 主成分分析詳解 寫給初學者 結合matlab轉載

整數 變量 行為 保持 sum osc 入參 函數 data 一、簡介 PCA(Principal Components Analysis)即主成分分析,是圖像處理中經常用到的降維方法,大家知道,我們在處理有關數字圖像處理方面的問題時,比如經常用的圖像的查詢

Java的LockSupport.park()實現分析轉載

兩個 這也 his access 需要 tracking orm return 指令 LockSupport類是Java6(JSR166-JUC)引入的一個類,提供了基本的線程同步原語。LockSupport實際上是調用了Unsafe類裏的函數,歸結到Unsafe裏,只有

轉載值得推薦的C/C++框架和庫 (真的強大)

libxml 滿足 ml2 代碼量 evo overview turn method 集合 原文地址 http://blog.csdn.net/xiaoxiaoyeyaya/article/details/42541419 值得學習的C語言開源項目 - 1. Webbe