回溯法解n纸币凑数问题

前言:我们都知道动态规划算法的一个经典案例是0-1背包问题。其中这个问题求的是最优解(即最大解)。然后现在有这么一种情况:设有n张面值不一定相等的纸币,其面值为Vi(0<=i<=n-1),随意输入一个值val,(0<=val<=ΣVi),是否可以通过选择m张纸币使得它们的面值之和为val.即存在解Xi序列(X0,X1,X2,...,Xn)不为0序列。

举个栗子:

现在有6张纸币。其面值Vi序列为(1,1,5,10,20,50).要输入一个val,其中val满足不等式0<=val<=87.(87为这6张纸币面值之和)。设Xi序列为问题的解,其中Xi的取值代表是否选择该纸币,比如,X0=1表示选择第1个纸币,其面值V0=1;X4=1表示选择第5个纸币,其面值V4=为20。若val=6,很明显有两个可行解Xi序列为(1,0,1,0,0,0)、(0,1,1,0,0,0).

现在的这个问题似乎是变形的0-1背包问题,但是似乎求的不是最优解而是所有的可行解,所以这里用解决8皇后问题的经典算法回溯法。

大致思想如下:

假设这么有一个函数Select(i,num),其中i代表当前要进行选择判断的纸币序号。num为当前已选择纸币的总面值。和8皇后问题思路一样,在选择i的时候,其前面i次选择都满足了num<=val这个条件。所以进入这个函数的第一件事就是判断num==val?如果等同,则直接输入Xi序列.如果不等于,就判断一下这个i是否已经超过了最后一张纸币的序号,如果是,什么都不做直接结束。如果i仍然是指向某一张纸币,那么就进行分支:第一步是:如果选择i纸币后,其num仍满足<=val,则进行下一个选择Select(i+1,num)。第二步是恢复原来状态,不选择i纸币,然后进行下一个选择Select(i+1,num)。第二步的意义在于让在作出第一步选择之后,无论它成功与否,它都能回到初态,向不选择i的方向进行。很容易知道这个求解过程是一棵满2叉树,它的解共有2^(n-1)个。

代码:

/*
问题的2叉树表示图(部分):
                              X0
							  |
					   ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
					   |              |
					 0 |              | 1
					   |              | 
					  X1              X1
					  |               |
			      ˉˉˉˉˉˉˉˉˉ       ˉˉˉˉˉˉˉˉˉ
				  |       |       |       |
				0 |       | 1   0 |       | 1
				  |       |       |       |
				 X2       X2     X2       X2
				 |  
				。。。
 
*/
#include
using namespace std;
void Select(int i,int num,int *v,const int n,const int val,int *x){
	if (val == num){
		cout << "x可行序列为 : (";
		for (int i = 0; i < n; i++){
			cout << x[i];
			if (i != n - 1)
				cout << ",";
			else
				cout << ")" << endl;
		}
		return;
	}
	if (i >= n)
		return;
	if ((num + v[i]) <= val){
		x[i] = 1;
		num += v[i];
		Select(i + 1, num, v, n, val, x);
	}
	x[i] = 0;
	num -= v[i];
	Select(i + 1, num, v, n, val, x);
}
int main(){
	int v[6] = { 1, 1, 5, 10, 20, 50 }, x[6] = { 0 };
	int num = 0, i = 0, n = 6;
	int val;
	cout << "输入想要的凑数结果 : val = ";
	cin >> val;
	cout << endl;
	if (val >= 0 && val <= 87)
		Select(i, num, v, n, val, x);
	else
		cout << "\n输入的val不正确." << endl;
	return 0;
}

运行结果:

回溯法解n纸币凑数问题_第1张图片


你可能感兴趣的:(c++笔记)