1. 程式人生 > >揹包問題-01揹包,完全揹包,多重揹包

揹包問題-01揹包,完全揹包,多重揹包

  • 揹包問題-01揹包,完全揹包,多重揹包


  • 01揹包:

概念:

有Goods_Num件物品,MAX_Volume的最大裝載量,每種物品只有一件,每種物品都有對應的重量或者說體積Volume[i],價值Value[i],求解裝包的最大價值

狀態轉移方程推導:

假設目前已經有 i-1件物品裝在容量為 j 的揹包中,並且得到最大價值Package[ i-1 ][ j ],當前裝第i件,那麼討論分兩個角度,如果第i個物品裝不入揹包,那麼最大價值不變。如果第i個物品可以放入,那麼如果揹包容量為 j-Volume[i]的時候的最大裝包價值加上第i個物品價值大於Package[i-1][j],那麼Package[i][j]最大價值就更新了,否則就不裝i

得到狀態轉移方程:

a. Volume[ i ] > j :Package[ i-1 ][ j ]

b. j >= Volume[ i ] :max( Package[ i-1 ][ j-Volume[i] ] +Value[ i ], Package[ i-1 ][ j ] )

實現程式碼:

#include<iostream>
#include<algorithm>
#include<memory>
using namespace std;
#define MAX_SIZE 1024
int Volume[MAX_SIZE];    /*每個物品所佔空間(或重量)*/
int Value[MAX_SIZE];     /*物品價值*/
int Package[MAX_SIZE][MAX_SIZE];   /*未優化,Package[i][j] 表示前i個物品放入容量為j的購物車的最大價值*/
int MAX_Volume, Goods_Num;  /*最大容量,物品數量*/

void Init()
{
	memset(Package, 0, sizeof(Package));
}
void _01Package()
{
	for (int i = 1; i <= Goods_Num; i++)
	{
		for (int j = 1; j <= MAX_Volume; j++)
		{
			if (j < Volume[i])     /*裝不下第i個物品*/
				Package[i][j] = Package[i - 1][j];
			else
				Package[i][j] = max(Package[i - 1][j], Package[i - 1][j - Volume[i]] + Value[i]);
				/*將第i個物品放在容量為j - Volume[i](已得的最優情況)的購物車裡*/
		}
	}
}
void Print_Res()  /*列印結果*/
{
	cout << "The max value of Package:" << Package[Goods_Num][MAX_Volume] << endl;
	
	int i, j = MAX_Volume;
	cout << "Goods in Package: ";
	for (int i = Goods_Num; i > 0; i--)
	{
		if (Package[i][j] > Package[i - 1][j])
		{
			cout << i<<" ";
			j -= Volume[i];
		}
	}
	cout << endl;
}

優化:

在設計演算法時會發現,上一層的資料在本層中並沒有修改,而是作為狀態的記錄,而且本層當前Index只會用到上一層Index之前的資料,所以可以降成一維,稱為滾動陣列。並且要求每次都重後往前遍歷,避免重複疊包

優化程式碼:

#include<iostream>
#include<algorithm>
#include<memory>
using namespace std;
#define MAX_SIZE 1024
int Volume[MAX_SIZE];    /*每個物品所佔空間(或重量)*/
int Value[MAX_SIZE];     /*物品價值*/
int _Package[MAX_SIZE];   /*優化後,Package[i]表示容量i的購物車所獲得的最大價值*/
int MAX_Volume, Goods_Num;  /*最大容量,物品數量*/

void Init()
{
	memset(_Package, 0, sizeof(_Package));
}

void _01Package_Optimize()  /*優化演算法*/
{
	for (int i = 1; i <= Goods_Num; i++)
		for (int j = MAX_Volume; j >= Volume[i]; j--) /*每個物品最多隻能裝一次,從後開始遍歷防止最優情況疊加*/
			_Package[j] = max(_Package[j], _Package[j - Volume[i]] + Value[i]);
}

void Print_Res_Optimize()
{
	cout << "The max value of Package:" << _Package[MAX_Volume] << endl;
}

  • 完全揹包:

概念:

有Goods_Num件物品,MAX_Volume的最大裝載量,每種物品數量無限,每種物品都有對應的重量或者說體積Volume[i],價值Value[i],求解裝包的最大價值

狀態轉移方程推導:

與01揹包的思路基本相似,不過重要的一點是,每種物品是無限的,所以揹包可以多填

得狀態轉移方程:

a. Volume[ i ] > j Package[i-1][ j ]

b. j>=Volume[ i ] :max( Package[ i-1 ][ j-m*Volume[ i ] ] +m*Value[ i ], Package[ i-1 ][ j ] )

實現程式碼:

#include<iostream>
#include<algorithm>
#include<memory>
using namespace std;
#define MAX_SIZE 1024
int Volume[MAX_SIZE];    /*每個物品所佔空間(或重量)*/
int Value[MAX_SIZE];     /*物品價值*/
int Package[MAX_SIZE][MAX_SIZE];   /*未優化,Package[i][j] 表示前i個物品放入容量為j的購物車的最大價值*/
int MAX_Volume, Goods_Num;  /*最大容量,物品數量*/

void Init()
{
	memset(Package, 0, sizeof(Package));
}

