数据结构(六)—— 二叉树(4)回溯

文章目录

  • 一、题
  • 1 257 二叉树的所有路径
    • 1.1 最好理解的回溯写法
    • 1.2 写法2
    • 1.3 写法3
    • 1.4 错误示例


一、题

1 257 二叉树的所有路径

最关键的退出条件:判断遍历到了叶子节点

1.1 最好理解的回溯写法

为什么要用到一个int数组来存path,是因为如果当前root->val是负数或者大于10的数,算多个字符串,很难去回溯

vector<int> path;
vector<string> res;

void dfs(TreeNode* root){
    path.push_back(root->val);
        // 到了叶子节点
    if(root->left == NULL && root->right == NULL){
        string temp;
        for(int i = 0; i < path.size(); ++i) {
            temp += to_string(path[i]);
            temp += "->";
        }
        temp.pop_back();temp.pop_back();
        res.push_back(temp);
        return;
    }
        
    if(root->left) {
        dfs(root->left);
        path.pop_back();
    }
    if(root->right) {
        dfs(root->right);
        path.pop_back();
    }
}

vector<string> binaryTreePaths(TreeNode* root) {
    dfs(root);
    return res;
}

在写法3中,隐藏了一个path的回溯,是因为他把path作为参数传进了递归函数中,代码如下所示:

void traversal(TreeNode* cur, string path, vector<string>& result) {
    path += to_string(cur->val); // 中
    if (cur->left == NULL && cur->right == NULL) {
        result.push_back(path);
        return;
    }
    if (cur->left) traversal(cur->left, path + "->", result); // 左
    if (cur->right) traversal(cur->right, path + "->", result); // 右
}

vector<string> binaryTreePaths(TreeNode* root) {
    vector<string> result;
    string path;
    if (root == NULL) return result;
    traversal(root, path, result);
    return result;
}

1.2 写法2

递归+回溯:回溯是递归的副产品,只要有递归就会有回溯

首先考虑深度优先搜索;而题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。
数据结构(六)—— 二叉树(4)回溯_第1张图片
递归和回溯就是一家的,本题也需要回溯。

1、确定递归函数输入输出
要传入根节点,记录每一条路径的vector&,和存放结果集的vector&,这里递归不需要返回值,
void traversal(TreeNode* cur, vector& path, vector& result)
2、确定递归终止条件
一般来说都是if(cur == NULL) return,但是本题要找到叶子节点,就开始结束的处理逻辑了(把路径放进result里)。
那么什么时候算是找到了叶子节点? 是当 cur不为空,其左右孩子都为空的时候,就找到叶子节点。

if (cur->left == NULL && cur->right == NULL) { // 遇到叶子节点
    string sPath;
    for (int i = 0; i < path.size() - 1; i++) { // 将path里记录的路径转为string格式
        sPath += to_string(path[i]);
        sPath += "->";
    }
    sPath += to_string(path[path.size() - 1]); // 记录最后一个节点(叶子节点)
    result.push_back(sPath); // 收集一个路径
    return;
}

3、确定单层递归逻辑
因为是前序遍历,需要先处理中间节点,中间节点就是我们要记录路径上的节点,先放进path中。
path.push_back(cur->val);

然后是递归和回溯的过程,上面说过没有判断cur是否为空,那么在这里递归的时候,如果为空就不进行下一层递归了。
所以递归前要加上判断语句,下面要递归的节点是否为空,如下
if (cur->left) traversal(cur->left, path, result);
此时还没完,递归完,要做回溯啊,因为path 不能一直加入节点,它还要删节点,然后才能加入新的节点。

if (cur->left) {
    traversal(cur->left, path, result);
    path.pop_back(); // 回溯
}
if (cur->right) {
    traversal(cur->right, path, result);
    path.pop_back(); // 回溯
}

4、整合traversal()

class Solution {
private:

    void traversal(TreeNode* cur, vector<int>& path, vector<string>& result) {
        path.push_back(cur->val); // 中,中为什么写在这里,因为最后一个节点也要加入到path中 
        // 这才到了叶子节点
        if (cur->left == NULL && cur->right == NULL) {
            string sPath;
            for (int i = 0; i < path.size() - 1; i++) {
                sPath += to_string(path[i]);
                sPath += "->";
            }
            sPath += to_string(path[path.size() - 1]);
            result.push_back(sPath);
            return;
        }
        if (cur->left) { // 左 
            traversal(cur->left, path, result);
            path.pop_back(); // 回溯
        }
        if (cur->right) { // 右
            traversal(cur->right, path, result);
            path.pop_back(); // 回溯
        }
    }

public:
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> result;
        vector<int> path;
        if (root == NULL) return result;
        traversal(root, path, result);
        return result;
    }
};

1.3 写法3

1、确定输入输出
输入:节点、每条路径string、每条路径组成的vector&
输出:空
void traversal(TreeNode* cur, string path, vector& result)

注意:函数输入定义的是string,每次都是复制赋值,没使用引用,否则就无法做到回溯的效果。(这里涉及到C++语法知识)

2、确定退出条件

if (cur->left == NULL && cur->right == NULL) {
	result.push_back(path);
	return;
}

3、确定单层逻辑
中左右

path += to_string(cur->val); // 中
...  // 退出条件
if (cur->left) traversal(cur->left, path + "->", result); // 左
if (cur->right) traversal(cur->right, path + "->", result); // 右

4、整合

class Solution {
private:

    void traversal(TreeNode* cur, string path, vector<string>& result) {
        path += to_string(cur->val); // 中
        if (cur->left == NULL && cur->right == NULL) {
            result.push_back(path);
            return;
        }
        if (cur->left) traversal(cur->left, path + "->", result); // 左
        if (cur->right) traversal(cur->right, path + "->", result); // 右
    }

public:
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> result;
        string path;
        if (root == NULL) return result;
        traversal(root, path, result);
        return result;

    }
};

在哪儿回溯的?回溯隐藏在traversal(cur->left, path + "->", result);中的 path + "->"。 每次函数调用完,path并没有加上"->",这就是回溯了。

使用如下代码可以更好的体会到回溯

if (cur->left) {
    path += "->";
    traversal(cur->left, path, result); // 左
    path.pop_back(); // 回溯 '>'
    path.pop_back(); // 回溯 '-'
}
if (cur->right) {
    path += "->";
    traversal(cur->right, path, result); // 右
    path.pop_back(); // 回溯 '>' 
    path.pop_back(); //  回溯 '-' 
}

1.4 错误示例

void traversal(TreeNode* root, string path, vector<string>& res) {
        path += to_string(root->val);
        if(root->left == nullptr && root->right == nullptr){
            res.push_back(path);
            return;
        }
        if(root->left != nullptr){
            // path += "->";
            traversal(root->left, path, res);
        }
        if(root->right != nullptr){
            // path += "->";
            traversal(root->right, path, res);
        }
}

vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> res;
        string path;
        traversal(root, path, res);
        return res;
}

现输入如下
数据结构(六)—— 二叉树(4)回溯_第2张图片
1、如果不加上->,结果如下所示:
数据结构(六)—— 二叉树(4)回溯_第3张图片
2、如果取消注释,结果如下所示:
数据结构(六)—— 二叉树(4)回溯_第4张图片
->没有被pop掉

你可能感兴趣的:(数据结构,算法,深度优先)