回溯法算法学习

回溯法算法学习

回溯算法的定义

回溯法(back tracking)(探索与回溯法)是一种选优搜索法,又称为试探法,有“通用的解题法”之称,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回到上一步,重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。

回溯算法的思想

回溯法是一种试探性的算法,一边搜索带入值,一边判断被带入值的合法性,如果合法就在此基础上进一步搜索带入值,验证合法性,直到求出解。本质是搜索树,解是由根节点到叶子结点的路径,表现为n维向量。
在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。由于在搜索过程中,放弃了一些没有必要搜索的结点,整个算法的效率就提高了.

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

图的着色搜索树为例,1、2、3代表红、蓝、黑三种颜色,虚线代表结点不能取色的颜色:
在这里插入图片描述

回溯算法的使用条件

回溯法需要满足多米诺性质,该性质证明了回溯法的合理性,是回溯法不丢解的依据。
多米诺性质:
在这里插入图片描述
如果当前结点不满足约束条件,能够推导出它的子结点也不满足约束条件。即解的前k个分量不满足条件时,那么前k+1个分量也不满足条件,于是我们可以在选择第k个分量时停止其第k+1个分量的探索。考察多米诺性质能够保证回溯时不会丢解。
例子:
在这里插入图片描述

回溯算法的实现方式

递归实现

思路简单,设计容易,但效率低,其设计范式如下:

//针对N叉树的递归回溯方法  
void backtrack(int t) {
	if (t > n) output(x); //叶子节点,输出结果,x是可行解  
	else
		for i = 1 to k //当前节点的所有子节点  
	{
		x[t] = value(i); //每个子节点的值赋值给x  
		//满足约束条件和限界条件  
		if (constraint(t) && bound(t))
			backtrack(t + 1); //递归下一层  
	}
}

迭代实现

算法设计相对复杂,但效率高。

//针对N叉树的迭代回溯方法
void iterativeBacktrack() {
	int t = 1;
	while (t > 0) {
		if (ExistSubNode(t)) //当前节点的存在子节点
		{
			for i = 1 to k //遍历当前节点的所有子节点
			{
				x[t] = value(i); //每个子节点的值赋值给x
				if (constraint(t) && bound(t)) //满足约束条件和限界条件
				{
					//solution表示在节点t处得到了一个解
					if (solution(t)) output(x); //得到问题的一个可行解,输出
					else t++; //没有得到解,继续向下搜索
				}
			}
		} else //不存在子节点,返回上一层
		{
			t--;
		}
	}
}

回溯算法解决的问题

问题描述:对于一组各不相同的数字,从中任意抽取1-n个数字,构成一个新的集合。
求出所有的可能的集合。例如,对于集合{1,2,3},其所有子集为{1},{2},{3},{1,2},{1,3},{2,3}{1,2,3},
给定一个数组(元素各不相同),求出数组的元素的所有非空组合(即数组的所有非空子集)
解法:位向量法。用一个辅助数组表示各个元素的状态。1表示在集合中,0表示不在数组中。递归地求解所有的子集。
搜索树:
在这里插入图片描述
算法描述如下://这里的算法对空集也输出了,可修改之使得只输出非空集合。
下面代码分别用递归回溯和迭代回溯的方法实现了算法

#include <iostream>
using namespace std;
//递归实现
void getSubset(int list[],bool v[],int a,int b){
  if(a == b){
    for(int i = 0; i < b; i++){
      if(v[i])
      cout<<list[i]<<"  ";
    }
    cout<<endl;
    return;
  }
  v[a] = true;
  getSubset(list,v,a+1,b);
  v[a] = false;
  getSubset(list,v,a+1,b);
 
}
//迭代实现
void getSubset_iterative(int list[],int n){
  int t = 0;//t 代表解空间树的深度
  bool v[n+1] = {false,false,false,false};
  int init[n+1] = {0,0};
  while( t >= 0){
          if(2 > init[t]){
            init[t]++;
            v[t] = !v[t];
            if( t >= n ){
              for(int i = 0; i <=n; i++){
                if(v[i])  cout<<list[i]<<"  ";
              }
              cout<<endl;
            }
            else{
              ++t;//进入下一层
            } 
          }
          else{
            //回溯至上一层,并将该层的状态重置,一定要重置
            init[t] = 0;
            --t;
          }    
  } 
}
int main(){
    int li[] = {1,2,3,4};
    bool v[] = { false,false,false,false};
    getSubset(li,v,0,4);
    getSubset_iterative(li,3);
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值