昨天我们重点讲了二叉树的深度优先遍历,今天我们的重点是二叉树的广度优先遍历。
题目分析:
对于二叉树的层序遍历,使用最多的就是迭代法,递归法反而比较麻烦。对于迭代法而言,层序遍历的题目有一套模板,掌握了模板可以解决许多关于层序遍历的题目。与深度优先遍历不同,广度优先遍历二叉树使用的是队列queue。
题目解答: 使用迭代法(注意记住解题模板)
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
if (root == nullptr) return {};
vector<vector<int>> ans;
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
int size = que.size(); // 这里统计出每一层的结点的个数,待会儿依次遍历
vector<int> temp;
for (int i = 0; i < size; i++) { // 这里使用固定的size而非que.size(),是因为que.size()在不断变化
TreeNode* node = que.front();
que.pop();
temp.emplace_back(node->val); // 得到当前结点的值
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
// 一层遍历结束
ans.emplace_back(temp);
}
return ans;
}
};
上述的迭代法解决层序遍历的问题可以看作是一个模板。在上面的代码中,我们没有让空结点入队列,如果让空结点也入队列的话,代码要稍作修改。
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
if (root == nullptr) return {};
vector<vector<int>> ans;
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
int size = que.size(); // 这里统计出每一层的结点的个数,待会儿依次遍历
vector<int> temp;
for (int i = 0; i < size; i++) { // 这里使用固定的size而非que.size(),是因为que.size()在不断变化
TreeNode* node = que.front();
que.pop();
if (node) temp.emplace_back(node->val); // 如果当前结点不为空,得到当前结点的值
else continue; // 否则的话就不能继续下去了,继续干这一层的其他结点
que.push(node->left); // 空结点也可以进入队列
que.push(node->right);
}
// 一层遍历结束
if (!temp.empty()) ans.emplace_back(temp); // 注意这里需要判断
}
return ans;
}
};
关于层序遍历也可以使用递归的方法进行解答,不过在递归的时候,还需要回溯。
class Solution {
public:
void order(TreeNode* cur, vector<vector<int>>& result, int depth)
{
if (cur == nullptr) return;
if (result.size() == depth) result.push_back(vector<int>());
result[depth].push_back(cur->val);
order(cur->left, result, depth + 1); // 注意这里隐藏了回溯的过程,depth进行了回溯
order(cur->right, result, depth + 1);
}
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
int depth = 0;
order(root, result, depth);
return result;
}
};
其中,将回溯的过程展示出来如下所示:
void order(TreeNode* cur, vector<vector<int>>& result, int depth)
{
if (cur == nullptr) return;
if (result.size() == depth) result.push_back(vector<int>());
result[depth].push_back(cur->val);
depth++;
order(cur->left, result, depth); // 这里隐藏了回溯的过程,depth进行了回溯
depth--; // depth回溯,干完了这一层就depth--
depth++;
order(cur->right, result, depth);
depth--;
}
通过以上模板,我们可以做很多类似的题目。
题目分析:
本题和二叉树层序遍历完全一致,最后进行反转即可。
题目解答:
class Solution {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
if (root == nullptr) return {};
queue<TreeNode*> que;
vector<vector<int>> ans;
que.push(root);
while (!que.empty()) {
vector<int> temp;
int size = que.size();
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
temp.emplace_back(node->val);
que.pop();
if (node->left != nullptr) que.push(node->left);
if (node->right != nullptr) que.push(node->right);
}
ans.emplace_back(temp);
}
reverse(ans.begin(), ans.end()); // 最后进行一个反转
return ans;
}
};
使用递归:
class Solution {
public:
void order(TreeNode* node, vector<vector<int>>& result, int depth) {
if (node == nullptr) return;
if (result.size() == depth) result.emplace_back(vector<int>()); // 如过result.size() == depth的话,那么S说明此时这个刚刚到这个depth,result需要加一个vector存这个depth对应的层,注意depth是从开始的
result[depth].emplace_back(node->val); // 中
order(node->left, result, depth + 1); // 注意这里隐藏了回溯的过程,因为depth+1并没有改变depth的值,等这条语句执行完,depth仍然还是之前的depth
order(node->right, result, depth + 1); // 右
}
vector<vector<int>> levelOrderBottom(TreeNode* root) {
int depth = 0;
vector<vector<int>> ans;
order(root, ans, depth);
reverse(ans.begin(), ans.end()); // 进行反转
return ans;
}
};
题目分析:
仍然是可以直接使用层序遍历的题目。
题目解答:
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
if (root == nullptr) return {};
// 仍然是层序遍历的变式
queue<TreeNode*> que;
vector<int> ans;
que.push(root);
while (!que.empty()) {
vector<int> temp;
int size = que.size();
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
temp.emplace_back(node->val);
que.pop();
if (node->left != nullptr) que.push(node->left);
if (node->right != nullptr) que.push(node->right);
}
ans.emplace_back(temp[temp.size() - 1]);
}
return ans;
}
};
题目解答:
class Solution {
public:
vector<double> averageOfLevels(TreeNode* root) {
// 仍然可以使用层序遍历
if (root == nullptr) return {};
queue<TreeNode*> que;
vector<double> ans;
que.push(root);
while (!que.empty()) {
double result;
vector<double> temp;
int size = que.size();
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
temp.emplace_back(node->val);
que.pop();
if (node->left != nullptr) que.push(node->left);
if (node->right != nullptr) que.push(node->right);
}
for (int i = 0; i < temp.size(); i++) {
result += temp[i];
}
ans.emplace_back(result / temp.size());
result = 0;
}
return ans;
}
};
题目分析:
N叉树的层序遍历和二叉树的层序遍历非常类似,不同的地方就是对于二叉树的每一个结点,其只有可能有两个孩子,但是对于N叉树而言,可能存在多个孩子。在进行每一个结点的遍历的时候,需要遍历其所有的结点。
题目解答:
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
if (root == nullptr) return {};
// 与二叉树的层序遍历非常类似,仍然是使用队列
vector<vector<int>> ans;
queue<Node*> que;
que.push(root);
while (!que.empty()) {
int size = que.size(); // 这个长度就是表示当前队列右几个元素,待会儿全部弹出来
vector<int> temp;
// 下面这个循环的意思是先把这一层的处理完毕
for (int i = 0; i < size; i++) {
Node* node = que.front(); // 取得对头的元素
que.pop();
// 这里还是判断一下是否为空,按照这个设定,应该是不为空的
if (node) temp.emplace_back(node->val);
// 将node的孩子结点(应该是不为空的)依次放入队列
for (auto& ptr : node->children) {
que.push(ptr);
}
}
ans.emplace_back(temp);
}
return ans;
}
};
题目解答:
class Solution {
public:
vector<int> largestValues(TreeNode* root) {
if (root == nullptr) return {};
// 使用层序遍历来解决问题
queue<TreeNode*> que;
que.push(root);
vector<int> ans;
while(!que.empty()) {
vector<int> temp;
int size = que.size();
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
que.pop();
temp.emplace_back(node->val);
if (node->left) que.push(node->left); // 还是让空指针不要进入队列,否则当指针为空,就不是TreeNode*了
if (node->right) que.push(node->right);
}
// temp装的是每一层的元素,求temp中的最大值
int max = temp[0];
for (int i = 1; i < temp.size(); i++) {
max = max < temp[i] ? temp[i] : max; // 使用三元运算符来判断
}
ans.emplace_back(max);
}
return ans;
}
};
题目解答:
class Solution {
public:
Node* connect(Node* root) {
if (root == nullptr) return nullptr;
// 始终是在一层进行操作,适合使用层序遍历的方法
queue<Node*> que;
que.push(root);
while (!que.empty()) {
int size = que.size();
vector<Node*> result;
for (int i = 0; i < size; i++) {
Node* node = que.front();
result.emplace_back(node);
que.pop();
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
// 此时的result是每一层的结点
int len = result.size(); // 先记录一层的结点个数
result.emplace_back(nullptr); // 然后在里面补充一个空结点
for (int i = 0; i < len; i++) {
result[i]->next = result[i + 1];
}
}
return root;
}
};
题目解答:
class Solution {
public:
Node* connect(Node* root) {
if (root == nullptr) return nullptr;
// 始终是在一层进行操作,适合使用层序遍历的方法
queue<Node*> que;
que.push(root);
while (!que.empty()) {
int size = que.size();
vector<Node*> result;
for (int i = 0; i < size; i++) {
Node* node = que.front();
result.emplace_back(node);
que.pop();
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
// 此时的result是每一层的结点
int len = result.size(); // 先记录一层的结点个数
result.emplace_back(nullptr); // 然后在里面补充一个空结点
for (int i = 0; i < len; i++) {
result[i]->next = result[i + 1];
}
}
return root;
}
};
以上的题目都是可以使用层序遍历的方法快速解决的题目,所以需要把层序遍历方法的模板理解到位。
本题其实上就是使用前序遍历对每个结点的左右孩子进行反转即可,既可以使用迭代法,也可以使用递归的方法进行。
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == nullptr) return root;
// 使用前序遍历结合迭代法进行操作
stack<TreeNode*> stk;
stk.push(root);
while (!stk.empty()) {
TreeNode* node = stk.top();
stk.pop();
swap(node->left, node->right); // swap也可以用做结点的交换
// 由于栈是先进后出,所以要先将右子树压进栈中,由于上面涉及到了node->left的问题,所以要判断是否为空
if (node->right) stk.push(node->right);
if (node->left) stk.push(node->left);
}
return root;
}
};
递归法:
TreeNode* node
,即:void invert(TreeNode* node)
if (node == nullptr) return;
在以后的题目中,确定递归的终止条件,往往是感觉最简单,实际上是最难的部分,需要考虑到一些特殊的情况。
swap(node->left, node->right); // 中
invert(node->left); // 左
invert(node->right); // 右
此外就不需要考虑更多的东西了。
所以使用递归的整体解答为:
class Solution {
public:
void invert(TreeNode* node) {
if(node == nullptr) return;
swap(node->left, node->right); // 中
invert(node->left); // 左
invert(node->right); // 右
}
TreeNode* invertTree(TreeNode* root) {
invert(root);
return root;
}
};
与二叉树的前序遍历类似,N叉树的前序遍历也可以使用迭代法或者递归来实现。
迭代法:
class Solution {
public:
vector<int> preorder(Node* root) {
if (root == nullptr) return {};
stack<Node*> stk;
stk.push(root);
vector<int> ans;
while (!stk.empty()) {
Node* node = stk.top();
stk.pop();
if (node) ans.emplace_back(node->val);
// 先将右边的孩子入栈,然后入栈左边的孩子
for (int i = node->children.size() - 1; i >= 0; i--) {
stk.push(node->children[i]);
}
}
return ans;
}
};
递归法:
class Solution {
public:
void traversal(Node* node, vector<int>& ans) {
// 递归的终止条件
if (node == nullptr) return;
// 单层的处理逻辑
ans.emplace_back(node->val); // 中
for (auto& temp : node->children) {
traversal(temp, ans); // 从左至右
}
}
vector<int> preorder(Node* root) {
// 使用递归法
vector<int> ans;
traversal(root, ans);
return ans;
}
};
这里的单层的处理逻辑可以跟二叉树的基本一样,只是说二叉树最多只有2个结点,但是N叉树有N个结点。
和N叉树的前序遍历基本是一样的,使用迭代法就是入栈的顺序不一样。前序遍历是右边的结点先入栈,后序遍历则是左边的结点先入栈。然后后序遍历最后还需要反转一下数组。
使用递归法只需要换一下顺序即可。
class Solution {
public:
vector<int> postorder(Node* root) {
if (root == nullptr) return {};
// 使用非递归来实现,这里我们让空结点也入栈,也有可能不是空结点
stack<Node*> stk;
stk.push(root);
vector<int> ans;
while (!stk.empty()) {
Node* node = stk.top();
stk.pop();
// 由于我们不知道具体children是不是也包括了空结点,这里还是判断一下
if (node) ans.emplace_back(node->val);
// 由于是后序遍历,我们先让左节点进,那么就最后出,然后对整体的结果进行排序即可
for (int i = 0; i < node->children.size(); i++) {
stk.push(node->children[i]);
}
}
reverse(ans.begin(), ans.end());
return ans;
}
};
递归法:
class Solution {
public:
// 使用递归
void traverse(Node* node, vector<int>& vec) {
if (node == nullptr) return;
// 先遍历结点的子树
for (auto& ptr : node->children) {
traverse(ptr, vec);
}
// 再存值
vec.emplace_back(node->val);
}
vector<int> postorder(Node* root) {
vector<int> ans;
traverse(root, ans);
return ans;
}
};
补充:对于第9题而言,使用前序遍历以及后序遍历都是可以的,但是不能使用真正的中序遍历。
class Solution {
public:
void invert(TreeNode* node) {
if (node == nullptr) return;
invert(node->left); // 左
swap(node->left, node->right); // 中
invert(node->right); // 右
}
TreeNode* invertTree(TreeNode* root) {
invert(root);
return root;
}
};
上面是使用中序遍历进行反转的代码,可以看到,我们最开始反转了左子树,然后交换了左右子树,然后反转了右子树。得到的结果应该是,原来的左子树被反转了一次,然后移到了右子树的位置,然后又被反转了一次(此时又反转回来了),最终的结果只是左右子树的根结点交换了,后序的结点并没有交换。
如果将代码改为:
class Solution {
public:
void invert(TreeNode* node) {
if (node == nullptr) return;
invert(node->left); // 左
swap(node->left, node->right); // 中
invert(node->left); // 右
}
TreeNode* invertTree(TreeNode* root) {
invert(root);
return root;
}
};
其中,两次都反转所谓的左子树,就可以了,但是这已经不是真正意义上的中序遍历了。