揹包問題 (knapsack problem)

0/1揹包問題:python

某種限制下的最優問題。總體最優能夠由部分最優獲得。算法

因爲子問題相交,能夠用動態規劃方法求解。即,利用空間記錄中間計算結果,後續的計算經過簡單的判斷和查表獲得。數組

中間結果的存儲主要是key-value鍵值對。對於內建dictionary支持的python或者有STL庫的C++,解這種問題比較方便,代碼也少。若是以上二者都沒有,好比C,就麻煩點。若是key--(x, y, z, ...)中的x,y,z等都是整數,那麼能夠利用n維數組來存儲中間結果,其index就是key(好比如下代碼對0/1揹包問題的實現)。dom

固然,若是總重量比較大,實際上用數組存儲是不對的,其效率可能遠低於brute force算法。利用dictionary(或者map)來存儲,在0/1揹包問題上,在全部狀況下,都比brute force好(至少不會更差)。spa

/**
 * knapsack.c -- 0/1 knapsack problem
 *
 **/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <time.h>

static int N = 0;
static int W = 0;
static int *p = NULL;
static int *w = NULL;
static int **T = NULL;
static int **record = NULL;

static void knapsack(void)
{
	int i=0;
	int weight=0;

	for(i=0; i<N; i++)
	{
		for (weight=1; weight<=W; weight++)
		{
			if (i == 0)
			{
				if (w[i] <= weight)
				{
					T[i][weight] = p[i];
					record[i][weight] = 1;
				}
				else
				{
					T[i][weight] = 0;
					record[i][weight] = 0;
				}
				continue;
			}

			if (w[i] <= weight)
			{
				int v1 = T[i-1][weight-w[i]]+p[i];
				int v2 = T[i-1][weight];
				if (v1 >= v2)
				{
					record[i][weight] = 1;
					T[i][weight] = v1;
				}
				else
				{
					record[i][weight] = 0;
					T[i][weight] = v2;
				}
			}
			else
			{
				T[i][weight] = T[i-1][weight];
				record[i][weight] = 0;
			}
		}
	}
}

static void backtrace(void)
{
	int i=N-1; 
	int weight=W;
	printf("selected: ");
	while(i >= 0 && weight >= 0)
	{
		if (record[i][weight] == 1)
		{
			printf("%4d", i);
			weight = weight - w[i];
			i--;
		}
		else
		{
			i--;
		}
	}
	printf("\n");
}

static void init(void)
{
	int i = 0;
	assert(N > 0);
	assert(W > 0);
	p = (int *)malloc(sizeof(int) * N);
	assert(p != NULL);
	w = (int *)malloc(sizeof(int) * N);
	assert(w != NULL);
	for (i=0; i<N; i++)
	{
		p[i] = random()%10+1; /* 1~10 */
		w[i] = random()%W + 1; /* 1~W */
	}

	T = (int **)malloc(sizeof(int*) * N);
	assert(T != NULL);
	for (i=0; i<N; i++)
	{
		T[i] = (int*)malloc(sizeof(int) * (W+1));
		assert(T[i] != NULL);
	}

	record = (int **)malloc(sizeof(int*) * N);
	assert(record != NULL);
	for (i=0; i<N; i++)
	{
		record[i] = (int*)malloc(sizeof(int) * (W+1));
		assert(record[i] != NULL);
	}

	for(i=0; i<N; i++)
		T[i][0] = 0;	/* no more available weight */

}

static void clean(void)
{
	int i = 0;
	assert(p != NULL);
	assert(w != NULL);
	assert(record != NULL);
	assert(T != NULL);
	for (i=0; i<N; i++)
		free(record[i]);
	free(record);

	for (i=0; i<N; i++)
		free(T[i]);
	free(T);

	free(p);
	free(w);
	p = NULL;
	w = NULL;
	T = NULL;
	record = NULL;
	N = 0;
	W = 0;
}

static void print_p(void)
{
	int i;
	printf("p: ");
	for (i=0; i<N; i++)
		printf("%4d", p[i]);
	printf("\n");
}

static void print_w(void)
{
	int i;
	printf("w: ");
	for(i=0; i<N; i++)
		printf("%4d", w[i]);
	printf("\n");
}

static void test(int n, int w)
{
	N = n;
	W = w;
	init();

	printf("N = %d \t W = %d \n", N, W);
	print_p();
	print_w();

	knapsack();
	printf("MaxTotal = %d \n", T[N-1][W]);
	backtrace();

	clean();
}

int main(int argc, char *argv[])
{
	int i, ret;
	srand((unsigned int)time(NULL));
	printf("================== Test 1 =======================\n");
	test(3, 20);

	printf("================== Test 2 =======================\n");
	test(10, 20);

	printf("================== Test 3 =======================\n");
	test(10, 100);
	return 0;
}

 

以前有道題是2n個整數,分紅n-n兩個集合A和B,求sum(A) - sum(B)的絕對值最小的分法。code

用map(或者dictionary)來存吃key-value,能夠很快解決這道題,如論總的sum多大。遞歸

定義m(i, k, diff) 爲最多到index爲i,還剩k個元素須要歸入(好比n=10,而後已經選取了4個,那麼k=6),最接近但小於等於diff的值。原始call爲m(2n-1, n, totalsum/2).string

因而就有key-value pair爲(i,k,diff) -- m. 而後用相似上面的方法求解。這種狀況下,應該要用遞歸倒着寫。it

相關文章
相關標籤/搜索