欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

回溯法

程序员文章站 2022-07-12 10:08:08
...

一、概述

1、概念

回溯法有“通用解题法”之称。用它可以系统地搜索问题的所有解。回溯法是一个既带有系统性又带有跳跃性的搜索算法。

在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。

当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯 。(其实回溯法就是对隐式图的深度优先搜索算法)。若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。 而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。

2、注意

(1)为什么有的回溯是0到N-1,有的是1到N

第一种情况数组下标一般从0开始,第二种一般从1开始(推荐)

(2)对于层数不确定或条件复杂的,最好用非回溯做

如不连续的和条件

(3)为什么不用广度优先

https://mp.weixin.qq.com/s/ClKJXdyFeSSW0A8FLFksnA

3、解题思路

(1)针对所给问题,定义易于搜索的解空间;

(2)以深度优先方式搜索解空间

(3)在搜索过程中对每一步用**剪枝函数(判断条件:可以放吗?值符合条件吗?)**避免无效搜索。

4、技巧

(1)当情况比较复杂时,要学会自己处理:如使用数组等

(2)当起点确定时,从第二层开始,但要注意数组起点的初始化

二、子集树问题

1、概念

当所给问题是从n个元素的集合S中找出S满足某种性质的子集时,相应的解空间树称为子集树

这类子集树通常有2n个叶结点,其结点总个数为2(n+1)-1。遍历子集树的算法需Ω(2^n)计算时间。总解空间层为1到n+1。如下,3个元素,层数为1到4。

回溯法

2、解题思路

 /**
  * 回溯法搜索子集树的算法范式
  * output(x)     记录或输出得到的可行解x
  * constraint(t) 当前结点的约束函数
  * bount(t)      当前结点的限界函数
  * @param t 	  t为当前解空间的层数,从第0层开始。n为总解空间的层数
  */
void backtrace(int t)
{
    //这里可以有剪枝
    //....
    
    //一次搜索完成
	if (t > n)
	{
        //进行当前求最值、是否符合题意等情况的判断,符合则记录或输出!!!
		Output(x);
	}
	else
	{
        //相当于暴力枚举,列举全部情况
        //1、处理第t个个体,第t个个体有n种选择
		for (int i = 0; i <= n; i++)
		{
            //2、处理
			x[t] = i;
            //3、判断(最优性剪枝和可行性剪枝、是否已访问等)
            if (constrain(t) && bound(t))
            {
                backtrace(t + 1);
            }
            //4、恢复(简单的赋值后续可以重新覆盖,可以不进行恢复)
            ...
		}
	}	
}

3、例题

(1)0-1背包问题

两种以上选择,处理完要恢复。

回溯法

#include <stdio.h>

#define N 3         //物品的数量
#define C 16        //背包的容量

int w[4]={0,10,8,5};  //每个物品的重量
int v[4]={0,5,4,1};   //每个物品的价值
int x[4]={0,0,0,0};   //x[i]=1代表物品i放入背包,0代表不放入

int CurWeight = 0;  //当前放入背包的物品总重量
int CurValue = 0;   //当前放入背包的物品总价值

int BestValue = 0;  //最优值;当前的最大价值,初始化为0
int BestX[N];       //最优解;BestX[i]=1代表物品i放入背包,0代表不放入

void backtrack(int t)
{
    //一次搜索完毕
    if(t>N)
    {
        //如果找到了一个更优的解
        if(CurValue>BestValue)
        {
            //保存更优的值和解
            BestValue = CurValue;
            for(int i=1;i<=N;++i) BestX[i] = x[i];
        }
    }
    else
    {
        //遍历当前节点的子节点:0 不放入背包,1放入背包
        for(int i=0;i<=1;++i)
        {
            x[t]=i;

            if(i==0) //不放入背包
            {
                backtrack(t+1);
            }
            else //放入背包
            {
                 //约束条件:放的下
                if((CurWeight+w[t])<=C)
                {
                    CurWeight += w[t];
                    CurValue += v[t];
                    backtrack(t+1);
                    CurWeight -= w[t];	//恢复
                    CurValue -= v[t];
                }
            }
        }

    }
}

int main(int argc, char* argv[])
{
    backtrack(1);
    printf("最优值:%d\n",BestValue);
    for(int i=1;i<=N;i++)
    {
       printf("最优解:%-3d",BestX[i]);
    }
    return 0;
}

