五大算法之回溯算法

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

回溯法解决的问题可以用树结构来描述,每个状态下都对应有n种选择。以全排列问题为例,如对[1, 2, 3]进行全排列,每次从1, 2, 3中选择了一个值后,下一次又可以从1, 2, 3中选择一个值,将这个过程绘制成一棵树,每个节点都有1, 2, 3三个子节点。通常对树的遍历为深度优先的方式,即先序遍历的方式,如果遍历整个树结构,那么时间复杂度将是指数级的,回溯法则通过一定的条件判断,当节点不满足条件时,就会回退到上一个节点,然后再开始下一次的遍历,从而对树进行剪枝,减少需要遍历的路径。如排列的结果无重复值,这一回溯条件可减去多余的路径,同时回溯法还有终止条件,如排列结果长度为3。

因此,解决一个回溯问题,实际上就是一个决策树的遍历过程。有四个要素需要考虑:

  • 当前路径:指的是已经做出的选择。
  • 选择列表:指的是当前所有可做的选择。
  • 回溯条件:指的是判断选择是否有效。
  • 终止条件:指的是到达决策树底层,无法再做选择的条件。

代码方面,回溯算法的框架如下,其核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」。

result = []
def backtrack(当前路径, 选择列表):
    if 满足终止条件:
        result.add(路径)
        return

for 选择 in 选择列表:
        if 选择满足回溯条件:
            做选择
            backtrack(当前路径, 选择列表)
            撤销选择

一、全排列问题

下面还是以全排列为例,在红色节点状态时,当前路径为[2],可选择的数字是[1,2,3],但是满足回溯条件即无重复值的只有[1,3],终止条件为全排列结果长度为3。

代码如下:

class Solution {
public:
    vector<vector<int>> res;
    vector<vector<int>> permute(vector<int>& nums) {
        vector<int> tmp;
        backtrack(tmp,nums);
        return res;
    }
    void backtrack(vector<int> tmp,vector<int> nums)
    {
        if(tmp.size() == nums.size())
        {
            res.push_back(tmp);
            return;
        }
        for(int i=0;i<nums.size();i++)
        {
            vector<int>::iterator iter;
            iter = std::find(tmp.begin(),tmp.end(),nums[i]);
            if(iter==tmp.end())
            {
                tmp.push_back(nums[i]);
                backtrack(tmp,nums);
                tmp.pop_back();
            }
        }
    }
};

至此,我们就通过全排列问题详解了回溯算法的底层原理。当然,这个算法解决全排列不是很高效,因为对vector使用find需要 O(N) 的时间复杂度。有更好的方法通过交换元素达到目的。但是必须说明的是,不管怎么优化,都符合回溯框架,而且时间复杂度都不可能低于O(N!),因为穷举整棵决策树是无法避免的。这也是回溯算法的一个特点,不像动态规划存在重叠子问题可以优化,回溯算法就是纯暴力穷举,复杂度一般都很高。

二、括号生成问题

给出n代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。例如,给出n = 3,生成结果为:["((()))", "(()())", "(())()", "()(())", "()()()"]。

合法括号组合满足一下两点条件:

  • 一个合法括号组合的左括号数一定等于右括号数,这个显而易见。
  • 一个合法括号组合生成过程中,左括号生成数一定大于等于右括号生成数。比如这个括号组合"))((",前几个子串都是右括号多于左括号,显然不是合法的括号组合。