void Total_Package()
{
	for (int i = 1; i <= Goods_Num; i++)
	{
		for (int j = MAX_Volume; j >= Volume[i]; j--)  /*重後往前避免單物品多填影響後序的填包,後填不影響前*/
		{
			int k = j / Volume[i];   /*最多再填k個i物品*/
			for (int m = 0; m <= k; m++)
				Package[i][j] = max(Package[i - 1][j - m * Volume[i]] + m * Value[i], Package[i - 1][j]);
		}
	}
}

優化程式碼:

#include<iostream>
#include<algorithm>
#include<memory>
using namespace std;
#define MAX_SIZE 1024
int Volume[MAX_SIZE];    /*每個物品所佔空間(或重量)*/
int Value[MAX_SIZE];     /*物品價值*/
int _Package[MAX_SIZE];   /*優化後,Package[i]表示容量i的購物車所獲得的最大價值*/
int MAX_Volume, Goods_Num;  /*最大容量,物品數量*/

void Init()
{
	memset(_Package, 0, sizeof(_Package));
}

void Total_Package_Optimize()  /*優化演算法*/
{
	for (int i = 1; i <= Goods_Num; i++)
	{
		for (int j = Volume[i]; j <= MAX_Volume; j++)/*一維,從前往後dp才能實現一個物品的多填*/
			_Package[j] = max(_Package[j], _Package[j - Volume[i]] + Value[i]);
	}
}

void Print_Res_Optimize()/*列印*/
{
	cout << "The max value of Package:" << _Package[MAX_Volume] << endl;
}


  • 多重揹包:

概念:

有Goods_Num件物品,MAX_Volume的最大裝載量,第i件物品有數量為Num[i],每種物品都有對應的重量或者說體積Volume[i],價值Value[i],求解裝包的最大價值

狀態轉移方程推導:

其實就是在一個展開的01揹包問題,比如有3件相同物品,不如將它展開成 1 1 1 形式,然後01裝包

狀態轉移方程:

a. Volume[ i ] > j :Package[ i-1 ][ j ]

b. j >= Volume[i] :max( Package[ i-1 ][ j-Volume[ i ] ] +Value[i], Package[ i-1 ][ j ] )

只不過,對於物品數大於1的,需要展開Num[i]次

實現程式碼:

#include<iostream>
#include<algorithm>
#include<memory>
using namespace std;
#define MAX_SIZE 1024
int Volume[MAX_SIZE];    /*每個物品所佔空間(或重量)*/
int Value[MAX_SIZE];     /*物品價值*/
int Num[MAX_SIZE];	/*i物品的個數*/
int Package[MAX_SIZE];   /*優化後,Package[i]表示容量i的購物車所獲得的最大價值*/
int MAX_Volume, Goods_Num;  /*最大容量,物品數量*/

void Init()
{
	memset(Package, 0, sizeof(Package));
}

void Multi_Package()
{
	for (int i = 1; i <= Goods_Num; i++)
	{
		for (int j = 1; j <= Num[i]; j++)
		{
			for (int k = MAX_Volume; k >= Volume[i]; k--)
				Package[k] = max(Package[k], Package[k - Volume[i]] + Value[i]);
		}
	}
}

void Print_Res()
{
	cout << "The max value of Package:" << Package[MAX_Volume] << endl;
}

優化:

這時候想一個問題,如果一件物品的數量很大很大,那麼,對於第三個迴圈需要跑的時間就很長,在前面說過,多重揹包是可拆成01揹包形式,那麼,何不提前處理 Volume[MAX_SIZE],Value[MAX_SIZE],將問題先轉成01揹包。

轉化時有技巧,運用快速冪的知識,將一件物品拆成 1,2,4.......,當然有個容易想的前提:1~N的數可由2的若干指數和表示

優化程式碼:

#include<iostream>
#include<algorithm>
#include<memory.h>
using namespace std;
#define MAX_SIZE 1024
int Op_Vol[MAX_SIZE];   /*優化後的物品體積*/
int Op_Val[MAX_SIZE];   /*優化後的物品價值*/
int Package[MAX_SIZE];   /*揹包*/
int MAX_Volume,GoodsNum;  /*揹包最大體積,原先的貨品數量*/
int Op_GoodsNum;   /*優化後的物品數*/
 
void Init()  /*初始化*/
{
    Op_GoodsNum=0;
    memset(Op_Val,0,sizeof(Op_Val));
    memset(Op_Vol,0,sizeof(Op_Vol));
    memset(Package,0,sizeof(Package));
}

void Multi_Package()
{
    for(int i=1;i<=Op_GoodsNum;i++)   /*優化後轉變為01揹包問題*/
    {
        for(int j=MAX_Volume;j>=Op_Vol[j];j--)
            Package[j]=max(Package[j],Package[j-Op_Vol[i]]+Op_Val[i]);
    }
}

int main()
{
    int vol,val,num;
    while(cin>>GoodsNum>>MAX_Volume)
    {
        Init();
        for(int i=1;i<=GoodsNum;i++)
        {
            cin>>vol>>val>>num;
            for(int j=1;j<=num;j<<=1)   /*優化成01揹包問題*/
            {
                Op_Val[++Op_GoodsNum]=j*val;
                Op_Vol[Op_GoodsNum]=j*vol;
                num-=j;
            }
            if(num>0)
            {
                Op_Val[++Op_GoodsNum]=num*val;
                Op_Vol[++Op_GoodsNum]=num*vol;
            }
        }
        Multi_Package();
        cout<<Package[MAX_Volume]<<endl;
    }
    return 0;
}