算法(三)回溯法

一、概述

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。

img

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背包问题

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

img

#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个进行

特别适用于排序 序列中

img

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;
}


  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值