算法学习(一)回溯法

1、概念

      回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。

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

     许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。

2、基本思想

   在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。

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

       而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。

3、用回溯法解题的一般步骤:

    (1)针对所给问题,确定问题的解空间:首先应明确定义问题的解空间,问题的解空间应至少包含问题的一个(最优)解。

    (2)确定结点的扩展搜索规则

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

4、基本方法

1.递归

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

2.递推

//针对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--;
		}
	}
}

5、几种问题类型

1、子集树

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

如0-1背包问题,从所给重量、价值不同的物品中挑选几个物品放入背包,使得在满足背包不超重的情况下,背包内物品价值最大。它的解空间就是一个典型的子集树。

此类题型基本范式:

void backtrack (int t)
{
  if (t == n) output(x);  //输出结果
    else
      for (int i = 0;i <= n;i++) {
        if (constraint(t)&&bound(t)) { //边界条件与问题限制条件
                x[t] = i;         //(t,i)处确定值
                backtrack(t+1);   //探测下一层
             }
      }
}

LeetCode例题:子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

输入: nums = [1,2,3]
输出:
[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]
class Solution {
    List<List<Integer>> listAll = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        if (nums.length == 0) { return listAll; }
        helper(nums,0,new ArrayList<>());    //从0个数开始直到n个数的排列
        return listAll;
    }
    public void helper(int[] nums,int k,List<Integer> list) {
        listAll.add(new ArrayList<>(list));         //记录K个数时的结果
        for (int j = k;j < nums.length;j++) {  
            list.add(nums[j]);
            helper(nums,j+1,list);          //下一层子树
            list.remove(list.size()-1);//回溯到上一层的状态
        }
    }
}

 

2、排列树

所给的问题是确定n个元素满足某种性质的排列时,相应的解空间就是排列树。

LeetCode例题:括号生成

给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。

例如,给出 n = 3,生成结果为:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]
class Solution {
    List<String> list = new ArrayList<>();
    public List<String> generateParenthesis(int n) {
        helper("",0,0,n);
        return list;
    }
    
    void helper(String s,int l,int r,int n) {
        if (s.length() == 2*n) {  //用完所有括号,返回结果
            list.add(s);
            return;
        }
        if (l < n) { helper(s+"(",l+1,r,n); }  //左括号小于总数,可以放左括号
        if (r < l) { helper(s+")",l,r+1,n); }  //右括号小于左括号数,可以放右括号
    }
}

此类问题为排列树问题,即n个元素的全排列的满足条件子集,此类问题范式如下

void backtrack (int t)
{
  if (t>n) output(x);
    else
      for (int i=t;i<=n;i++) {
        swap(x[t], x[i]);
        if (constraint(t)&&bound(t)) backtrack(t+1);
        swap(x[t], x[i]);        //回溯排列到上一层的位置
      }
} 

3、八皇后

问题描述:八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。

约束条件:

                  1. a[0] ~ a[7]不能相同,存储棋子所在的列

                  2. 同一对角线上即坐标斜率为1,即(i,j)和(k,l)的位置,当|i-k|=|j-l| 时,两个皇后才在同一条对角线上。

递归回溯:

  int[] a = new int[n];  //存储皇后位置的数组,下标为行,值位列
  void Queen(int k,int n) {
       if (k == n) {   // k从0开始,到达了最后一行n-1之后
           System.out.println(Arrays.toString(a)); //打印输出
           return;
       }
       for (int i = 0;i < n;i++) {    //k行的每一列都有可能放置皇后
           if (isConflict(k,i)) {     //判断此处的K行,i列能否满足放置要求
               a[k] = i;           
               Queen(k+1,n);          //(k,i)处放置完成后,进行下一行的探测
           }
       }
   }
   boolean isConflict(int k,int i) {    //判断函数,扫描0到k-1行中是否有根(k,i)冲突
        for (int j = 0;j < k;j++) {
            if (a[j] == i || Math.abs(a[j]-i) == k-j) { //同列或者同一斜线
                return false;
            }
        }
        return true;
   }

各种大神解法:https://blog.csdn.net/hacker00011000/article/details/51582300

 

 

 

 

 

参考文章:https://blog.csdn.net/JarvisChu/article/details/16067319

https://www.cnblogs.com/steven_oyj/archive/2010/05/22/1741376.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值