0-1揹包問題之使用回溯法解決

問題描述:

一位旅行者準備旅行,所以決定挑選一些物品放入揹包之中。每一件物品有一個體積和價值,而揹包的總體積也是固定的,問該旅行者應該怎樣挑選物品,使得總的價值爲最大值?注意物品不能分割,即只能要麼全部選中,要麼不選。


解決方案:

1、動態規劃(現在先不管,哪天有空了再說吧~~);

2、回溯法:

      使用回溯法最重要的是要確定約束函數和限界函數,只有這樣才能確定需要減去哪些枝節,否則解空間太大,根本無法解決。

      在此問題中,首先我們可以看出,該問題的約束函數爲:

      如果當前揹包中的物品的總容量是cw,前面的k-1件物品都已經決定好是否要放入包中,那麼第k件物品是否放入包中取決於不等式

                               cw + wk <= M (其中,wk爲第k件物品的容量,M爲揹包的容量)(此即約束條件)

       然後我們再尋找限界函數,這個問題比較麻煩,我們可以回憶一下揹包問題的貪心算法,即物品按照    物品的價值/物品的體積 來從大到小排列,然後最優解爲
      (1,1,1.......,1,t,0,0,......),其中0<=t<=1;

       因此,我們在確定第k個物品到底要不要放入的時候(在前k-1個物品已經確定的情況下),我們可以考慮我們能夠達到的最大的價值,即我們可以通過計算只放入一部分的k物品來計算最大的價值。我們要確保當前選擇的路徑的最大的價值要大於我們已經選擇的路徑的價值。這就是該問題的限界條件。通過該條件,可以減去很多的枝條,大大節省運行時間。


代碼:

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>


int * pValue = NULL; //物品的價值
int * pWeight = NULL;//物品的重量
int counts = 0;		 //物品的個數
int *realSolution = NULL; //存放最終的解
int *testSolution = NULL; //存放每次走得路徑所得的解

/*計算在前k-1件物品已經做出決策的前提下,考慮可能達到的最大的效益值,返回一個上界,
  其中,cp代表揹包中當前的價值,cw代表當前的重量,k代表是考慮第幾個物品,m代表揹包的最大容量
*/
float BoundFound(int cp, int cw, int k, int m)
{
	int i = k;
	int c = cw;
	float b = (float)cp;
	while (i<=counts)
	{
		c += pWeight[i];
		if (c<m)
		{
			b += pValue[i];
		}
		else
		{
			return (b+(1-(float)(c-m)/pWeight[i])*(pValue[i]));
		}
		i++;
	}
	return b;
}

//m爲揹包的容量
void BackKnap(int m)
{
	int currentWeight = 0;
	int currentValue = 0;
	int k = 1;
	int finalValue = -1;
	while(true)
	{
		while(k<=counts && (currentWeight + pWeight[k] <= m))
		{
			testSolution[k] = 1;
			currentWeight += pWeight[k];
			currentValue += pValue[k];
			k++;
		}
		if (k>counts)
		{
			finalValue = currentValue;
			k = counts;
			memmove((void *)realSolution, (void *)testSolution, (counts+1) * sizeof(int));
		}
		else
		{
			testSolution[k] = 0;
		}
		//如果發現這樣的一條路徑走得最好結果也沒有我現在的結果好,則果斷要求回溯
		while (BoundFound(currentValue, currentWeight, k+1, m) <= finalValue) 
		{
			while((testSolution[k] != 1) && (k != 0)) k--;
			if (k==0)
			{
				return ;
			}
			testSolution[k] = 0;
			currentWeight -= pWeight[k];
			currentValue -= pValue[k];
		}
		k++;
	}
}

void main()
{
	printf("please input the counts of packages:");
	scanf("%d", &counts);
	printf("please input the volumn of the bag:");
	int m = 0;
	scanf("%d", &m);
	pWeight = (int *)malloc((counts+1) * sizeof(int));
	memset((void*)pWeight, 0, sizeof(int)* (counts+1));
	for(int i = 1; i <= counts; i++)
	{
		printf("please input the weight of the %dth package:", i);
		scanf("%d", pWeight + i);
	}
	pValue = (int *)malloc((counts+1) * sizeof(int));
	memset((void*)pValue, 0, sizeof(int) * (counts+1));
	for(i = 1; i <= counts; i++)
	{
		printf("please input the value of the %dth package:", i);
		scanf("%d", pValue + i);
	}
	realSolution = (int *)malloc((counts+1) * sizeof(int));
	memset((void*)realSolution, 0, sizeof(int) * (counts+1));
	testSolution = (int *)malloc((counts+1) * sizeof(int));
	memset((void*)testSolution, 0, sizeof(int) * (counts+1));
	BackKnap(m);
	printf("the best reslut is choosing ");
	int maximunValue = 0;
	for (i = 1; i<=counts; i++)
	{
		if (realSolution[i] == 1)
		{
			maximunValue += pValue[i];
			printf("the %dth package  ", i);
		}
	}
	printf("\n and the maximun value is %d\n",maximunValue);
}
注意上述代碼輸入物品的體積和價值的時候,輸入的順序要按照 物品的價值/物品的體積 從大到小排列。

編譯環境VC++6.0

出現的主要問題:

1、第64行的while循環開始的時候寫成if了,這樣的話就不能發揮限界函數的作用了!

2、可以通過一步一步的跟蹤調試來熟悉整個揹包問題的解空間所構成的樹!

3、還是得熟悉回溯法的基本思路啊!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章