7-1 子集和问题 (50 分)
题目描述
设集合S={x1,x2,…,xn}是一个正整数集合,c是一个正整数,子集和问题判定是否存在S的一个子集S1,使S1中的元素之和为c。试设计一个解子集和问题的回溯法,并输出利用回溯法在搜索树(按输入顺序建立)中找到的第一个解。
输入格式:
输入数据第1行有2个正整数n和c,n表示S的大小,c是子集和的目标值。接下来的1行中,有n个正整数,表示集合S中的元素。 是子集和的目标值。接下来的1 行中,有n个正整数,表示集合S中的元素。
输出格式:
输出利用回溯法找到的第一个解,以空格分隔,最后一个输出的后面有空格。当问题无解时,输出“No Solution!”。
输入样例:
在这里给出一组输入。例如:
5 10
2 2 6 5 4
结尾无空行
输出样例:
在这里给出相应的输出。例如:
2 2 6
结尾无空行
基本思路
回溯剪枝,见注释
参考代码
//非递归实现,解集树搜索,类似于枚举,重点在于回溯的时候剪枝的判断。
//对于每一个解集树结点,优先考虑“加入”,因此回溯时可以不用考虑每一层的“不加”分支(因为上层节点考虑“加入”,当前结点“不加入”的考虑没有意义)
//因此回溯时找到第一个“不加入”的结点(下层结点中的所有“不加入”分支不考虑),然后继续回溯到第一个“加入”的结点,将该结点即为问题节点,然后考虑其“不加入”分支继续搜索
#include <iostream>
using namespace std;
int n,c;
const int maxn=100000;
int a[maxn]={0};
int visit[maxn]={0};
int sum=0;
void print(int x);
bool find(int x,int n,int c);
int main()
{
cin>>n>>c;
int sum_each=0;
for(int i=0;i<n;i++)
{
cin>>a[i];
sum_each+=a[i];
}
int judge;
judge=find(0,n,c);
if(sum_each<c||!judge){
cout<<"No Solution!";
}
}
void print(int x){
for(int i=0;i<=x;i++)
{
if(visit[i])
{
cout<<a[i]<<" ";
}
}
}
bool find(int x,int n,int c)//定位,总数,目标数
{
while(true){
if(visit[x]==0)//未访问
{
visit[x]=1;
sum+=a[x];
if(sum==c)//满足题目要求
{
print(x);
return true; 9
}
else if(sum>c)//大于所需数则回溯
{
visit[x]=0;
sum-=a[x];
}
x++;
}
if(x==n)//回溯剪枝
{
//因为每一个分支都是优先选择"加入的",因此每一层回溯的时候不用考虑“不加”的分支
while(true)
{
x--;
if(x<0) return false;//无解
if(visit[x]==0){
break;
}
visit[x]=0;//将剪去的结点的分支默认都设置为0,以便后面重新开始
//一直回溯到选择“不加”的那一个结点,回溯路径上的“不加”的分支全部剪掉
}
//找到第一个“不加”分支的结点后,继续回溯,找到第一个“加”分支选择的结点
while(true){
x--;
if(x<0) return false;//无解的情况
if(visit[x]){
break;
}
}
//x位置结点的“加”分支被全部剪掉,转而考虑不加“分支”
sum-=a[x];
visit[x]=0;
x++;
}
}
}
习题答案
代码:
#include <iostream>
#include <climits>
#include <cstdio>
#include <cstdlib>
using namespace std;
int* x;
int sum = 0; //子集和
int n;//数字个数
int c;//子集和
int* numbers; //数字集合
int remain; //剩余数字和
void backtrack (int t)
{
if (t > n) { // 到达叶子结点
if (sum == c) {
for (int i = 1; i <= n; i++) {
if (x[i] == 1) //如果选择了该数字则输出
cout << numbers[i] << " ";
}
cout << endl;
exit(0);
}
return;
}
//选择当前的数字,遍历左子树
//如果当前分支的数字和已经超过了子集和c,则剪枝
if (sum + numbers[t] <= c) {
sum += numbers[t];
x[t] = 1;
backtrack(t + 1);
sum -= numbers[t];
}
remain -= numbers[t]; // 进入到下一层,剩余的数字和要减去当前的数字
//不选择当前的数字,遍历右子树
//如果当前数字和加上所有剩余数字和仍小于前面访问过的叶子节点的数字和,则剪枝
if (sum + remain >= c) {
x[t] = 0;
backtrack(t + 1);
}
remain += numbers[t];
}
int main() {
cin >> n >> c;
numbers = new int[n + 1];
remain = 0;
for (int i = 1; i <= n; i++) {
cin >> numbers[i];
remain += numbers[i]; // 剩余数字和初始化为所有数字的和
}
if (remain < c) {
cout << "No Solution!" << endl;
return 0;
}
x = new int[n + 1]; //记录是否选择第i个数字
backtrack(1);
cout << "No Solution!" << endl;
return 0;
}
7-2 最佳调度问题 (40 分)
题目描述
假设有n(n<=20)个任务由k(k<=20)个可并行工作的机器完成。完成任务i需要的时间为ti。 试设计一个算法,对任意给定的整数n和k,以及完成任务i 需要的时间为ti ,i=1~n。计算完成这n个任务的最佳调度,使得完成全部任务的时间最早。
输入格式:
输入数据的第一行有2 个正整数n和k。第2 行的n个正整数是完成n个任务需要的时间。
输出格式:
将计算出的完成全部任务的最早时间输出到屏幕。
输入样例:
在这里给出一组输入。例如:
7 3
2 14 4 16 6 5 3
结尾无空行
输出样例:
在这里给出相应的输出。例如:
17
结尾无空行
参考代码
#include <iostream>
using namespace std;
const int maxn=111111;
int t[maxn];// 完成任务i所需要的时间为 time【i】 , 数组名用 time 会报错
int n,k;// n个等待分配的任务(时间),k个容器(机器)
int best=maxn;//最佳调度(最短时间),初始化为一个很大的数
int total[maxn]={0};// 分配完任务后,第i个容器完成任务所需要的时间总和为 total【i】,初始化为 0
int deep=0;// 深度,tmie【dep】表示第dep个任务需要的时间,
// 每次分配完 dep+1 , 当 dep == n 时可以得到一个分配方案, 并得到该方案所需要的时间
int get_time();// 计算一种分配方案所需要的时间
void dfs(int deep);
int main()
{
cin>>n>>k;
for(int i=0;i<n;i++)
{
cin>>t[i];
}
dfs(0);// 从 第0个任务的分配开始
cout<<best;
return 0;
}
int get_time(){
int tt=0;
for(int i=0;i<k;i++)
{
tt=max(total[i],tt);//该分配方案所需要的时间 = 最后完成任务的那个容器所需要的时间
}
return tt;
}
// 深度遍历
void dfs(int deep){//第dep个任务的分配
if(deep==n){// 深度遍历到了叶子节点,得到一个分配方案,更新 best
int Totaltime=get_time();
best=min(best,Totaltime);
return;
}
for(int i=0;i<k;i++)
{
total[i]+=t[deep];// 将任务deep分给机器i
if(total[i]<best) // 如果机器i完成任务所需要的时间 > best , 那就没有继续往下搜索的必要了
dfs(deep+1);// 继续分配第 dep+1 个任务
total[i]-=t[deep];// 回溯,任务dep不分给机器i
//i++ , 继续讨论 任务dep分配给机器i+1的情况
}
return ;
}
习题答案
代码:
#include <iostream>
using namespace std;
int bestt = 50000;
int ct = 0;
int m[1000] = {0};
int jobs[1000] = {0};
int n, k;
void backtrack(int t) {
if (t >= n) { //到达叶子节点,找到问题的一个解
int max = 0;
//选择最长的工作时间
for (int i = 0; i < k; i++) {
if (m[i] > max)
max = m[i];
}
if (bestt > max)
bestt = max;
return;
}
//遍历每个分支
for (int i = 0; i < k; i++) {
m[i] += jobs[t];
if (m[i] < bestt) {
backtrack(t+1);
}
m[i] -= jobs[t];
}
}
int main() {
cin >> n >> k;
for (int i = 0; i < n; i++) {
cin >> jobs[i];
}
backtrack(0);
cout << bestt << endl;
}