動態規劃與回溯法解決0-1背包問題__動態規劃

來源:互聯網
上載者:User
問題描述:

0-1背包: 有N件物品和一個重量為M的背包。(每種物品均只有一件)第i件物品的重量是w[i],價值是p[i]。求解將哪些物品裝入背包可使價值總和最大。

動態規劃:

動態規划算法通常用於求解具有某種最優性質的問題。在這類問題中,可能會有許多可行解。每一個解都對應於一個值,我們希望找到具有最優值的解。動態規划算法與分治法類似,其基本思想也是將待求解問題分解成若干個子問題,先求解子問題,然後從這些子問題的解得到原問題的解。與分治法不同的是,適合於用動態規劃求解的問題,經分解得到子問題往往不是互相獨立的。若用分治法來解這類問題,則分解得到的子問題數目太多,有些子問題被重複計算了很多次。如果我們能夠儲存已解決的子問題的答案,而在需要時再找出已求得的答案,這樣就可以避免大量的重複計算,節省時間。我們可以用一個表來記錄所有已解的子問題的答案。不管該子問題以後是否被用到,只要它被計算過,就將其結果填入表中。這就是動態規劃法的基本思路。具體的動態規划算法多種多樣,但它們具有相同的填表格式。

動態規划算法可分解成從先到後的4個步驟:                                                    

1. 描述一個最優解的結構,尋找子問題,對問題進行劃分。

0-1背包問題具有最優子結構的性質。設(y1,y2,y3,……,yn)是所給的0-1背包問題的一個最優解,則是下面相應的子問題的一個最優解。

公式1-1

公式1-2

     公式1-1和公式1-2的n指的是出項的背包的容量n,m是背包的最大容納的品質,這可以看見,原問題的最優解出現在子問題的最優解之中,所以我們可以同過記錄的形式把子問題的最優解記錄現在,這真是我們動態規劃中最常看見的打表的思想,通過增減多一個矩陣的空間複雜度來提高程式的時間複雜度。

2. 定義狀態。往往將和子問題相關的各個變數的一組取值定義為一個狀態。某個狀態的值就是這個子問題的解。

假設我們輸入的值如下,背包的容量是N = 3,背包的容重 M = 10。我們有三組貨物可供選擇分別是P[1] = 4,P[2] = 5,P[3] = 6。W[1] = 3, W[2] = 4, W[3] = 5.

3. 找出狀態轉移方程。一般是從一個狀態到另一個狀態時變數值改變。

公式1-3

根據公式1-3我們很容易得到如下表格:

圖1-1

4. 以“自底向上”的方式計算最優解的值。

同過分析我打出了最優子結構的表格現在我們就可以調用這個表格通過自下而上的序列對最優子結構序列進行輸出。從最下邊的ZYJ[3][10]開始,判斷和ZYJ[2][10]的大小,顯然比ZYJ[2][10]說明是通過調用子結構加上p[i]得到的,現在我們給它減去w[i]。得到下一個位置ZYJ[2][5]和上面同理推出結果序列。

5. 從已計算的資訊中構建出最優解的路徑。

原始碼如下:寫得不好,見笑了

/**01背包:用動態規劃解決01背包,先繪表,按表輸出*/#include <stdio.h>#include "timex.h"#define MAX 32////////////函式宣告/////////////void printZYJ(int ZYJ[MAX][MAX],int row,int col);//輸出最優解表void printJG(int ZYJ[MAX][MAX],int p[MAX],int n,int m);//輸出結果表void createMatrix(int ZYJ[MAX][MAX],int w[MAX],int p[MAX],int JG[MAX],int row,int col);//打表記錄最優解int main(){//printMyTime();//自己名和時間的函數int m,n;int i;int a; //表示有a組貨物供選擇int p[MAX] = {0};//價格數組int w[MAX] = {0};//重量數組int ZYJ[MAX][MAX] = {0}; // zyj[i][j]表示裝入貨物為1<i<n時,品質為1<j<m是,最大的價zyj[i][j]//待最佳化int JG[MAX] = {0};//輸出結果序列printf("請輸入背包的容重和背包的容量\t");scanf("%d  %d",&m,&n);printf("背包最多容納的品質是:%d\n背包最大容納的數量是:%d\n",m,n);printf("輸入需要進行選擇的貨物個數\t");scanf("%d",&a);printf("輸入需要進行選擇貨物的品質價格序列\n");for(i = 1; i <= a; i++){printf("第%d貨物是:\t",i);scanf("%d%d",&w[i],&p[i]);}createMatrix(ZYJ,w,p,JG,a,m);printZYJ(ZYJ,a,m);printf("正確的輸出序列式:\n");printJG(ZYJ,JG,n,m);return 0;}void createMatrix(int ZYJ[MAX][MAX],int w[MAX],int p[MAX],int JG[MAX],int row,int col){int i, j;for(i = 1; i <= row; i++){for(j = 1; j <= col; j++){if(w[i]>j){ZYJ[i][j] = ZYJ[i-1][j];//品質不允許的情況下,還是上面的最優解}else{if(ZYJ[i-1][j] > ZYJ[i-1][j-w[i]]+p[i]){ZYJ[i][j] = ZYJ[i-1][j];}else{ZYJ[i][j] = ZYJ[i-1][j-w[i]]+p[i];}}}}j = col;for(i = row;i >= 1; i--){if(ZYJ[i][j] > ZYJ[i-1][j]){JG[i] = 1;j = j - w[i];}else{JG[i] = 0;}}}void printZYJ(int ZYJ[MAX][MAX],int row,int col){int i, j;for(i = 1; i <= row; i++){for(j = 1; j <= col; j++){printf("%3d",ZYJ[i][j]);}printf("\n");}}void printJG(int ZYJ[MAX][MAX],int p[MAX],int n,int m){int i;for(i = 1;i <= n; i++){printf("%3d",p[i]);}printf("\n最後的最大品質是:%d\n",ZYJ[n][m]);}

