二叉树的【层序遍历】【垂序遍历】具体思路,文中包含多种方法(附完整代码)


前言

本文将主要围绕以下问题进行讨论:

二叉树的层序遍历、垂序遍历。

层序遍历的两种方法 【迭代法】【递归法】

垂序遍历的两种方法 【递归排序】【哈希递归】

对于垂序遍历的方法多种多样,大家也可以多多尝试不同的思路。


一、如何理解二叉树的层序遍历?

从根节点开始,首先访问根节点,然后依次访问根节点的每一层的所有节点。从第一层开始,自上而下,从左至右依次访问每一层的节点。

题目链接: [点击跳转] Leetcode 102. 二叉树的层序遍历

请添加图片描述


二、二叉树层序遍历

1.方法一(迭代法)

进行广度优先搜索,所以选择借助先进先出的队列。

引用辅助队列,首先对传入节点进行判断,若不为空,压入队列中。
这时,进入循环(直至队列为空:所有节点全部遍历完成停止)

在if循环中遍历出同一层的所有节点,再指向两侧子节点。

如下代码可以实现层序遍历,并且已标注好每句的作用:

class Solution {
public:
    vector<vector<int>> tre;//用于存储最终的层序遍历结果,其中每个子向量代表一层的节点值。
    queue<TreeNode*> que;//用于辅助实现层序遍历,存储待处理的节点。
    vector<vector<int>> levelOrder(TreeNode* root) {
        if (root == nullptr) {
            return tre;
        }
        que.push(root);//如果根节点不为空,将根节点root放入队列que中
        while (!que.empty()) {//进入循环,只要队列不为空,就一直进行
            int size = que.size();//记录当前队列的大小size,这个大小代表了当前层的节点数量
            vector<int> level;//创建一个临时向量level,用于存储当前层的节点值
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();//取出队首节点node
                que.pop();//并将其从队列中弹出
                level.push_back(node->val);//将该节点的值node->val放入临时向量level中
                if (node->left) {//如果该节点有左子树,则将左子树节点node->left放入队列中
                    que.push(node->left);
                }
                if (node->right) {
                    que.push(node->right);
                }
            }
            tre.push_back(level);
        }
        return tre;
    }
};

2.方法二(递归法)

使用递归的方式实现了二叉树的层序遍历,通过记录当前层级来动态地扩展结果向量。

虽然递归的方法可能会受到系统栈空间的限制,但对于一般规模的二叉树,这种方法是简洁有效的。

与使用队列的方法相比,递归方法可能在理解上更加直观,但在处理大规模数据时可能会出现栈溢出的问题。

代码如下(示例):

class Solution {
public:
    vector<vector<int>> tre;
    vector<vector<int>> levelOrder(TreeNode* root) {     
        if (root == nullptr) {
            return tre;
        }
        digui(root, 0);
        return tre;
    }
    void digui(TreeNode* node, int level) {
        if (node == nullptr) {
            return;
        }
        if (level == tre.size()) {//判断当前层级是否等于结果向量tre的大小,如果是,说明需要为新的一层创建一个空的子向量,将其加入结果向量tre中
            tre.push_back({});
        }
        tre[level].push_back(node->val);
        digui(node->left, level + 1);
        digui(node->right, level + 1);
    }
};

时间复杂度为O(n)。
空间复杂度为O(n)。


三、如何理解二叉树的垂序遍历?

这里我们引用leetcode上的一道题来帮助理解。

题目链接: [点击跳转] Leetcode 987. 二叉树的垂序遍历
请添加图片描述

对于位于坐标 (x, y) 的结点,其左右子结点坐标分别为 (x + 1, y- 1) 和 (x + 1, y + 1),树的根结点位于 (0, 0)。

垂序遍历是从最左边的列开始到最右边的列结束,按列索引每一列上的所有结点,形成从上到下排序的有序列表,若同行同列有多个结点,则按结点值从小到大排序。


1.方法一(递归排序)

通过深度优先搜索的方式,将节点的行和列信息记录下来,存储在一个数组或列表 node中,然后对 node 按照列、行、值的顺序进行排序。

代码如下:

class Solution {
public:
    int tr = INT_MIN;
    vector<vector<int>> verticalTraversal(TreeNode* root) {
        vector<tuple<int, int, int>> node;
        function<void(TreeNode*, int, int)> dfs = [&](TreeNode* root, int i, int j) {//接受TreeNode*类型指针和两个整数参数且无返回值的函数。
            if (!root) {
                return;
            }
            node.emplace_back(j, i, root->val);
            dfs(root->left, i + 1, j - 1);
            dfs(root->right, i + 1, j + 1);
        };
        dfs(root, 0, 0);
        sort(node.begin(), node.end());
        vector<vector<int>> tre;
        for (auto [j, _, val] : node) {
            if (j != tr) {
                tr = j;
                tre.emplace_back();
            }
            tre.back().push_back(val);
        }
        return tre;
    }
};

时间复杂度为O(nlogn)。


2.方法二(哈希递归)

使用映射和多重集合来记录二叉树中每个节点的位置信息,并通过遍历和排序来实现垂序遍历。

map<int, multiset<pair<int, int>>> maph:使用map来存储每一列的节点信息。键是列坐标,值是一个多重集合(multiset),其中每个元素是一个pair,包含行坐标和节点值。

vector<vector> tre:用于存储最终的垂序遍历结果,其中每个子向量代表一列的节点值。

代码如下:

class Solution {
public:
    map<int, multiset<pair<int, int>>> hash;
    void digui(TreeNode* root, int x, int y) {
        if (!root) return;
        hash[y].insert({x, root->val});
        digui(root->left, x + 1, y - 1);
        digui(root->right, x + 1, y + 1);
    }
    vector<vector<int>> verticalTraversal(TreeNode* root) {
        digui(root, 0, 0);
        vector<vector<int>> tre;
        for (auto& [_, x] : hash) {
            vector<int> vals;
            for (auto& [_, val] : x) {
                vals.push_back(val);
            }
            tre.push_back(vals);
        }
        return tre;
    }
};

时间复杂度为O(nlogn)。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值