题目1:从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
1.1 思路解析
为了更好地讲解从上到下打印二叉树系列的题目,我们所选的二叉树示例如图1所示。
图1: 二叉树示例
根据题意,我们需要一个一边压入节点一边输出节点的容器,也就是可以选择队列或者双向队列。这里一边只压入,一边只输出,所以队列就可以满足要求。
1.2 基本知识温习
首先复习一下STL中队列的基本知识,主要有以下:
1:头文件
#include <queue>
2:声明
queue<数据类型> 队列名称; //队列名称如que
3:基本方法
front() :返回第一个元素(队顶元素);复杂度为O(1)
back() :返回最后被压入的元素(队尾元素);复杂度为O(1)
empty() :当队列为空时,返回true;复杂度为O(1)
size() :返回队列的长度;复杂度为O(n)
push(x) :将x压入队列的末端;复杂度为O(1)
pop() :弹出队列的第一个元素(队顶元素);没有返回值,复杂度为O(1)
1.3 步骤讲解
首先不用先着急,在草纸上画一下基本的步骤,缕清思路,再一蹴而就。根据二叉树示例,我们有如下步骤:
图2:单向队列算法步骤图
根据第一层、第二层的处理,是不是已经了然于心了?第一层的压入操作完成后,队列中存的是第一层的所有节点;第一层节点的弹出操作完成后,队列中存的是第二层的全部节点;第二层节点的弹出操作完成后。队列中存的是第三层的全部节点。以此类推,直至某层的没有左右子节点,队列为空,算法结束。
其中的关键点是:1:弹出时,弹出的次数是当前列队的长度,也即是当前层的所有节点。2:队列中是节点,而不是val,因为节点保存着其左右子节点信息,能够由节点找到其下一层节点。
1.4 算法代码
class Solution {
public:
vector<int> levelOrder(TreeNode* root) {
vector<int> res;
if (root == NULL)
return res;
queue<TreeNode*> que;
que.push(root);
TreeNode* node;
while (!que.empty()) {
node = que.front();
que.pop();
res.push_back(node->val);
if (node->left)
que.push(node->left);
if (node->right)
que.push(node->right);
}
return res;
}
};
题目2:从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
2.1 思路解析
根据题目1的基础,只需要做一步,就是每层的数据单独存放到一个容器(vector)中。而哪里又是每一层的分界点呢。对了,就是每一层开始要处理的时候,队列的长度就是该层节点的总个数,一个计数问题即可完成分层的工作。
2.2 算法代码
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
if (root == NULL)
return res;
queue<TreeNode*> que;
que.push(root);
TreeNode* node;
while (!que.empty()) { //比que.size()快很多
vector<int> tmp;
int num = que.size();
while (num--) {
node = que.front();
que.pop();
tmp.push_back(node->val);
if (node->left)
que.push(node->left);
if (node->right)
que.push(node->right);
}
res.push_back(tmp);
}
return res;
}
};
题目3:按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
3.1 思路解析
基于题目2的思路,做一点改动即可,就是对行做奇偶判断,若是偶数层,则翻转这一行的结果即可。这种方法简单易于理解,缺点就是翻转数组的复杂度是O(n)。还有一种思路是使用双向队列,因为单向队列明显不够用了,可以用单向队列模拟一下,尽管改变压入队列的左右子树顺序,到第三层就会出现问题。
3.2 基本知识温习
首先复习一下STL中双向队列的基本知识,主要有以下:
1:头文件
#include <deque>
2:声明
deque<数据类型> 双向队列名称; //队列名称如dq
3:基本方法
front() :返回第一个元素(队顶元素);复杂度为O(1)
back() :返回最后被压入的元素(队尾元素);复杂度为O(1)
empty() :当队列为空时,返回true;复杂度为O(1)
size() :返回队列的长度;复杂度为O(n)
push_front(x) :将x压入队列的头部;复杂度为O(1)
push_back(x) :将x压入队列的尾部;复杂度为O(1)
pop_front() :弹出队列的第一个元素(队顶元素);没有返回值,复杂度为O(1)
pop_back() :弹出队列的最后一个元素(队尾元素);没有返回值,复杂度为O(1)
3.3 步骤讲解
为了更好地理解算法,本文约定:若是从左向右打印,就从队列右边弹出,若从右向左打印,就从左边弹出。有一个细节容易忽略:若从某个方向在弹出操作,那这个方向的节点未弹出完成之前,这个方向不能有压入操作,只能从另一个端压入。对于列队,边为front,右边为back。前三步如图所示:
图3:双向队列算法步骤图
其中有一个难点,就是压入左右子节点的顺序。例如第二步从右向左打印,在双向队列右边弹出3时,在左边以什么顺序压入3的左右子节点呢?顺着走思路好像有点混乱了,那逆推一下,若想下一步从左边先弹出8(3的右子节点),再弹出5(3的左子节点),是不是得先从左边压入5再压入8,即从左边先压入左子节点再压入右子节点。第三步从左向右打印,从左边弹出8后,以什么顺序压入8的左右子节点呢?还是逆推,对于8的子节点,若想下一步从右边边弹出先弹出9再弹出2,则2(8的右子节点)应该先从右边压入,再压入9(8的左子节点),即从右边先压入右子节点再压入左子节点。对于5亦是如此。至此一个“之”字分析解析完,规律也总结完了。
3.4 算法代码
双端队列,右端出则左边进,左端出则右端进。同时考虑下一次哪段出,由于每次都换端弹出,则元素先进后出。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
if (NULL == root)
return vector<vector<int>>();
vector<vector<int>> res;
bool isRight = true;
deque<TreeNode*> que;
que.push_back(root);
while (!que.empty()) {
int num = que.size();
vector<int> tmp;
while (num--) {
if (isRight) {
TreeNode *node = que.back();
que.pop_back();
tmp.push_back(node->val);
if (node->left)
que.push_front(node->left);
if (node->right)
que.push_front(node->right);
} else {
TreeNode *node = que.front();
que.pop_front();
tmp.push_back(node->val);
if (node->right)
que.push_back(node->right);
if (node->left)
que.push_back(node->left);
}
}
isRight = !isRight;
res.push_back(tmp);
}
return res;
}
};
107. 二叉树的层次遍历 II
题目:
给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
解法1:借助从上向下打印二叉树的解法,反转结果数组
class Solution {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
vector<vector<int>> res;
if (NULL == root)
return res;
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
vector<int> tmp;
int len = que.size();
while (len--) {
TreeNode* node = que.front();
que.pop();
tmp.push_back(node->val);
if (node->left)
que.push(node->left);
if (node->right)
que.push(node->right);
}
res.push_back(tmp);
}
reverse(res.begin(), res.end());
return res;
}
};
解法2:用栈压入每层节点数组
class Solution {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
vector<vector<int>> res;
if (NULL == root)
return res;
queue<TreeNode*> que;
stack<vector<int>> sta;
que.push(root);
while (!que.empty()) {
vector<int> tmp;
int len = que.size();
while (len--) {
TreeNode* node = que.front();
que.pop();
tmp.push_back(node->val);
if (node->left)
que.push(node->left);
if (node->right)
que.push(node->right);
}
sta.push(tmp);
}
while (!sta.empty()) {
res.push_back(sta.top());
sta.pop();
}
return res;
}
};
附录:补充下vector的基本知识
1:头文件
#include<vector>
2:声明
vector<数据类型> 动态数组名称; //动态数组名称如vec
3:基本方法
capacity() :返回动态数组的容量
size() :返回动态数组实际长度
empty() :是否为空时,返回true
front() :返回动态数组第一个元素;复杂度为O(1)
back() :返回动态数组最后一个元素;复杂度为O(1)
push_back(x) :将x压入动态数组的尾部;复杂度为O(1)
pop_back() :弹出动态数组最后一个元素;没有返回值,复杂度为O(1)
4:迭代器
begin() :指向第一个元素位置
end() :指向最后一个元素的下一个位置
cbegin():指向第一个元素位置,通过指针只读元素,不能对元素做修改
cend() :指向最后一个元素的下一个位置
5:方法
#include <algorithm>
reverse(vec.begin(), vec.end()) :翻转动态链表
sort(vec.begin(), vec.end()) :默认是从小到大的排序
//如果想从大到小排序,有两种方法:
5.1:自写比较函数:
bool comp(const int& x, const int& y) {
return x > y;
}
sort(vec.begin(), vec.end(), comp);
5.2:用lamda
sort(vec.begin(),vec.end(),[](const int& x, const int& y){return x>y;});