前言
本文将主要围绕以下问题进行讨论:
二叉树的层序遍历、垂序遍历。
层序遍历的两种方法 【迭代法】【递归法】
垂序遍历的两种方法 【递归排序】【哈希递归】
对于垂序遍历的方法多种多样,大家也可以多多尝试不同的思路。
一、如何理解二叉树的层序遍历?
从根节点开始,首先访问根节点,然后依次访问根节点的每一层的所有节点。从第一层开始,自上而下,从左至右依次访问每一层的节点。
题目链接: [点击跳转] 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)。