回溯法
求解步骤
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;
}