(2)N皇后问题

一个一个放。

第一个皇后放到第N个结束。充分利用问题隐藏的约束条件:每个皇后必然在不同的行(列),每个行(列)必然也只有一个皇后。这样我们就**可以把第N个皇后放到第N个行中,使用Pos[i]表示皇后i在i行中的位置(也就是列号)(i = 0 to N-1)。**这样代码会大大的简洁,因为节点的子节点数目会减少,判断冲突也更简单。而且避免了重复的计算。

#include<bits/stdc++.h>
using namespace std;
int n;
int x[100];	//第t个放在第x[]列
int v[100]; //时间优化:标注第v[]列是否已被占用
int sum=0;

void output(){
    cout << "第" <<sum << "种放置方法为:" << endl;
 for(int i=1;i<=n;i++){
    cout << "(" <<i << "," << x[i] << ")" << endl;
 }

}
int place(int k){
   //遍历所有已经放的1到k-1个皇后位置
   for(int j=1;j<k;j++){
    if(x[j]==x[k] || abs(x[j]-x[k])== abs(j-k))	//不能同列,不能对角
        return 0;
   }
   return 1;
}
//递归
void backTrace(int t){
    if(t>n){ //摆完了,如果t>n说明已经放了n个,已经完成一次放置
        sum++;
        //output();
    }
    else{
        //处理第t个,有n种选择,尝试将第t个皇后放在第t行第x[t](i)列
        for(int i=1;i<=n;i++){
            //时间优化:第i列没有个体放置
            if(v[i] == 0){
               x[t]=i;
               if(place(t)){	//可以放在i位置处,则继续搜索
                    v[i] = 1;
                    BackTrace(t+1);
                    v[i] = 0;
               }
               //这里不可以省略!!
               x[t]=0; 
            }
        }

    }
}
//非递归
void backTrace1(){
   int k;
   x[1]=0;
   k=1;
   while(k>=1){
    x[k]+=1;////先放在第一个位置
    while((x[k]<=n && !(place(k)))){//如果不能放
        x[k]++;//  //放在下一个位置
    }
    if(x[k]<=n){
        if(k==n){// //如果已经放完了n个皇后
            sum++;
            //output();
        }
        else{//  //没有处理完,让k自加,处理下一个皇后
            k++;
            x[k]=0;
        }
    }else{// 当前无法完成放置,则进行剪枝,回溯回去,回到第k-1步
      k--;
    }
   }
}
int main()
{
    memset(x,0,sizeof(x));
    cin >> n;
    backTrace(1);
    //backTrace1();
    cout << sum;
    return 0;
}

(3)图的着色问题

https://www.cnblogs.com/ECJTUACM-873284962/p/8447050.html#_label3_1

(4)骑士游历问题

当情况比较复杂时,要学会自己处理:如使用数组等

https://www.cnblogs.com/cs-whut/p/11153529.html

(5)集合划分问题

https://www.cnblogs.com/ECJTUACM-873284962/p/8447050.html#_label3_1

(6)连续邮资问题???

**问题:**假设国家发行了k种不同面值的邮票,并且规定每张信封上最多只允许贴h张邮票。连续邮资问题要求对于给定的k和h的值,给出邮票面值的最佳设计,在1张信封上可贴出从邮资1开始,增量为1的最大连续邮资区间。

思路:

(1)用stampval来保存各个面值,用maxval来保存当前所有面值能组成的最大连续面值。

(2)stampval[0] 一定等于1,因为1是最小的正整数。相应的,maxval[0]=1*h。

(3)stampval[0] 一定等于1,因为1是最小的正整数。相应的,maxval[0]=1*h。接下去就是确定第二个,第三个…第k个邮票的面值了。

(4)对于stampval[i+1],它的取值范围是stampval[i]+1~maxval[i]+1。stampval[i]+1是因为这一次取的面值肯定要比上一次的面值大,而这次取的面值的上限是上次能达到的最大连续面值+1, 是因为如果比这个更大的话, 那么就会出现断层, 即无法组成上次最大面值+1这个数了。

https://www.cnblogs.com/ECJTUACM-873284962/p/8447050.html#_label3_1

