【算法基础】回溯(Backtracking)

本文详细介绍了回溯算法的概念、递归和堆栈实现方式,通过LeetCode示例展示了如何在电话号码转字母组合和二叉树前序遍历中应用回溯。回溯虽非高效,但它是解决树形结构问题的经典方法。
摘要由CSDN通过智能技术生成

1 定义

回溯是递归的一种形式,同时也是递归的副产品,也就是说只要有递归就会有回溯。在遇到必须从多种选项中选择一项的时候,你选择了一项后又会出现多种选项供选择,直到选择到了“正确的选项”函数才结束。因此,回溯法解决的问题都可以抽象为树形结构(不一定是二叉树)。

回溯虽然是一种非常典型的递归算法,但是任何递归算法都可以重写为堆栈算法。事实上,这就是递归算法被翻译成机器或汇编语言的方式。

2 算法思想

回溯法就是暴力搜索,并不是什么高效的算法,最多再剪枝一下(dfs的优化)。因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度,都构成的树的深度。因为用到了递归,是递归就必须要有终止条件(basecase),在树状结构中,这棵树(N叉树)的高度必须是有限制。

在此,我引入“原问题”和“子问题”这两个概念来帮助大家理解回溯。题目要求解决的问题,可以看成是一个原问题。而在这整个原问题中,其中的一个选项就可以看作是一个子问题,如此循环往复:该子问题本身,又可以看成是一个新的原问题。

3 代码模板

3.1 递归实现

void trackback(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        trackback(路径,选择列表); // 递归
        pop//回溯,撤销处理结果
    }
}

在这其中,这个for循环本身是一个横向遍历,也就是说一个根节点的下一层有多少个子节点,这个循环就会执行多少次;for循环中的trackback函数调用就是一个递归的过程,是一个纵向遍历,也就是说这棵树有多少层,这个函数就会递归多少次。pop的这个操作就是回溯。

3.2 栈实现

bool solve(Node& n) {
    创建一个栈stack;
    将节点n压入stack;
    while(!stack.empty())  {
        if 栈顶部是叶子节点 {
            if(该节点是目标节点) return true;
            else stack.pop();
        }
        else {
            if 栈顶部的节点有未尝试的子节点
                将下一个未尝试的子节点推到栈上;
            else 把它从栈顶弹出;

    }
    return false;
}

4 例题

4.1 例题1

【leetcode-17】给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

4.1.1 题目分析

首先使用哈希表存储每个数字对应的所有可能的字母,然后进行回溯操作。

回溯过程中维护一个字符串,表示已有的字母排列(如果未遍历完电话号码的所有数字,则已有的字母排列是不完整的)。该字符串初始为空。每次取电话号码的一位数字,从哈希表中获得该数字对应的所有可能的字母,并将其中的一个字母插入到已有的字母排列后面,然后继续处理电话号码的后一位数字,直到处理完电话号码中的所有数字,即得到一个完整的字母排列。然后进行回退操作,遍历其余的字母排列。

回溯算法用于寻找所有的可行解,如果发现一个解不可行,则会舍弃不可行的解。在这道题中,由于每个数字对应的每个字母都可能进入字母组合,因此不存在不可行的解,直接穷举所有的解即可。

4.1.2 代码实现

class Solution {
public:
    vector<string> letterCombinations(string digits) {
        vector<string> combinations;
        if(digits.empty()){
            return combinations;
        }
        unordered_map<char,string> phoneMap{
            {'2', "abc"},
            {'3', "def"},
            {'4', "ghi"},
            {'5', "jkl"},
            {'6', "mno"},
            {'7', "pqrs"},
            {'8', "tuv"},
            {'9', "wxyz"}
        };
        string combination;
        traceback(combinations,combination,0,phoneMap,digits);
        return combinations;
    }
    void traceback(vector<string>& combinations,string& combination,int i,unordered_map<char,string>& phoneMap,string& digits){
        if(i == digits.size()){
            combinations.push_back(combination);
            return;
        }else{
            char digit = digits[i];
            auto letters = phoneMap[digit];
            for(auto letter:letters){
                combination.push_back(letter);
                traceback(combinations,combination,i+1,phoneMap,digits);
                combination.pop_back();
            }
        }
    }
};

4.2 例题2

【leetcode-144】给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

4.2.1 题目分析

这道题可以用递归暴力获取答案,但是,这里给出一种用栈实现的更为巧妙的解法,其结构与bfs中的基本模板有些类似。只不过,在栈中,数据履行“先进后出”的原则,使用在将子节点压入栈是,要先压右节点,后压左节点。

4.2.2 代码实现

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> ans;
        if(!root){
            return ans;
        }
        stack<TreeNode*> s;
        s.push(root);
        while(!s.empty()){
            auto node = s.top();
            s.pop();
            ans.push_back(node->val);
            if(node->right){
                s.push(node->right);
            }
            if(node->left){
                s.push(node->left);
            }
        }
        return ans;
    }
};

  • 30
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
回溯线搜索(backtracking line search)是一种用于优化算法中的搜索技术。其基本思想是在搜索方向上进行一系列的试探,以找到一个满足一定条件的可接受步长。具体来说,假设我们在某一点 $x$ 处进行优化,搜索方向为 $d$,则回溯线搜索的过程如下: 1. 选择一个初始步长 $\alpha_0>0$,一般可以选择较小的值,比如 $\alpha_0=1$ 或 $\alpha_0=0.1$; 2. 在每一次迭代中,计算 $f(x+\alpha_k d)$,其中 $f$ 是待优化的目标函数,$k$ 表示当前的迭代次数; 3. 如果 $f(x+\alpha_k d) \leq f(x) + c_1 \alpha_k \nabla f(x)^T d$,其中 $c_1 \in (0,1)$ 是一个预回溯直线搜索(backtracking line search)是一种用于优化算法的技术,特别是用于求解无约束非线性优化问题。在每次迭代中,回溯直线搜索算法会尝试在当前搜索方向上找到一个满足一定条件的步长,使得在该步长下目标函数值可以得到显著的改善。 具体来说,回溯直线搜索算法在每次迭代中按照当前搜索方向移动一定步长,然后检查目标函数是否得到了改善。如果目标函数得到了改善,则接受该步长并继续迭代;否则,将步长缩小一定比例(通常是折半),并重复该过程,直到找到一个满足条件的步长。 回溯直线搜索算法的优点是可以在无约束优化问题中进行全局搜索,因为它可以通过改变搜索方向和步长来遍历整个搜索空间。然而,回溯直线搜索算法的缺点是它可能需要进行大量的迭代才能找到最优解,因此在实际应用中,通常需要与其他优化算法结合使用,以便在更短的时间内找到最优解。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值