回溯法(1):全排列问题

 本文介绍回溯法的全排列问题及其两种衍生题。解法只需要在原来的基础上做些修改即可。

  1.需要一个一维数组来保存结果集 。需要考虑以哪种属性来构造数组,即考虑数组中要存放什么类型的数据,反过来放行不行?具体看衍生题第2题。

  2.需要进行两次比较一次是 一次全排列后进行一次比较,另一次是在递归的过程中进行比较,进而排除那些极端情况,比如过程中产生的部分结果要比最优的总结果还要糟糕,进而排除,即剪枝

   3.*分析问题有时要考虑极端情况

      for i = t to n do, t表示到下一层时不用从1开始,而是直接从t开始,t就相当于节点的出度。那么什么时候用t,什么时候用1呢,即什么时候是 for i = 1 to n do ? 当节点的出度为n时,即从i= 1开始算起。也就是说此时不再是n个数的不重复全排列了,也就是说前后可以重复。比如说这题可以 1 -> 2 -> 1.比如说n位数有2^n种肯能。也就是说进入下一层节点的时候要从头对所有可能对情况进行遍历。

后面几题均会以此题为模版出题 


衍生问题1:

工作分配问题

设有n件工作要分配给n个人去完成,将工作i分配给第j个人所需费用为。试设计一个算法,为每个人分配1件不同的工作,并使总费用达到最小。

//在全排列对基础上筛选出最优解
void Backtrack(int i){
    if(i > n){//一次全排列完进行比较
        if(cc < minc)//cc存放当前总代价
            return;
    }
    else {
        for( int j = i; j <= n; j++){//从i开始对节点的出度进行遍历
            if(cc < minc){//剪枝
                swap(work[i], work[j]);//work[i]存放排列结果的下标
                cc += c[i][work[i]];
                Backtrack(i+1);//进入下一层节点
                cc -= c[i][work[i]];//矫正cc的值,返回到上一层的状态
                swap(work[i], work[j]);//矫正顺序,返回到上一层的状态
            }
        }
    }
}

 衍生问题2:

最佳调度问题

设有n个任务由k个可并行工作的机器来完成,完成任务i需要时间为。试设计一个算法找出完成这n个任务的最佳调度,使完成全部任务的时间最早。(要求给出调度方案)

 

该算法可抽象为子集树回溯算法,针对特定的任务数和机器数定义解空间,对于n个任务和k个机器,

解编码:(X1,X2,。。。,Xn),Xi表示给任务i分配的机器编号;

解空间:{(X1,X2,。。。,Xn)| Xi属于S,i=1到n},S={1,2,。。。,k} 

      这个问题可以想象成有k个槽,n个高矮不一的箱子,怎么样把n个箱子放到k个槽当中,从而使高度最高的那个槽的高度最短?方法有很多中,极端的做法是把n个箱子都放到一个槽中,从而使时间运行时间最大,也可以分摊开来,从而使并行的运行时间最少。

    1.构造一维数组:time_mac[]表示第i个machine要运行的时间,x[task]表示当前的调度策略。best_x[i]表示最优的调度策略。

    2.if(time_mac[i]<min_t)  //为什么要跟min_t进行比较,因为过程中产生的非完全数据可能比最优的完全数据还要差,而如果继续进入下一层,只会时结果增加,因此要进行排除。考虑极端情况,将所有任务都分配到第一台机器中。

#define NUM_TASK 10
#define NUM_MAC 3
#include <iostream>
#include <climits>
using namespace std;
void output(int x[]);
void BackTrack(int task);
int getTime(int time_mac[]);
void output_assign(int best_x[]);
 
//所有数组下标从1开始
int x[NUM_TASK + 1];//x[task]表示给任务task分配机器x[task]
int best_x[NUM_TASK+1];//存储最优分配方案
int min_t=INT_MAX;//执行任务所需的最小时间
int t[NUM_TASK + 1] = { 0,1,7,4,0,9,4,8,8,2,4 };//每个任务所需时间
//int t[NUM_TASK + 1] = { 0,1,7,4 };//每个任务所需时间
int time_mac[NUM_MAC + 1] = {0};//每个机器运行结束的时间
 

void BackTrack(int task) {
 
	if (task > NUM_TASK) {
		
		int cur_time = getTime(time_mac);//当前已分配任务的完成时间
		/*输出所有分配情况,以及对应的时间*/
		//output(x);
		//cout << "time=" << cur_time <<endl;
		
		if (cur_time < min_t) { //剪枝
			min_t = cur_time;
			for (int i = 1; i <=NUM_TASK; i++) {
				best_x[i] = x[i];
			}
		}
	}
	else{
		for (int i = 1; i <= NUM_MAC; i++) {
			x[task] = i;
			time_mac[i] += t[task];//在第i台机器上添加任务task
			if(time_mac[i]<min_t)  //为什么要跟min_t进行比较,因为过程中产生的非完全数据可能比最优的完全数据还要差,而如果继续进入下一层,只会时结果增加,因此要进行排除。
				BackTrack(task+1);/
			time_mac[i] -= t[task];
		}
	}
}
 
void output(int x[]) {
	for (int i = 1; i <= NUM_TASK; i++) {
		cout << x[i]<< " ";
	}
}
 //返回time_mac[]数组当中的最大值
int getTime(int time_mac[]) {
	int max_time=time_mac[1];
	for (int i = 2; i <= NUM_MAC; i++) {
		if (time_mac[i] > max_time) {
			max_time = time_mac[i];
		}
	}
	return max_time;
}
 
void output_assign(int best_x[]) {
	for (int i = 1; i <= NUM_TASK; i++) {
		cout << "任务" << i << "分配给机器" << best_x[i] << endl;
	}
}

int main() {
	BackTrack(1);
 
	cout << "各个任务执行时间依次为:" << endl;
	for (int i = 1; i <= NUM_TASK; i++) {
		cout << t[i] << " ";
	}
	cout << endl;
	cout << "所需要的最小时间为:"<<min_t << endl;
	//output(best_x);
	output_assign(best_x);
	return 0;
}

目前关于递归的题目能采用套模版,然后在此基础上进行修改来解题。而要理解递归过程,必须画出递归调用树。

猜你喜欢

转载自blog.csdn.net/raylrnd/article/details/84403104