二叉树遍历算法

本文详细介绍了二叉树的四种遍历方法:先序遍历、中序遍历、后序遍历和层序遍历,包括递归和非递归(栈)的实现方式。同时,文章还讨论了在不同遍历过程中如何处理节点,以及在实际问题中如二叉搜索树的范围和、平衡二叉树、二叉树展开为链表等的应用。
摘要由CSDN通过智能技术生成

目录

先序遍历

中序遍历

后序遍历

层序遍历

938. 二叉搜索树的范围和

110. 平衡二叉树

114. 二叉树展开为链表

117. 填充每个节点的下一个右侧节点指针 II

116. 填充每个节点的下一个右侧节点指针


1,三种遍历都是先把二叉树的最左结点循环入栈(DFS迭代),以帮助找到返回处理的节点。

每个子树也是先把该右子树中的最左节点循环入栈,以帮助找到返回处理的节点。

先序遍历和中序遍历都是通过一个左子树(全绿的)的最右下叶节点的right(灰NULL)带出接下来的处理结点(左子树的父结点(紫)),表明该处理结点的左子树已经遍历处理完毕。因为不管先序遍历还是中序遍历,一颗子树的结束点是在该子树的最右下叶节点。

2,先序和中序代码结构一致,是处理完父和左或左和父之后再处理右,天然可以通过父节点带出右子树。但是后序遍历是先处理左和右再处理父,其中某次父节点出栈,只用一个栈的话没法确定左右是否处理完毕,因此需要增加一个栈记录状态。

3,后序遍历需要用两个栈,一个栈用于记录返回处理的结点,一个用于记录状态,0表示处理了左子结点,1表示处理了右子结点。

注:

1,二叉树遍历非递归经典解法DFS(栈)、BFS(队列),注意入栈的条件都是当节点非空node != nullptr时入栈,否则从栈中取出节点指针,取值会非法。

2,DFS栈解法比递归的好处:以先序为例,在把当前p的right、left入栈后,在下一轮从栈pop出left前的这段时间,可以额外进行各种处理(如二叉树转为链表时需对p的left和right重置值leetcode114栈解法),因为在额外处理前已经用栈记录了先序遍历的节点顺序。

先序遍历

DFS递归

注:(递归:从上到下)先父节点,后子节点,是尾递归

public void preorderTraversal(TreeNode node)
{
    if (node == null) {
        return;
    }
    System.out.println(node.value); // 访问当前结点
    preorderTraversal(node.left); // 前序遍历其左子树
    preorderTraversal(node.right); // 前序遍历其右子树
}

DFS解法1:DFS栈通用模板(只适用于先序遍历,因为只有先序遍历是先父后子):CSDN

注:1,因为二叉树状态转换虽然是DAG(有向无环图)但不同路径间没有重复(从同一点A出发,只有一条路径到达另一点B),所以左右孩子入栈前不需要(空间换时间的visit)判重。

       2,入栈栈顺序是先right孩子,后left孩子,这样出栈是先左后右,满足先序遍历顺序。

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

DFS解法2:用栈stack记住来时的路来时的路即父节点

注:本解法的意图强调:用栈stack记住来时的路(即stack中是存的所有的父节点)。

//先序遍历(栈)
void pre_visit(BTree* root)
{
    std::stack<BTree*> stack_tree;
    BTree* cur_node = root;

    while(cur_node != NULL || !stack_tree.empty())
    {
        if(cur_node != NULL) { // 当前节点非空:记录到结果集,并入栈作为子节点的灯塔
            std::cout << cur_node->value.c_str() << "\t";  // 记录到结果集
            stack_tree.push(cur_node);  // 当前节点入栈
            cur_node = cur_node->leftChild;  // 处理左
        } else {
            cur_node = stack_tree.top();  // 父节点出栈
            stack_tree.pop();  //栈顶元素出栈
            cur_node = cur_node->rightChild;  // 处理右
        }
    }
}

中序遍历

DFS递归

注:先孩子,又父亲,再孩子。所以是非尾递归

public void inorderTraversal(TreeNode node)
{
    if (node == null) {
        return;
    }
    inorderTraversal(node.left); // 中序遍历结点的左子树
    System.out.println(node); // 访问当前结点
    inorderTraversal(node.right); // 中序遍历结点的右子树
}

DFS栈:记住来时的路

//中序遍历(非递归)
void mid_visit(BTree* root)
{
    std::stack<BTree*> stack_tree; 
    BTree* cur_node = root;

    while(cur_node != NULL || !stack_tree.empty())
    {
        if(cur_node != NULL) {
            stack_tree.push(cur_node);  //当前节点入栈
            cur_node = cur_node->leftChild;  //指向左子节点,进行相同处理
        } else {
            cur_node = stack_tree.top();  //指向栈顶节点
            stack_tree.pop();  //栈顶节点出栈
            std::cout << cur_node->value.c_str() << "\t";  //处理当前节点的值
            cur_node = cur_node->rightChild;  //指向右子节点,进行相同处理
        }
    }
}

后序遍历

DFS递归非尾递归

public void postorderTraversal(TreeNode node) {
    if (node == null) {
        return;
    }
    postorderTraversal(node.left); // 后序遍历结点的左子树
    postorderTraversal(node.right); // 后序遍历结点的右子树
    System.out.println(node); // 访问当前结点
}

 DFS栈:

思路1:借用先序遍历的"根左右"入结果集。改为"根右左"入一个可以支撑倒序容器并反向输出得到"左右根"

思路1-解法1:以"根右左"顺序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值