回溯法

    *回溯法(探索与回溯法)是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为回溯点
   *回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解:如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。
   *若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。
一、回溯法的算法框架
    1、问题的解空间     

     复杂问题常常有很多的可能解,这些可能解构成了问题的解空间。解空间也就是进行穷举的搜索空间,所以,解空间中应该包括所有的可能解。确定正确的解空间很重要,如果没有确定正确的解空间就开始搜索,可能会增加很多重复解,或者根本就搜索不到正确的解。

    例如,对于有n个物品的0/1背包问题,当n=3时,其解空间是:

{(0,0, 0), (0, 0, 1), (0, 1, 0), (1, 0, 0), (0, 1, 1), (1, 0, 1), (1, 1, 0), (1, 1,1) }

    2、回溯法的基本思想    

    回溯法的基本步骤

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

(2)确定易于搜索的解空间结构(一般有三种结构,组合树,排列树,n叉树);

(3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。

    常用剪枝函数:

约束函数(constraint)在扩展结点处剪去不满足约束的子树;

限界函数(bound)剪去得不到最优解的子树。

    需要注意的是,问题的解空间树是虚拟的,并不需要在算法运行时构造一棵真正的树结构,只需要存储从根结点到当前结点的路径。

例如,对于n=30/1背包问题,三个物品的重量为{20, 15, 10},价值为{20, 30, 25},背包容量为25。其解空间树如下,其解空间树中的8个叶子结点分别代表该问题的8个可能解:

   生成问题状态的基本方法   
*扩展结点:一个正在产生儿子的结点称为扩展结点。
*活结点:一个自身已生成但其儿子还没有全部生成的节点称做活结点。
*死结点:一个所有儿子已经产生的结点称做死结点。
*深度优先的问题状态生成法:如果对一个扩展结点R,一旦产生了它的一个儿子C,就把C当做新的扩展结点。在完成对子树C(以C为根的子树)的穷尽搜索之后,将R重新变成扩展结点,继续生成R的下一个儿子(如果存在)。
*回溯法:为了避免生成那些不可能产生最佳解的问题状态,要不断地利用限界函数(boundingfunction)来处死那些实际上不可能产生所需解的活结点,以减少问题的计算量。
3.递归回溯

回溯法对解空间作深度优先搜索,因此,在一般情况下用递归方法实现回溯法。

//t表示递归深度,该函数用于对第t层的某个节点搜索其所有子树
void backtrack (int t)
{
   if (t>n) output(x); //算法已搜索到某个叶节点,输出该搜索结果
   else
     for (int i=f(n,t);i<=g(n,t);i++) 
	 //f(n,t), g(n,t)分别表示拓展节点的子树的起始编号和终止编号 
     {  
	 	x[t]=h(i);  //h(i)表示第i个可选值 
        if (constraint(t)&&bound(t)) backtrack(t+1); 
        //constraint(t),bound(t)分别表示约束函数和限界函数 
     }
}
4.子集树与排列树

在用回溯法求解问题时,常常遇到两种典型的解空间树:

子集树:当所给的问题是从n个元素的集合S找出满足某种性质的子集时,相应的解空间树成为子集树。例:01背包问题。

 排列树:当所给问题是确定 n 个元素的满足某种性质的排列 时,相应的解空间树称为排列树。例:旅行售货员问题。

遍历子集树需O(2n)计算时间 

void backtrack (int t)
{
  if (t>n) output(x);
  else {
       x[t]=1;
       if (legal(t)) backtrack(t+1);
       x[t]=0;
       if (legal(t)) backtrack(t+1);
}


遍历排列树需要O(n!)计算时间 

void backtrack (int t)
{
  if (t>n) output(x);
  else
      for (int i=t;i<=n;i++) {
           if (legal(i)) {
               swap(x[t], x[i]);
               backtrack(t+1);
               swap(x[t], x[i]);  }
      }
} 


二、例子解析

1、  0-1背包问题

  问题描述 : 01背包是在M件物品取出若干件放在空间为C的背包里,每件物品的体积为W1,W2……Wn,与之相对应的价值为P1,P2……Pn。求出获得最大价值的方案,注意在本题中所有的体积值均为整数。

  第一步:找出问题的解空间,共有 2 ^n种方案。

  第二步:问题的解空间结构为组合树。

  第三步:找到约束函数以及限界函数。本问题明显的约束函数为,装入物品的体积∑Wi <= C, 而限界函数可以设计判断当前路径能否产生更优的解。(按照价值重量比从大到小排序,将物品装入背包。如果产生的价值更大,则有可能产生最优解)

以下为本人写的JAVA代码,不保证最好。

package OJ;

import java.util.*;

/**
 * input: 5 n 2 6 2 3 6 5 5 4 4 6 10 c output: 15
 * 
 */

// 0-1背包问题,回溯法求解
public class ZeroOnePackage {
	static int n; // 物品个数
	static int c; // 背包容量
	static Thing[] things; // 物品
	static int bestp; // 最大价值量
	static int cp; // 当前价值量
	static int cw; // 当前背包容量
	static int[] x; // 表示搜索路径
	static class Thing {
		int w; // 重量
		int p; // 价值
		float ratio; // 价值重量比,即单位重量所占价值
		public Thing(int w, int p, float ratio) {
			this.w = w;
			this.p = p;
			this.ratio = ratio;
		}
	}

	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		n = input.nextInt();		
		x = new int[n + 1];
		things = new Thing[n + 1];
		int tmpw; 
		int tmpp;
		float r = 0;
		for (int i = 1; i <= n; ++i) {
			tmpw = input.nextInt();
			tmpp = input.nextInt();
			r = (float) (tmpp) / tmpw;
			things[i] = new Thing(tmpp, tmpw, r);
		}
		c = input.nextInt();
		Arrays.sort(things, 1, n + 1, new Comparator() {

			@Override
			public int compare(Object o1, Object o2) {
				// 从大到小排列
				Thing t1 = (Thing) o1;
				Thing t2 = (Thing) o2;
				if (t1.ratio < t2.ratio)
					return 1;
				else if (t1.ratio > t2.ratio)
					return -1;
				else
					return 0;
			}
		});
		backtrack(1);
		System.out.println(bestp);
	}

	public static void backtrack(int t) {
		if (t > n) {
			if (bestp < cp)
				bestp = cp;
		} else {
			if (cw + things[t].w <= c) {
				x[t] = 1;
				cw += things[t].w;
				cp += things[t].p;
				backtrack(t + 1);
				cw -= things[t].w;
				cp -= things[t].p;
				x[t] = 0;
			}
			if (bestp < bound(t + 1)) 
				backtrack(t + 1);
		}
	}

	public static int bound(int k) {
		int cleft = c - cw;
		int curValue = cp;
		while (k <= n && things[k].w <= cleft) {
			cleft -= things[k].w;
			curValue += things[k].p;
			k++;
		}
		if (k <= n)
			curValue += things[k].p * cleft / things[k].w;
		return curValue;
	}

}


 

2、  旅行售货商问题 (TSP)

待补充


3、  n皇后问题

待补充


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值