回溯法:

回溯法中,首先需要明確下面三個概念:

1.約束函數:約束函數是根據題意定出的。通過描述合法解的一般特徵用於去除不合法的解,從而避免繼續搜尋出這個不合法解的剩餘部分。因此,約束函數是對於任何狀態空間樹上的節點都有效、等價的。

2.態空間樹:狀態空間樹是一個對所有解的圖形描述。樹上的每個子節點的解都只有一個部分與父節點不同。

3.展節點、活結點、死結點:所謂擴充節點,就是當前正在求出它的子節點的節點,在DFS中,只允許有一個擴充節點。活結點就是通過與約束函數的對照,節點本身和其父節點均滿足約束函數要求的節點;死結點反之。由此很容易知道死結點是不必求出其子節點的(沒有意義)。

利用回溯法解題的具體步驟

首先,要通過讀題完成下面三個步驟:

(1)    描述解的形式,定義一個解空間,它包含問題的所有解。

假設我們的輸入和上面是一樣的背包的容量是N = 3,背包的容重 M = 10。我們有三組貨物可供選擇分別是P[1] = 4,P[2] = 5,P[3] = 6。W[1] = 3,W[2] = 4, W[3] = 5。假設最後的JG序列如下{x1,x2,……xn}則每種x[i]都有兩種可能{0,1},表示x是否進入背包,那麼這個0-1背包問題就可以形式化的描述成:

公式1-4

公式1-5

也就是說0-1背包問題實質上就是求這樣一個n維德向量{x1,x2,……xn},x屬於{0,1},1<=i<=n,使得的值最大,的同時滿足。

 

(2)構造狀態空間樹。

是向量{x1,x2,x3}的解空間樹左箭頭表示0右箭頭表示1,自然{x1,x2,……xn}其中包涵2n中子樹,所以只要搜尋這個些可能就可以找到最優解。為了降低空間複雜度,我們只記錄一次最優的子序列,這就要求我們對這個解空間樹進行兩次搜尋。第一次找到最大的價值,最後在根據最大價值找出結果序列。

(3)構造約束函數(用於殺死節點)。

遞迴實現殺死節點,每次判斷是不是超出背包的重量如果是的話直接截取。

原始碼如下:寫得不好,見笑了

/**程式說明:用回溯法解決01背包1)建立解空間樹找出 最優條件MAX((i = 1-n)p[i]JG[i]), 結束條件(i = 1-n)w[i]JG[i]<=m JG[i]={0,1};*/#include <stdio.h>#include "timex.h"#define MAX 32////////////函式宣告/////////////void printJG(int p[MAX],int n,int m);//輸出結果表void blJKJ(int count,int m,int n,int *price);//遍曆解空間樹void blJKJ_2(int count,int m,int n,int price);//第二次遍曆找出最優方案int isOver(int count,int m);//判斷是不是超出邊界int getJG(int count);//擷取一組可能的解的價格int p[MAX] = {0};//價格數組int w[MAX] = {0};//重量數組int JG[MAX] = {0};//輸出結果序列int main(){//printMyTime();//調用自己的函數,輸出時間,和姓名int m,n;int i;int a; //表示有a組貨物供選擇int price = 0; //表示最優價格printf("請輸入背包的容重和背包的容量\t");scanf("%d  %d",&m,&n);printf("背包最多容納的品質是:%d\n背包最大容納的數量是:%d\n",m,n);printf("輸入需要進行選擇的貨物個數\t");scanf("%d",&a);printf("輸入需要進行選擇貨物的品質價格序列\n");for(i = 1; i <= a; i++){printf("第%d貨物是:\t",i);scanf("%d%d",&w[i],&p[i]);}blJKJ(0,m,n,&price);printf("最優的價格是:\t");printf("%d\n",price);printf("正確的輸出序列式:\n");blJKJ_2(0,m,n,price);return 0;}int isOver(int count,int m){int i,j = 0;for(i = 1;i <= count; i++){j = j + JG[i]*w[i];}if(j > m)return 1;else return 0;}int getJG(int count){int i , j = 0;for( i = 1; i <= count; i++){j = j +JG[i]*p[i];}return j;}void blJKJ(int count,int m,int n,int *price){int j = 0,k;if(isOver(count,m))return ;if(count == n){j = getJG(count);if(j > *price)*price = j;return ;}for(k = 1; k >= 0; k--){JG[count+1] = k;blJKJ(count + 1,m,n,price);}}void blJKJ_2(int count,int m,int n,int price){int i,j = 0,k;if(isOver(count,m))return ;if(count == n){j = getJG(count);if(price == j){for(i = 1; i <= n; i++ ){printf("%3d",JG[i]);}printf("\n");return ;}return ;}for(k = 1; k >= 0; k--){JG[count+1] = k;blJKJ_2(count + 1,m,n,price);}}


 




原始碼如下:寫得不好,見笑了

聯繫我們

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