//标记每种取到的钱数 
//m使用了第m张邮票贴到邮件上进行凑总数,不能多于h张
void mark(int n,int m,int sum)
{  
    if(m>h) return;  
    vis[sum]=true;
    //现有n种数值不同的邮票可以凑数
    for(int i=1;i<=n;++i) mark(n,m+1,sum+stampval[i]);    
}

(7)符号三角形问题?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kPQq2SYz-1595651301331)(C:\Users\ddjj6\AppData\Roaming\Typora\typora-user-images\1583325911845.png)]

**问题:**第一行有n个元素,对于给定的n,计算有多少个不同的符号三角形,使其所含的“+”和“-”的个数相同。

思路:

(1)三角形所含符号总个数为n*(n+1)/2,当其为奇数时,显然不存在包含的"+“号个数与”-"号个数相同的符号三角形。此条件可用于剪枝。

(2)要会使用数组对三角形进行模拟。

(3)由题意知,只要第一行n个确定,整个三角型就确定了。

(4)第一行有n个符号,则此时三角形有n行。

#include<bits/stdc++.h>
using namespace std;
int n,half,counts,p[100][100],sum;//(2)

void backtrack(int t)
{
    if(t>n) sum++;
    else
    {
        for(int i=0;i<2;i++)
        {
            p[1][t]=i;//第一行符号
            counts+=i;//当前"+"号个数
            //由当前第一行符号情况得出整个三角形,遍历第二行到第t行   (3)(4)
            for(int j=2;j<=t;j++)
            {
                //条件
                p[j][t-j+1]=p[j-1][t-j+1]^p[j-1][t-j+2];
                counts+=p[j][t-j+1];
            }
            if(!((counts>half)||(t*(t+1)/2-counts>half))){
                backtrack(t+1);
            }
            for(int j=2;j<=t;j++)
            {
                counts-=p[j][t-j+1];
            }
            counts-=i;
        }
    }
}

//第一行的符号个数,n*(n+1)/4,当前"+"号个数,符号三角矩阵,已找到的符号三角形数
int main()
{
    cin>>n;
    half=n*(n+1)/2;
    //(1)
    if(half%2==1)
    {
        cout<<"共有0个不同的符号三角形。"<<endl;
        return 0;
    }
    half=half/2;
    backtrack(1);
    cout<<"共有"<<sum<<"个不同的符号三角形。"<<endl;
}

https://www.cnblogs.com/ECJTUACM-873284962/p/8447050.html#_label3_1

(8)健康的荷斯坦奶牛?

字典序最小:注意这里从1往0开始枚举,因为题目希望字典序最小

import java.util.Scanner;
public class Main {
	
	static int v;
	static int[] need;
	static int g;
	static int[][] have;
	static int[] select;
	static int min = Integer.MAX_VALUE;
	static int[] best;
	static int flag = 0;
	public static void main(String[] args) {	
		Scanner in = new Scanner(System.in);
		v = in.nextInt();
		need = new int[v + 1];
		for(int i = 1;i <= v;i++) {
			need[i] = in.nextInt();
		}
		g = in.nextInt();
		select = new int[g + 1];
		best = new int[g + 1];
		for(int i = 1;i <= g;i++) {
			select[i] = 0;
			best[i] = 0;
		}
		have = new int[g + 1][v + 1]; 
		for(int i = 1;i <= g;i++) {
			for(int j = 1;j <= v;j++) {
				have[i][j] = in.nextInt();
			}
		}
		
		dfs(1,0);
		
		System.out.print(min + " ");
		for(int i = 1;i <= g;i++) {
			if(best[i] == 1) {
				System.out.print(i);
			}
			if(i < g && best[i] == 1) {
				System.out.print(" ");
			}
		}
		
	}
	
	public static void dfs(int t,int num) {
		if(num >= min) return;
		if(t > g) {
			if(judge(select)) {
				if(num < min) {
					min = num;
					for(int i = 1;i <= g;i++) {
						best[i] = select[i];
					}
				}
			}
		}else {
			//1、多种情况,注意这里从1往0开始枚举(因为题目希望字典序最小,故前面的要先考虑先选)
			for(int i = 1;i >= 0;i--) {
				//2、处理
				select[t] = i;
				//3、判断
				if(i == 0) {
					dfs(t + 1,num);
				}else if(i == 1) {
					dfs(t + 1,num + 1);
				}
				//4、回复
				select[t] = 0;
			}
		}
	
			
	}
	
