算法基礎四:動態規划---0-1背包問題


算法基礎四:動態規划---0-1背包問題

一、算法描述與分析

1、問題的理解與描述

問題理解

image-20211005122142263

問題描述

image-20211005122212399

2、解題思路

image-20211005140844320

①思路

image-20211005141845274

②狀態轉移方程

f(k,w):當背包容量為w,現有k件物品可以偷所能偷到的最大價值。

image-20211005142218077

③表格(圖示)

image-20211005142328958

解釋:

  • 第一行和第一列為0,因為當背包容量為0的時候,不論還有幾件物品可以偷,那么價值都是0,偷不到。如果剩下0件物品可以偷,那么價值也是0。相當於初始化。
  • 比如f(1,2),意思就是背包容量還剩下2,現在還有一件物品可以偷,這個物品的重量為2,價值為3。或者帶入轉移方程也可以看出來,偷與不偷的情況。

二、算法的偽代碼描述

1.算法偽代碼

KNAPSACK(v, w, C)
	n <- length[v]
	for j <- 0 to C
		do f[0,j] <-0 #j控制容量
	for i<-1 to n
		do f[i,0] <-0
	for j<-1 to C
		do f[i,j] <- f[i-1,j]
			if wi <= j
				then if vi + f[i-1,j-wi] > f[i-1,j
					then f[i,j] <- vi + f[i-1,j-wi
	return  f

2、構造一個最優解

利用KNAPSACK返回的矩陣m,可以構造出如下最優解:

BUILD-SOLUTION(f,w,C)
	n <- length[w]
	j <- C
	for i<- n to 1
		do if f[i,j] = f[i-1,j]
				then x[i] <- 0
			else 
				x[i] <- 1
				j <- j-w[i]#如果拿了的話,容量減少
	return x

三、代碼實現

1、算法代碼

public class Knapsack {
    public static int[][] knapsack(int[] w,int[] v,int c){
        int i,j,n = w.length;
        int [][] f = new int[n+1][c+1];
        for (i=1;i<n+1;i++)
            f[i][0] = 0;
        for (j=0;j<c+1;j++)
            f[0][j] = 0;
        for (i=1;i<=n;i++)
            for (j=1;j<=c;j++){
                f[i][j] = f[i-1][j];//默認其太重放不下。或者是不拿當前這個物品的情況。
                if (w[i-1]<=j)//此種情況就是如果當前物品能放下
                    if (v[i-1]+f[i-1][j-w[i-1]]>f[i-1][j])//並且拿了這個物品的總價值要大於不拿這個物品
                        f[i][j] = v[i-1]+f[i-1][j-w[i-1]];
            }
        return f;
    }

    public static int[] buildSolution(int[][] f,int[] w,int c){
        int i,j=c,n=w.length;
        int[] x = new int[n];
        for (i=n;i>=1;i--)
            if (f[i][j] == f[i-1][j])//說明當前這個物品,可能是沒拿,也有可能是放不下。
                x[i-1] = 0;//那么就說明這個物品沒拿
            else {//否則就說拿了,需要減去這個重量,重新的去尋找下一個拿了的物品。
                x[i-1] = 1;
                j -= w[i-1];
            }

        return x;
    }

}

2、測試代碼

public class Test {
    public static void main(String[] args) {
        int w[] = {2,3,4,5},v[]={3,4,5,7};
        int[][] f;
        int[] x;
        f = Knapsack.knapsack(w,v,9);
        x = Knapsack.buildSolution(f,w,9);
        for (int i = 0; i < 4; i++) {
            System.out.print(x[i]+" ");
        }
        System.out.println();
    }
}

四、思考求解動態規划

1、組成部分一:確定狀態

①最后一步:

​ 在本題中,最后一步,也就是從最后一個物品出發,最后一個物品,拿還是不拿。不拿的價值是多少,拿了的價值是多少。

②子問題:

​ 前一個物品拿完之后,當前這個物品拿不拿,拿了之后價值和沒拿之后的價值。

2、組成部分二:轉移方程

​ 列出轉移方程

3、初始條件和邊界情況

  • 數組要開多大?
  • 如果重量為0,值該怎么辦。
  • 如果沒有物品可以拿,值該怎么辦。
  • 循環要多少次

4、計算順序

  • 由於我們是從左到右的,一直到右下角,在計算當前的時候,會參考二維數組的左面,左斜上方的格子里 的值,自然我們的計算順序是從左到右,從上到下。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM