算法之回溯法

回溯法

求解步骤
1,定义给定问题的解向量解空间【子集树/排列树】
2,设计剪支函数【限界函数及约束函数】
3,深度优先遍历结合剪支得出解
求解过程
1,是否为完全解,是则输出;
2,是否为部分解,是则进行下一个解分量的判断;
3,是否为当前可选集合中的最后一个元素,是则回溯,重新判断上一个节点,否则判断可选集合中的下一个元素
注:若上一个节点选择的是上一个可选集合中的最后一个元素,则再次回溯。

练习一:求解部分和问题
题目内容:
给出N个正整数组成的数组A,求能否从中选出若干个,使他们的和为K。如果可以,输出:“YES”,否则输出"NO"。

输入格式:
第1行:2个数N、K, N为数组的长度, K为需要判断的和(2 ≤N ≤ 20,1 ≤ K ≤ 10^9)
第2 到第 N + 1行:每行1个数,对应数组的元素A[i] (1 ≤ A[i]≤ 10^6)

输出格式:
如果可以,输出:“YES”,否则输出"NO"。
样例输入
4 13
1
2
4
7
样例输出
YES

算法描述【非递归】
输入:需要判断的和k,数组长度n,数组元素
输出:判断结果YES/NO
1,初始化状态向量result[i]=-1;[-1未初始状态,0表示选入,1表示不选入,2表示已无可选情况]
2,num=0;
3,while(num>=0)
3.1result[num]++,判断该元素下一状态是否可行;
3.2若该状态可行,则转步骤3.3;若发生冲突,则result[num]++试探下一状态;
3.3若判断函数返回成功标志,输出YES,结束算法;
3.4若num<n-1且处于可选状态内,num++,判断下一元素的状态;
3.5若result[num]>=2,即已无可选状态时,重置num的状态,num–,回溯至其根结点判断其状态。
4,判断所有状态皆无成功结果,输出NO,算法结束。

c++实现

#include<iostream>
using namespace std;
int n, k;
int number[20]; 
int *result=new int[n];
int check(int num)
{
	int sum = 0;
	for (int i = 0; i < n; i++)
	{
		if (result[i] == 0)
			sum = sum + number[i];
	}
	if (sum < k) {
		return 1;
	}
	if (sum == k)return 2;
	else return 0;
}
int main(){
	cin >> n >> k;
	for (int a = 0; a < n; a++)
	{
		cin >> number[a];
		result[a] = -1;//表示第a个元素的状态1表未采用,0表采用,-1为初始状态
	}
	int num = 0;
	while (num >= 0) {
		result[num] ++;//判断下一个可选状态
		int temp = check(num);
		if(result[num] < 2 && temp == 0)
		{
			result[num]++;//不可选
		}
		if (temp==2)
		{
			cout << "YES"; return 0;
		}
		if (num < n-1&&result[num]<2)num++;//进行下一阶段的判断
		if(result[num]>=2) 
			result[num--] = -1;//重置num状态,回溯到上一节点
	}
	cout << "NO";//无可选搭配
	return 0;
}

注意
1,需有输出结果,判断是否重置回溯的时候需用if判断是否执行,而非直接else【漏掉对NO情况的判断】;
2,发生冲突时,result[num]++一次即可,避免遗漏对于元素处于不加入状态的情况;
3,直接在while循环结束后进行失败标志的输出即可。
4,对于和的判断,每次判断时在函数中根据标志状态计算,避免回溯时对sum的计算复杂。

练习2求解最小机器重量设计问题

题目内容:
设某一机器由n个部件组成,部件编号为1-n,每一种部件都可以从m个不同的供应商处购得,供应商编号为1~m。设wij是从供应商j处购得的部件i的重量,cij是相应的价格。对于给定的机器部件重量和机器部件价格,计算总价格不超过d的最小重量机器设计。(注意:输出结果中第一行最后没有空格。比如下面的输出样例中1 3 1后面没有空格。)

输入格式:
第1行输入3个正整数n,m和d。接下来n行输入wij(每行m个整数),最后n行输入cij(每行m个整数),这里1≤n、m≤100。

输出格式:
输出的第1行包括n个整数,表示每个对应的供应商编号,第2行为对应的最小重量。

输入样例:
3 3 7
1 2 3
3 2 1
2 3 2
1 2 3
5 4 2
2 1 2

输出样例:
1 3 1
4

算法描述
矩阵weight[i][j]存储供应商j部件i的重量,value[i][j]存储价格,result[i]存储部件i所选供应商,r[i]为最终结果
输入:部件数n,供应商m,最大价格d,各供应商所提供不同部件价格及重量
输出:采用的各部件来源,总重量
1,初始化j=-1;
2,k=0;
3,while(k>=0)
3.1result[k]++;
3.2若仍有可选供应商且无法满足约束条件【总价格不大于d】,则对下一供应商的判断;
3.3若result[k]<m且k==n-1即已有搜寻结果且已搜寻至最后一个部件,则计算其重量sum,与temp比较大小,若较小,则更新temp的值,并将所选供应商存入最终结果r[i]中;否则,舍弃,该选择定非最优解;
3.4若result[k]<m且k<n-1,即该部件供应商已有搜寻结果但尚有部件未进行搜寻,判断已选择供应商部件的重量,与temp进行大小比较,若小于,则k++,对下一部件的供应商进行搜寻;否则,此结点定非最优解,进行下一轮循环;
3.5若result[k]>=m即该部件已无可选供应商,则回溯,result[k]=-1,k–即重置当前部件的供应商结果,回溯至上一节点的选择。
4,输出结果。

c++实现

#include<iostream>
using namespace std;
int n, m; int d;
int weight[10][10];
int value[10][10];
int *result = new int[n];
int check(int i)
{
	int sum = 0;
	for (int a = 0; a <= i; a++)
		sum = sum + value[a][result[a]];//约束条件:判断所选部件的价值是否超过最大价值
	if (sum > d)return 0;//超过则返回0
	else return 1;
}
int main()
{
	cin >> n >> m >> d;
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < m; j++)
			cin >> weight[i][j];
	}
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < m; j++)
			cin >> value[i][j];
	}
	for (int a = 0; a < n; a++)
		result[a] = -1;
	int k = 0; int temp = 99999;  int *r = new int[n];
	//k表示正在探索适合部件的编号
	while (k >= 0) {
		result[k]++;//进行下一供应商的部件判断
		while (result[k] < m&&check(k) == 0)//无法满足约束条件判断下一供应商直至无可选供应商
			result[k]++;
		//走到了一个叶子节点,比较其重量,存入较少的结果
		if (result[k] < m&&k == n - 1) {
			int sum = 0;
			for (int a = 0; a <= k; a++)
				sum = sum + weight[a][result[a]];
			if (sum < temp)
			{
				temp = sum;
				for (int a = 0; a <=k; a++)
					r[a] = result[a];
			}
		}
		//有可选供应商进行下一部件的判断
		if (result[k] < m&&k < n - 1) {
			int sum = 0;
			for (int a = 0; a <= k; a++)
				sum = sum + weight[a][result[a]];
			if(sum<temp)
				k++; 
			else 
				continue;
		}
		//无可选供应商,回溯至上一节点
		if (result[k] >= m)
			result[k--] = -1;
	    
	}
	for (int a = 0; a < n; a++)
	{
		cout << r[a] + 1;
		if (a < n - 1)cout << " ";
	}
	cout << "\n";
	cout << temp;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值