	public static boolean judge(int[] select) {
		for(int j = 1;j <= v;j++) {
			int t = 0;
			for(int i = 1;i <= g;i++) {
				if(select[i] == 1) {
					t += have[i][j];
				}
			}
			if(t < need[j]) {
				return false;
			}
		}
		return true;
	}
	
	
}

(9)变换????

https://www.luogu.com.cn/problem/P1037

import java.util.Scanner;
public class Main {
	
	static int[][] rule = new int[10][10];
	static int result = 0;
	static int num;
	public static void main(String[] args) {	
		Scanner in = new Scanner(System.in);
		int n = in.nextInt();
		int k = in.nextInt();
		for(int a = 0;a < k;a++) {
			int i = in.nextInt();
			int j = in.nextInt();
			rule[i][j] = 1;
		}
		num = get(n);
		
	}
	
	public static void dfs(int t) {
		if(t > num) {
			
		}else {
			
		}
			
	}
	
	public static boolean judge(int[] select) {
		for(int j = 1;j <= v;j++) {
			int t = 0;
			for(int i = 1;i <= g;i++) {
				if(select[i] == 1) {
					t += have[i][j];
				}
			}
			if(t < need[j]) {
				return false;
			}
		}
		return true;
	}
	
	public static int get(int n) {
		int o = 0;
		while(n != 0) {
			num++;
			n /= 10;
		}
		return o;
	}
	
}

(10)盾神与砝码称重??

思路:

(1)每个砝码都可以选择放砝码盘、物品盘、不放(易错!)

(2)类似于0-1背包问题

https://www.dotcpp.com/oj/problem1548.html

回溯

import java.util.*;
 
public class Main
{

	static int m = 0,n = 0;
	static int[] fa = new int[25];
	static boolean flag = false;
	static int w;
	static int sum = 0;
    public static void main(String args[]) {
    	Scanner in = new Scanner(System.in);
    	n = in.nextInt();
    	m = in.nextInt();
    	for(int i = 1;i <= n;i++) {
    		fa[i] = in.nextInt();
    	}
    	for(int i = 0;i < m;i++) {
    		w = in.nextInt();
    		flag = false;
			dfs(1);
			if(flag) {
				System.out.println("YES");
			}else {
				System.out.println("NO");
			}
    	}
    }
    
    public static void dfs(int t) {
    	if(t > n) {
    		if(sum == w) {
    			flag = true;
    		}
    	}else {
    		for(int i = 0;i < 3;i++) {
    			if(i == 0) {
    				dfs(t + 1);
    			}
    			if(i == 1) {
    				sum += fa[t];
    				dfs(t + 1);
    				sum -= fa[t];
    			}
    			if(i == 2) {
    				sum -= fa[t];
    				dfs(t + 1);
    				sum += fa[t];
    			}
    		}
    	}
    	
    }

}

非回溯

import java.util.*;
 
public class Main
{

	static int m = 0,n = 0;
	static int[] fa = new int[25];
	static boolean flag = false;
	static int w;
    public static void main(String args[]) {
    	Scanner in = new Scanner(System.in);
    	n = in.nextInt();
    	m = in.nextInt();
    	for(int i = 1;i <= n;i++) {
    		fa[i] = in.nextInt();
    	}
    	for(int i = 0;i < m;i++) {
    		w = in.nextInt();
    		flag = false;
			dfs(0,1);
			if(flag) {
				System.out.println("YES");
			}else {
				System.out.println("NO");
			}
    	}
    }
    
    public static void dfs(int now,int index) {
    	if(now == w) {
    		flag = true;
    		return ;
    	}
    	if(index > n) {
    		return ;
    	}
    	dfs(now + fa[index],index + 1);
    	dfs(now - fa[index],index + 1);
    	dfs(now,index + 1);
    }

}

三、排序树问题

1、概念

当所给问题是确定n个元素满足某种性质的排列时,相应的解空间树称为排列树。例如旅行售货员问题(如下图)的解空间树是一棵排列树,这类排列树通常有n!个叶结点。遍历子集树的算法需Ω(n!)计算时间。

根为第一层,n个数,n+1 层,从第一到第n个进行

