回溯法

问题的解空间

一个复杂问题的解决方案是由若干个小的决策步骤组成的决策序列,解决一个问题的所有可能的决策序列构成该问题的解空间。
应用回溯法求解问题时,首先应该明确问题的解空间。解空间中满足约束条件的决策序列称为可行解。
一般来说,解任何问题都有一个目标,在约束条件下使目标达到最优的可行解称为该问题的最优解。
问题的解由一个不等长或等长的解向量X={x1,x2,…,xn}组成,其中分量xi表示第i步的操作。
所有满足约束条件的解向量组构成了问题的解空间。
问题的解空间一般用树形式来组织,也称为解空间树或状态空间,树中的每一个结点确定所求解问题的一个问题状态。
树的根结点位于第1层,表示搜索的初始状态,第2层的结点表示对解向量的第一个分量做出选择后到达的状态,以此类推。 
在这里插入图片描述
求解问题类型:

  1. 找所有解
  2. 找最优解

解空间树通常有两种类型:
子集树:当所给的问题是从n个元素的集合S中找出满足某种性质的子集时,相应的解空间树称为子集树。
排列树:当所给的问题是确定n个元素满足某种性质的排列时,相应的解空间树称为排列树。

注意:问题的解空间树是虚拟的,并不需要在算法运行时构造一棵真正的树结构,然后再在该解空间树中搜索问题的解,而是只存储从根结点到当前结点的路径。
实际上,有些问题的解空间因过于复杂或状态过多难以画出来。

什么是回溯法

在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点(开始结点)出发搜索解空间树。
当从状态si搜索到状态si+1后,如果si+1变为死结点,则从状态si+1回退到si,再从si找其他可能的路径,所以回溯法体现出走不通就退回再走的思路。

回溯法搜索解空间时,通常采用两种策略避免无效搜索,提高回溯的搜索效率:

  1. 用约束函数扩展结点处剪除不满足约束的子树
  2. 用限界函数剪去得不到问题解或最优解的子树

这两类函数统称为剪枝函数

归纳起来,用回溯法解题的一般步骤如下:
确定问题的解空间树,问题的解空间树应至少包含问题的一个(最优)解。
确定结点的扩展规则。
以深度优先方式搜索解空间树,并在搜索过程中可以采用剪枝函数来避免无效搜索。

结论:回溯法=深度优先搜索+剪枝

首先根结点成为活结点(活结点是指自身已生成但其孩子结点没有全部生成的结点),同时也成为当前的扩展结点(扩展结点是指正在产生孩子结点的结点)。 
在当前的扩展结点处,搜索向纵深方向移至一个新结点。这个新结点就成为新的活结点,并成为当前扩展结点。
如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点(死结点是指由根结点到该结点构成的部分解不满足约束条件,或者其子结点已经搜索完毕)。
此时应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。
回溯法以这种方式递归地在解空间中搜索,直至找到所要求的解或解空间中已无活结点为止。

若用回溯法求问题的所有解时,需要回溯到根结点,且根结点的所有可行的子树都要已被搜索完才结束。而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。

回溯法的算法框架

非递归回溯框架

int x[n];				//x存放解向量,全局变量
void backtrack(int n)			//非递归框架
{  int i=1;				//根结点层次为1
   while (i>=1)			//尚未回溯到头
   {  if(ExistSubNode(t)) 		//当前结点存在子结点
      {  for (j=下界;j<=上界;j++)	//对于子集树,j=0到1循环
         {  x[i]取一个可能的值;
            if (constraint(i) && bound(i)) 
					//x[i]满足约束条件或界限函数
            {  if (x是一个可行解)
		   输出x;
               else	i++;		//进入下一层次
	     }
         }
      }
      else  i--;			//回溯:不存在子结点,返回上一层
   }
}

递归算法框架

解空间为子集树

int x[n];			   //x存放解向量,全局变量
void backtrack(int i)		   //求解子集树的递归框架
{  if(i>n)			   //搜索到叶子结点,输出一个可行解
      输出结果;
   else
   {  for (j=下界;j<=上界;j++)   //用j枚举i所有可能的路径
      {  x[i]=j;		   //产生一个可能的解分量//其他操作
         if (constraint(i) && bound(i))
            backtrack(i+1);	   //满足约束条件和限界函数,继续下一层
      }
   }
}

解空间为排列树

int x[n];			//x存放解向量,并初始化
void backtrack(int i)		//求解排列树的递归框架
{  if(i>n)			//搜索到叶子结点,输出一个可行解
	输出结果;
   else
   {  for (j=i;j<=n;j++)	//用j枚举i所有可能的路径
      {//第i层的结点选择x[j]的操作
         swap(x[i],x[j]);	//为保证排列中每个元素不同,通过交换来实现
         if (constraint(i) && bound(i))
	     backtrack(i+1);	//满足约束条件和限界函数,进入下一层
         swap(x[i],x[j]);	//恢复状态//第i层的结点选择x[j]的恢复操作
      }
   }
}

回溯法与深度优先遍历的异同

两者的相同点:
回溯法在实现上也是遵循深度优先的,即一步一步往前探索,而不像广度优先遍历那样,由近及远一片一片地搜索。
两者的不同点:
(1)访问序不同:深度优先遍历目的是“遍历”,本质是无序的。而回溯法目的是“求解过程”,本质是有序的。
(2)访问次数的不同:深度优先遍历对已经访问过的顶点不再访问,所有顶点仅访问一次。而回溯法中已经访问过的顶点可能再次访问。
(3)剪枝的不同:深度优先遍历不含剪枝,而很多回溯算法采用剪枝条件剪除不必要的分枝以提高效能。

回溯法算法的时间分析

通常以回溯算法的解空间树中的结点数作为算法的时间分析依据,假设解空间树共有n层。
第1层有m0个满足约束条件的结点,每个结点有m1个满足约束条件的结点;
第2层有m0m1个满足约束条件的结点,同理,第3层有m0m1m2个满足约束条件的结点。
第n层有m0m1…mn-1个满足约束条件的结点,则采用回溯法求所有解的算法的执行时间为 
T(n)=m0+m0m1+m0m1m2+…+m0m1m2…mn-1。
通常情况下,回溯法的效率会高于蛮力法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值