背包问题 (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

相关文章
相关标签/搜索