特别适用于排序 序列中

回溯法

2、解题思路

for if swap 处理 递归 恢复

/**
 * 
 * output(x)     树一次遍历结束,记录或输出得到的可行解x
 * constraint(t) 当前结点的题目约束函数
 * bount(t)      当前结点的数据限界函数
 * @param t      t为当前解空间的层数
 * @param n      n为条件给的个数
 */
   void backtrack(int t){
       if(t > n){
           //进行当前求最值、是否符合题意等情况的判断,符合则记录或输出!!!
       		output(x);
       }
       else{
            //第t层有(n-t+1)种交换方式
       		for (int i = t; i <= n; i++) {
                //1、(判断准备工作)先判断再交换(最后计算)
               //判断(最优性剪枝和可行性剪枝、是否已访问等)
               if(constraint(t) && bount(t)){
                    //交换
                   	swap(x[t], x[i]);
                    //(最后计算)
                    ...
                    backtrack(t+1);
               		swap(x[t], x[i]);
             	}
                //2、(判断准备工作)先交换再判断(最后计算)
                //swap(x[t], x[i]);  
                //if (constraint(t)&&bound(t)) backtrack(t+1);  
                //swap(x[t], x[i]); 
           }
       }
   }

backtrack(1)	//第一层开始从头到尾
backtrack(2)	//第一层固定(起点固定),从第二层开始

java swap()函数

	public static void swap(int i,int j) {
		int t = x[i];
		x[i] = x[j];
		x[j] = t;
	}

3、技巧

(1)注意初始化

初始化序列干脆为1 2 3 4 …… n

(2)求最优记录输出要判断

(3)推荐使用顺序:判断-交换-计算

(4)为什么要swap呢

这个swap其实是“挪”向了同层的其他节点,但这个地方我们没有建树,直接通过在这条路径上进行swap得到。

譬如,一条12345路径,我们从1出发1->2->3->4->5

那我们backtrace(2)的时候会把2和345交换位置,就是为了获得1->3,1->4,1->5开头的路径。

(5)一般数组下标都是从1开始,便于根据题意操作

(6)由backpack(t+1)知:若当前为第t层,则上一节点在第(t-1)层为x[t-1],下一节点为x[i]

4、例题

(1)全排列问题

https://blog.csdn.net/long_long_int/article/details/79930888

非字典序 :swap

#include<iostream>
using namespace std;
int n,a[100];
void dfs(int t)
{
	if(t>n)
	{
		for(int i=1;i<=n;i++)
		{
			cout<<a[i]<<" ";
		}
		cout<<endl;
		return;
	}
	for(int i=t;i<=n;i++)
	{
		swap(a[i],a[t]);//交换 
		dfs(t+1);
		swap(a[i],a[t]);//回溯回来的时候一定要换回来 
	}
}
int main()
{
	while(cin>>n)
	{
		for(int i=1;i<=n;i++)
		{
			a[i]=i;//初始化a数组,第一次干脆定从小到大
		}
		dfs(1);
	}
}

字典序:子集树

#include<iostream>
using namespace std;
int n,a[100],v[100];
//a数组用于保存每一次的排列,v数组用于判断数字是不是已经被选过 
void dfs(int dp)//dp从1到n,执行n次,每次选择一个数 
{
	if(dp>n)//dp为n+1的时候,就说明已经选了n个,可以输出了; 
	{
		for(int i=1;i<=n;i++)
		{
			cout<<a[i]<<" ";
		}
		cout<<endl;
		return;
	}
	for(int i=1;i<=n;i++) 
	{
        a[dp]=i;//保存当前选择 
		if(!v[i])//判断是不是选择过 
		{
			v[i]=1;//标记这个数字,防止同一个排列选择相同的数字。 
			dfs(dp+1);
			v[i]=0;//恢复
		}
	}
} 
int main()
{
	while(cin>>n)
	{
		dfs(1);
	}
}

(2)旅行商问题

起点已定,故从第2层开始即可

https://blog.csdn.net/lfb637/article/details/80635047

