算法训练营|DAY14|树的递归遍历和迭代遍历

这一期讲的是关于树这个数据结构的专题了。

首先我们要明白树的两种通用遍历分别是深度优先搜索,和广度优先搜索。这里我们介绍深度优先搜索的三种表现形式:前序遍历,中序遍历和后序遍历。这三种搜索方式可以用递归法或者迭代法表示出来。事实上,很多递归能写出来的代码,大都可以使用迭代法表示出来。

递归法很简单,我们来看代码分析

首先是前序遍历

class Solution {
public:
vectorresult;
void dfs(TreeNode* root){
    if(root)
    result.push_back(root->val);
    if(root)
    dfs(root->left);
    if(root)
    dfs(root->right);
}
    vector preorderTraversal(TreeNode* root) {
        if(root==nullptr)
        return result;
        dfs(root);
        return result;
    }
};

前序遍历的规则是“中左右“,即先遍历树的中间节点,再分别遍历左右两子树,并在其遍历左右两子树时仍然遵循此规则。所以我们可以容易的理解dfs代码部分先将中间节点保存后,分别进行左子树和右子树的递归。

中序遍历和后序遍历的递归代码,都和前序遍历差不多,只是略微调整一下进入子树的时机而已,下面直接给出代码。

中序遍历

class Solution {
public:
void dfs(TreeNode* root,vector& result){
    if(root==nullptr)return ;
    dfs(root->left,result);
    result.push_back(root->val);
    dfs(root->right,result);
}
    vector inorderTraversal(TreeNode* root) {
        vectorresult;
        dfs(root,result);
        return result;
    }
};

后序遍历

class Solution {
public:
void dfs(TreeNode* root,vector& result){
    if(root==nullptr)return ;
    dfs(root->left,result);
    dfs(root->right,result);
    result.push_back(root->val);
}
    vector inorderTraversal(TreeNode* root) {
        vectorresult;
        dfs(root,result);
        return result;
    }
};

说完了递归遍历,我们再来看看非递归遍历

非递归遍历中的迭代遍历,前后序的代码是差不多的,但是中序遍历有很大差别、

先说前序遍历的迭代,思路是用一个栈来模拟递归的操作,为什么我们会想到使用栈来模拟呢?因为递归实际上就是编译器将函数各参数放入递归内部,返回时再将其弹出,所以我们用一个栈来模拟递归操作,是再合适不过的。我们写一个循环判断栈中是否为空,为空则说明没有元素要处理了,那么循环内的逻辑就是先创立一个临时的节点指针指向当前栈口处元素,先判断其是否为空,为空不能操作,会操作空指针的。不为空时我们先将遍历到的节点直接放入数组中,因为前序遍历是先处理中间节点,这之后我们按照先放入右节点再放入左节点的规律来使节点指针向后遍历。这是为什么呢?原因在于栈的独特定义,我们要先放入右节点再放入左节点,才能在下一步时候先处理左节点!这样的思路如果能想得清楚,不难写出以下代码

class Solution {
public:
    vector preorderTraversal(TreeNode* root) {
        stack s;
        vectorresult;
        if(root==nullptr)return result;
        s.push(root);//加入的是节点而并非节点对应的值,这里要尤其注意
        while(!s.empty()){
            TreeNode* node=s.top();s.pop();
           if(node)result.push_back(node->val);
            else continue;
            s.push(node->right);
            s.push(node->left);
        }
        return result;
    }
};

后序遍历思路很类似,需要注意的是后序遍历的情况为”左右中“,我们可以按照前序遍历的代码模板将前序遍历的压栈操作改为先放入左节点再放入右节点,这样它对应的原本遍历方法是中右左,当全部遍历完成之后,别急,我们将数组部分反转,即可得到后序遍历。

class Solution {
public:
    vector postorderTraversal(TreeNode* root) {
        stacks;
        vectorresult;
        if(root==nullptr)return result;s.push(root);
        while(!s.empty()){
            TreeNode*node=s.top();s.pop();
            if(node)result.push_back(node->val);
            else continue;
            s.push(node->left);
            s.push(node->right);
        }
        reverse(result.begin(),result.end());
        return result;
    }
};

这里可以看出,后序和前序的代码差别不大,也就是改了入栈的顺序,和反转了一下数组。

接着,我们再来看看中序遍历。

中序遍历的思路是:我们需要建立一个指针来存储各节点,然后我们将它们放入栈内,我们先一直向二叉树的左节点遍历直到无法继续为止,弹出栈顶元素,此时栈顶元素即为我们要找的元素,为什么这么说呢?因为我们是一直向左走,直到无法再向左走,弹出的那个元素,此时就是这个子树的中间节点(由于没有左节点)直接放入答案数组内,再遍历这个节点的右节点(不要忘记将其压入栈内),如果它有左节点接着遍历,如果它没有,那么就直接弹出,重复上述操作,不明白可以用纸模拟一下,我个人觉得中序遍历思路有些抽象,不是很好理解。为什么中序遍历不能像前后序那样只调整位置呢?拿前序遍历举例它是先遍历的中间节点也是先处理的中间节点,后序的代码前面思路也是如此,只是后面有调整位置,换句话说,是此时遍历的节点正是我们此时要处理的数据!那我们在遍历中序时候可以先遍历左边子树达成一样的逻辑吗?答案肯定是否定的,因为我们一开始只有root这个节点,而它指向了根节点,也就是说我们注定是先遍历中间节点,所以这样的方法并不适合中序迭代。

class Solution {
public:
    vector inorderTraversal(TreeNode* root) {
        stacks;
        vectorresult;
        TreeNode*cur=root;
        while(cur!=nullptr||!s.empty()){
            if(cur){
                s.push(cur);cur=cur->left;
            }
            else{
                cur=s.top();s.pop();
                if(cur)result.push_back(cur->val);
                cur=cur->right;
            }
        }
        return result;
    }
};

后序遍历我觉得也是可以按照中序差不多的思路来改编的,但是我没有试过,因为上面的反转思路已经比较简介了。


那么有没有可以将三种迭代统一起来的代码呢?也是有的,这里得思路也是创建一个栈来模拟,不同的是,我们将凡是已经遍历过的数据一股脑地放进去,在我们要处理的数据之后紧接着加入一个null来标记,这种思路也被称为标记迭代法。当我们遍历到null的时候,就对下一数据进行加入答案数组的处理。

中序

class Solution {
public:
    vector inorderTraversal(TreeNode* root) {
        vector result;
        stack st;
        if (root != NULL) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
                if (node->right) st.push(node->right);  // 添加右节点(空节点不入栈)

                st.push(node);                          // 添加中节点
                st.push(NULL); // 中节点访问过,但是还没有处理,加入空节点做为标记。

                if (node->left) st.push(node->left);    // 添加左节点(空节点不入栈)
            } else { // 只有遇到空节点的时候,才将下一个节点放进结果集
                st.pop();           // 将空节点弹出
                node = st.top();    // 重新取出栈中元素
                st.pop();
                result.push_back(node->val); // 加入到结果集
            }
        }
        return result;
    }
};

该思路旨在if条件语句中处理压栈操作,else里处理加入答案数组,使代码变得简介,但是思路难想到,建议只记一种思路。


以上代码均可ac

你可能感兴趣的:(训练营,算法,c++,数据结构,逻辑回归)