1. 程式人生 > >[C/C++] 回溯法解0-1揹包問題

[C/C++] 回溯法解0-1揹包問題

用最小的空間裝最大價值的物品是經典的揹包問題,而0-1揹包是揹包問題中最簡單的情況,常見的做法有動態規劃和回溯法等。

本文用更為容易理解的回溯法來解決該問題。

我們把每個輸入的物品都看做一個節點,用標記陣列來標記是否使用該物品,於是物品節點之間能夠生成一顆二叉樹,二叉樹節點的左子(1)表示裝該物品,右子(0)表示不裝該物品。

於是我們可以從二叉樹的根節點用深度優先搜尋的方法來遍歷直到葉子節點,我們對每條路徑可以得到一個有效序列來標記是否在揹包中裝這n個物品(假設n=8,則例如:01100010)可以給出裝下3個物品的揹包情況。

當然,每次確定裝或不裝某物品時,我們都要計算當前揹包總重,在DFS的同時,如果發現當前揹包裝的總物品重量過大,則可以直接剪枝,從而大幅度提高程式效率。

注意:通常不只有一種情況可以產生最大價值。

程式如下:

#include<iostream>
using namespace std;
int weight[1001],value[1001];//重量和價值
int use[1001];//標記n個物品是否使用
int c; //揹包容量
int max_value=0;//最大價值
int b_count; //記錄最大價值的數量
void find_max_value(int n,int max){
	int w=0;
	for(int i=0;i<n;i++) w+=weight[i]*use[i]; //算總重
	if(w>c) return; //如果超重,回溯
	else if(n==max){
		int v=0;
		for(int i=0;i<n;i++) v+=use[i]*value[i]; //算總價值
		if(v>max_value) max_value=v;
		return ;//找到滿足的一個,回溯
	}
	else{ //不滿足,則生產2個子節點(0或1)
		use[n]=0;
		find_max_value(n+1,max);//不加入當前節點
		use[n]=1;
		find_max_value(n+1,max); //加入當前節點
	}
}
void find_use(int n,int max){
	int w=0;
	for(int i=0;i<n;i++) w+=weight[i]*use[i]; //算總重
	if(w>c) return; //如果超重,回溯
	else if(n==max){
		int v=0;
		for(int i=0;i<n;i++) v+=use[i]*value[i]; //算總價值
		if(v==max_value){ //當前是最大值的一種情況
			cout<<"case "<<b_count<<":"<<endl;
			b_count++;
			for(int i=0;i<n;i++){
				if(use[i]==1) cout<<(i+1)<<endl;
			}
		}
		return ;//找到滿足的一個,回溯
	}
	else{ //不滿足,則生產2個子節點(0或1)
		use[n]=1; //先1後0則先輸出大的滿足的(如5),先0後1則先輸出小的(如1 4)
		find_use(n+1,max);//不加入當前節點
		use[n]=0;
		find_use(n+1,max); //加入當前節點
	}
}
int main()
{
	int n;
	char t;
	cin>>n;
	for(int i=0;i<n;i++) cin>>weight[i]>>t>>value[i];
	cin>>c;
	find_max_value(0,n);
	//cout<<"max value:"<<max_value<<endl;
	b_count=1;
	find_use(0,n);	
	system("pause");
	return 0;
}

程式進行了兩遍搜尋,第一遍找出了當前揹包的最大價值max_value。第二遍則找出可以符合最大價值max_value的所有物品組合序列。

回溯法解0-1揹包問題相對於DP來說,可以更簡單理解和入門