/**
   @回溯-旅行商(TSP)问题
*/
#include<iostream>
#include<algorithm>
#define MAX 100
using  namespace std;
int n;                               //城市个数
int a[MAX][MAX];                   //城市间距离
int x[MAX];                       //记录路径
int bestx[MAX]  = {0};           //记录最优路径
int bestp = 63355;              //最短路径长
int cp = 0;                    //当前路径长
void backpack(int t){
     if(t>n){
        //要能回去且距离仍最小
        if((a[x[n]][1])&&(a[x[n]][1]+cp<bestp)){
              bestp = a[x[n]][1]+cp;
              for(int i = 1;i<=n;i++){
                 bestx[i] = x[i];
              }
        }
     }else{
         for(int i = t;i<=n;i++){
             /*约束为当前节点到下一节点的长度不为0,限界为走过的长度+当前要走的长度之和小于最优长度
              *注意:backpack(t+1)当前为第t层,上一节点在第(t-1)层为x[t-1],下一节点为x[i]
              */
             //1、判断(最优性剪枝和可行性剪枝)
             if((a[x[t-1]][x[i]])&&(cp+a[x[t-1]][x[i]]<bestp)){
                //2、交换
                swap(x[t],x[i]);   
                //3、计算
                cp+=a[x[t-1]][x[t]];
                backpack(t+1);
                //恢复
                cp-=a[x[t-1]][x[t]];
                swap(x[t],x[i]);
            }
        }
    }
}

int main(){
    cout<<"输入城市个数:"<<endl;
    cin>>n;      //顶点数
    //初始化序列
    for(int i = 1;i<=n;i++){
         x[i] = i;
    }
    cout<<"输入城市之间的距离(0表示城市间不通):"<<endl;
    for(int i = 1;i<=n;i++){
        for(int j = 1;j<=n;j++){
            cin>>a[i][j];
        }
    }
    //起点已定,故从第2层开始即可
    backpack(2);
    cout<<"最少旅行费用为: "<<bestp<<endl;
    cout<<"旅行路径为:"<<endl;
    for(int i = 1;i<=n;i++){
       cout<<bestx[i]<<" ";
    }
    cout<<bestx[1];
    return 0;
}
/*
4
0 30 6 4
30 0 5 10
6 5 0 20
4 10 20 0
*/

import java.util.Scanner;
public class Main {
	

	static int n;
	static int[][] a;
	static int[] x;
	static int now;
	static int[] r;
	static int min = Integer.MAX_VALUE;
	public static void main(String[] args) {	
		Scanner in = new Scanner(System.in);
		n = in.nextInt();
		a = new int[n + 1][n + 1];
		r = new int[n + 1];
		x = new int[n + 1];
		for(int i = 1;i <= n;i++) {
			x[i] = i;
		}
		for(int i = 1;i <= n;i++) {
			for(int j = 1;j <= n;j++) {
				a[i][j] = in.nextInt();
			}
		}
		dfs(2);
		System.out.println(min);
		for(int i = 1;i <= n;i++) {
			System.out.print(r[i]);
		}
		System.out.print(r[1]);
	}
	
	
	public static void dfs(int t) {
		if(t > n) {
			if(a[x[n]][x[1]] != 0 && now + a[x[n]][x[1]] < min) {
				min = now + a[x[n]][x[1]];
				for(int i = 1;i <= n;i++) {
					r[i] = x[i];
				}
			}
		}else {
			for(int i = t;i <= n;i++) {
				if(a[x[t - 1]][x[i]] != 0 && now + a[x[t - 1]][x[i]] < min) {
					now += a[x[t - 1]][x[i]];
					swap(i,t);
					dfs(t + 1);
					swap(i,t);
					now -= a[x[t - 1]][x[i]];
				}
			}
		}
	}
	
	public static void swap(int i,int j) {
		int t = x[i];
		x[i] = x[j];
		x[j] = t;
	}
	
}

(3)批处理作业调度问题

https://www.cnblogs.com/ECJTUACM-873284962/p/8447050.html#_label3_3

#include<iostream>
using namespace std;
#define MAX 200
int* x1;//作业Ji在机器1上的工作时间
int* x2;//作业Ji在机器2上的工作时间
int number=0;//作业的数目
int* xorder;//作业顺序
int* bestorder;//最优的作业顺序
int bestvalue=MAX;//最优的时间
int xvalue=0;//当前完成用的时间
int f1=0;//机器1完成的时间
int* f2;//机器2完成的时间