根据上述的回溯法四个要素,我们逐个分析,对于红色节点,当前路径为“((”;可选择的数字是[‘(’, ‘)’];回溯条件为:左括号数小于等于n,右括号生成数小于等于左括号数;终止条件为:总括号生成数等于n*2。代码如下:

class Solution {
public:
    vector<string> res;
    int left = 0;
    int right = 0;

    vector<string> generateParenthesis(int n) {
        backtrace("",n);
        return res;
    }

    void backtrace(string tmp,int n)
    {
        if(tmp.size()==n*2)
        {
            res.push_back(tmp);
            return;
        }

        if(left < n)
        {
            tmp.push_back(‘(‘);
            left++;
            backtrace(tmp,n);
            tmp.pop_back();
            left--;
        }
        if(right<left)
        {
            tmp.push_back(‘)‘);
            right++;
            backtrace(tmp,n);
            tmp.pop_back();
            right--;
        }
    }
};

原文地址:https://www.cnblogs.com/BobPong/p/12663204.html

时间: 2024-11-02 18:48:17

五大算法之回溯算法的相关文章

五大常用算法----贪心、动态规划、分支限界、分治算法和回溯算法

五大常用算法之一:贪心算法 所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择.也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解. 贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择.必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关. 所以对所采用的贪心策略一定要仔细分析其是否满足无后效性. 五大常用算法之二:动态规划算法 五大常用算法之三:分支限界算法

【八皇后问题】 回溯算法

回溯算法:回溯算法实际上是一个类似枚举的搜索尝试方法,它的思想是在搜索尝试中寻找问题的解,当发现不满足求解条件时,就“回溯”返回,尝试别的路径.之前介绍的基础算法中的贪婪算法,动态规划等都具有“无后效性”,也就是在分段处理问题时,某状态一旦确定,将不再改变.而多数问题很难找到"无后效性”的阶段划分和相应决策,而是通过深入搜索尝试和回溯操作完成的. 八皇后问题:8*8的国际象棋棋盘中放八个皇后,是任意两个皇后不能互相吃掉.规则:皇后能吃掉同一行,同一列,同一对角线的任意棋子. 模型建立:不妨设八个

回溯算法入门及经典案例剖析(初学者必备宝典)

前言 基于有需必写的原则,并且当前这个目录下的文章数量为0(都是因为我懒QAQ),作为开局第一篇文章,为初学者的入门文章,自然要把该说明的东西说明清楚,于是...我整理了如下这篇文章,作者水平有限,有不足之处还望大家多多指出~~~ 概念 首先,回溯是什么意思?很多初学者都会问这样的一个问题.我们可以举这样一个例子: 1 1 1 1 0 1 0 1 0 1 0 1 0 1 1 1 我们看到了如图所示的一个4*4的迷宫了,我们假设数字1标记的位置为道路,数字0标记的位置为一堵墙,一个人由起点(0.0

五大经典算法之回溯法

一.基本概念 ??回溯法,又称为试探法,按选优条件向前不断搜索,以达到目标.但是当探索到某一步时,如果发现原先选择并不优或达不到目标,就会退回一步重新选择,这种达不到目的就退回再走的算法称为回溯法. 与穷举法的区别和联系: 相同点:它们都是基于试探的. 区别:穷举法要将一个解的各个部分全部生成后,才检查是否满足条件,若不满足,则直接放弃该完整解,然后再尝试另一个可能的完整解,它并没有沿着一个可能的完整解的各个部分逐步回退生成解的过程.而对于回溯法,一个解的各个部分是逐步生成的,当发现当前生成的某

8皇后以及N皇后算法探究,回溯算法的JAVA实现,非递归,循环控制及其优化

上两篇博客 8皇后以及N皇后算法探究,回溯算法的JAVA实现,递归方案 8皇后以及N皇后算法探究,回溯算法的JAVA实现,非递归,数据结构“栈”实现 研究了递归方法实现回溯,解决N皇后问题,下面我们来探讨一下非递归方案 实验结果令人还是有些失望,原来非递归方案的性能并不比递归方案性能高 代码如下: package com.newflypig.eightqueen; import java.util.Date; /** * 使用循环控制来实现回溯,解决N皇后 * @author [email pr

回溯0--递归回溯算法框架

递归回溯算法框架 一.心得 3 都是在for下的if下的 4 保存结果,找下一步,回溯,这三个是一起的 5 还有一个到达目的地输出解放在外面就好 search后面的k是轮数  三个数组:原数据数组标记数组储存结果数组 框架二 到目的地的情况要多加1,因为满足了的下一轮就好判断 二.代码 1 /* 2 递归回溯算法框架: 3 都是在for下的if下的 4 保存结果,找下一步,回溯,这三个是一起的 5 还有一个到达目的地输出解放在外面就好 6 */ 7 /* 8 框架一 9 */ 10 int se

回溯算法解八皇后问题(java版)

八皇后问题是学习回溯算法时不得不提的一个问题,用回溯算法解决该问题逻辑比较简单. 下面用java版的回溯算法来解决八皇后问题. 八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例.该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同一列或同一斜线上,问有多少种摆法. 思路是按行来规定皇后,第一行放第一个皇后,第二行放第二个,然后通过遍历所有列,来判断下一个皇后能否放在该列.直到所有皇后都放完,或者放哪

数据结构之回溯算法

借鉴:http://blog.csdn.net/baple/article/details/7181404 package database; public class NQuee { public static boolean Verify(int arr[],int i){                              //仅仅判断能不能放置这个皇后        for(int k =1;k != i;k++)                                  

五大算法思想—贪心算法

贪心法理解 贪心法在解决问题的策略上目光短浅,只根据当前已有的信息就做出选择,而且一旦做出了选择,不管将来有什么结果,这个选择都不会改变.换言之,贪心法并不是从整体最优考虑,它所做出的选择只是在某种意义上的局部最优. 一句话:不求最优,只求可行解. 判断贪心法 对于一个具体的问题,怎么知道是否可用贪心算法解此问题,以及能否得到问题的最优解? 我们可以根据贪心法的2个重要的性质去证明:贪心选择性质和最优子结构性质. 1.贪心选择性质 什么叫贪心选择?从字义上就是贪心也就是目光短线,贪图眼前利益,在