void backtrack(int t)
{
    if(t>number)
    {
        if(xvalue < bestvalue){
            for(int i=1;i<=number;i++) bestorder[i]=xorder[i];
                bestvalue=xvalue;
        }
    }
    else
    {
        for(int i=t;i<=number;i++)
        {
           //1、判断
           f1+=x1[xorder[i]];
           //计算该作业总时间
           f2[t]=(f2[t-1]>f1?f2[t-1]:f1)+x2[xorder[i]];
           //算入总时间
           xvalue+=f2[t];
           if(xvalue<bestvalue) {
                //2、交换
                swap(xorder[i],xorder[t]);
                backtrack(t+1);
                swap(xorder[i],xorder[t]);
           }
           //恢复
           xvalue-=f2[t];
           f1-=x1[xorder[i]];
       }
    }
}

int main()
{
    cout<<"请输入作业数目:";
    cin>>number;
    x1=new int[number+1];
    x2=new int[number+1];
    xorder=new int[number+1];
    bestorder=new int[number+1];
    f2=new int[number+1];
    x1[0]=0;
    x2[0]=0;
    xorder[0]=0;
    bestorder[0]=0;
    f2[0]=0;
    cout<<"请输入每个作业在机器1上所用的时间:"<<endl;
    int i;
    for(i=1;i<=number;i++)
    {
        cout<<"第"<<i<<"个作业=";
        cin>>x1[i];
    }
    cout<<"请输入每个作业在机器2上所用的时间:"<<endl;
    for(i=1;i<=number;i++)
    {
           cout<<"第"<<i<<"个作业=";
           cin>>x2[i];
    }
    for(i=1;i<=number;i++) xorder[i]=i;
    backtrack(1);
    cout<<"最节省的时间为:"<<bestvalue<<endl;
    cout<<"对应的方案为:";
    for(i=1;i<=number;i++) cout<<bestorder[i]<<"  ";
    cout<<endl;
}

(4)圆排列问题???

https://www.cnblogs.com/ECJTUACM-873284962/p/8447050.html#_label3_6

代码中圆排列的圆心横坐标以第一个圆的圆心为原点。所以,总长度为第一个圆的半径+最后一个圆的半径+最后一个圆的横坐标。

#include<iostream>
#include<math.h>
using namespace std;
#define MAX 200

float minlen=10000,x[4],r[4];//当前最优值,当前圆排列圆心横坐标,当前圆排列
int n;//圆排列中圆的个数
//计算当前所选择圆的圆心横坐标
float center(int t)
{
    float temp=0;
    for(int j=1;j<t;j++)
    {
        //由x^2=sqrt((r1+r2)^2-(r1-r2)^2)推导而来
        float valuex=x[j]+2.0*sqrt(r[t]*r[j]);
        if(valuex>temp) temp=valuex;
    }
    return temp;
}
//计算当前圆排列的长度
void compute()
{
    float low=0,high=0;
    for(int i=1;i<=n;i++)
    {
        if(x[i]-r[i]<low) low=x[i]-r[i];
        if(x[i]+r[i]>high) high=x[i]+r[i];
    }
    if(high-low<minlen) minlen=high-low;
}
void backtrack(int t)
{
    if(t>n) compute();
    else
    {
        for(int j=t;j<=n;j++)
        {
            //为什么这样不可以??把计算放在前面了!
            //float centerx=center(t);
            //if(centerx+r[t]+r[1]<minlen)
            //{
                //swap(r[t],r[j]);
                //x[t]=centerx;
                //backtrack(t+1);
                //swap(r[t],r[j]);
            //}
            //1、判断
            if(center(t)+r[t]+r[1]<minlen)
            {   //2、交换
                swap(r[t],r[j]);
                //3、计算
                x[t]=center(t);
                backtrack(t+1);
                swap(r[t],r[j]);
            }
        }
    }
}
int main()
{
    n=3;
    r[1]=1,r[2]=1,r[3]=2;
    cout<<"各圆的半径分别为:"<<endl;
    for(int i=1;i<=3;i++) cout<<r[i]<<" ";
    cout<<endl;
    cout<<"最小圆排列长度为:";
    backtrack(1);
    cout<<minlen<<endl;
}


相关标签: 算法 剪枝 dfs

上一篇: 回溯法

下一篇: docker安装jumpserver