二叉树的层序遍历
层序遍历相比较迭代和递归来说,简单很多。使用额外容器queue
来存放结点,将一层的元素存入到队列中,再按顺序输出。因为队列是先进先出,所以按照左右的顺序正常压入就行。代码如下:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> que;
vector<vector<int>> ans;
if(root != NULL) que.push(root);
while(!que.empty()){
int size = que.size(); //que的size表示这次需要处理几个结点
vector<int> vec;
while(size--){
TreeNode* cur = que.front(); //当前需要处理的结点
que.pop();
vec.push_back(cur->val);
//弹出后需要将其左右孩子压入队列,在下一次循环中遍历
if(cur->left) que.push(cur->left);
if(cur->right) que.push(cur->right);
}
ans.push_back(vec);
vec.clear();
}
return ans;
}
因为这里输出的是二维数组,所以在while循环中需要初始化一个储存当前层的元素。
LeetCode 637. 二叉树的层平均值
vector<double> averageOfLevels(TreeNode* root) {
vector<double> ans;
queue<TreeNode*> que;
if(root != NULL) que.push(root);
while(!que.empty()){
int size = que.size();
double sum = 0;
for(int i = 0; i < size ; i++){
TreeNode* cur = que.front();
sum += cur->val;
que.pop();
if(cur->left) que.push(cur->left);
if(cur->right) que.push(cur->right);
}
ans.push_back(sum / size);
}
return ans;
}
本题需要注意的就是sum
需要是double
类型。另外因为需要求平均值,所以不能使用while(size--)
循环,改为for(int i = 0; i < size ; i++)
。
LeetCode 429. N 叉树的层序遍历
这题其实没什么东西,很简单。只不过是在处理当前节点的子节点时,从左右孩子变成了遍历一个结点数组。
vector<vector<int>> levelOrder(Node* root) {
vector<vector<int>> ans;
queue<Node*> que;
if(root != NULL) que.push(root);
while(!que.empty()){
int size = que.size();
vector<int> vec;
while(size--){
Node* cur = que.front();
vec.push_back(cur->val);
que.pop();
for(auto it = cur->children.begin(); it != cur->children.end(); it++){
que.push(*it);
}
}
ans.push_back(vec);
}
return ans;
}
LeetCode 515. 在每个树行中找最大值
之前做过类似找最大最小值的题目,这里令对比对象初始化为INT_MIN
。
vector<int> largestValues(TreeNode* root) {
vector<int> ans;
queue<TreeNode*> que;
if(root != NULL) que.push(root);
while(!que.empty()){
int size = que.size();
int temp = INT_MIN;
while(size--){
TreeNode* node = que.front();
temp = node->val > temp ? node->val : temp;
que.pop();
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
ans.push_back(temp);
}
return ans;
}
LeetCode 116. 填充每个节点的下一个右侧节点指针
本题就是在遍历时加一个判断,如果为本层最后一个结点,使其指向NULL
即可。
Node* connect(Node* root) {
queue<Node*> que;
if(root != NULL) que.push(root);
while(!que.empty()){
int size = que.size();
while(size--){
Node* cur = que.front();
que.pop();
if(size != 0){
cur->next = que.front();
}else{
cur->next = NULL;
}
if(cur->left) que.push(cur->left);
if(cur->right) que.push(cur->right);
}
}
return root;
}
LeetCode 104. 二叉树的最大深度
这里要知道二叉树深度的概念,以及高度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
二叉树的高度是表示当前结点所在的位置。比如二叉树最下方的叶子结点的高度为1,根节点的高度与深度相同。
采用层序遍历,在一层结束后,depth++
即可。
int maxDepth(TreeNode* root) {
int ans = 0;
queue<TreeNode*> que;
if(root != NULL) que.push(root);
while(!que.empty()){
int size = que.size();
while(size--){
TreeNode* cur = que.front();
que.pop();
if(cur->left) que.push(cur->left);
if(cur->right) que.push(cur->right);
}
ans++;
}
return ans;
}
111. 二叉树的最小深度
当左右孩子都为空的时候,才说明遍历到最低点了,如果其中一个孩子为空则不是最低点。所以当找到左右孩子都为空的结点时,直接返回结果即可。
int minDepth(TreeNode* root) {
int min = 0;
queue<TreeNode*> que;
if(root != NULL) que.push(root);
while(!que.empty()){
int size = que.size();
min++;
while(size--){
TreeNode* cur = que.front();
que.pop();
//找到即返回
if(cur->left == NULL && cur->right == NULL) return min;
if(cur->left) que.push(cur->left);
if(cur->right) que.push(cur->right);
}
}
return min;
}
LeetCode 226. 翻转二叉树
本题也是有很多解法,递归迭代都可以实现。
-
递归:
递归上来还是三部曲:- 输入就是当前需要反转的结点,输出就是反转过后的结点。
- 终止条件是空结点时,结束本层递归
- 递归函数体就是反转左右结点
代码如下:
TreeNode* invertTree(TreeNode* root) { if (root == NULL) return root; swap(root->left, root->right); // 中 invertTree(root->left); // 左 invertTree(root->right); // 右 return root; }
后序递归的话只需要把反转逻辑放在最后即可。
如果是中序遍历,会将一个结点反转两次,所以不推荐中序,中序需要把代码写成这样:invertTree(root->left); // 左 swap(root->left, root->right); // 中 invertTree(root->left); // 右
我需要反转的右结点实际上是经过反转后的左节点,所以反转两次left。
-
迭代
迭代其实也很简单,在遍历的过程中添加一个swap
完成反转,代码如下:TreeNode* invertTree(TreeNode* root) { if(root == NULL) return root; stack<TreeNode*> st; st.push(root); while(!st.empty()){ TreeNode* cur = st.top(); st.pop(); swap(cur->left,cur->right); //中 if(cur->right) st.push(cur->right); //右 if(cur->left) st.push(cur->left); //左 } return root; }
这里只给出前序迭代的代码,后序类似。
迭代的统一写法(前序):
TreeNode* invertTree(TreeNode* root) { stack<TreeNode*> st; if(root == NULL) return root; st.push(root); while(!st.empty()){ TreeNode* cur = st.top(); if(cur != NULL){ st.pop(); if(cur->right) st.push(cur->right); if(cur->left) st.push(cur->left); st.push(cur); st.push(NULL); }else{ st.pop(); cur = st.top(); swap(cur->left, cur->right); st.pop(); } } return root; }
还是一样,在处理中间结点的时候
swap
一下。 -
层序
最后是层序,层序这题也可以做,相同的思路,一样的味道。
代码如下:TreeNode* invertTree(TreeNode* root) { queue<TreeNode*> que; if(root == NULL) return root; que.push(root); while(!que.empty()){ int size = que.size(); while(size--){ TreeNode* cur = que.front(); que.pop(); swap(cur->left,cur->right); if(cur->left) que.push(cur->left); if(cur->right) que.push(cur->right); } } return root; }
LeetCode 101. 对称二叉树
这题leetcode上给出的是简单难度,实际上思路和代码复杂度上怎么说也得是个中等难度。
首先需要搞清楚几种情况:
- 左节点为空,右节点不为空,不对称
- 左不为空,右为空,不对称
- 左右都为空,对称
- 左右都不为空且值不等,不对称
- 左右都不为空且值相等,对称
然后要清楚对比的是二叉树两侧的结点,分为内侧和外侧。下面给出卡哥的图:
这样看比较清晰。
最后看一颗二叉树是否是对称二叉树,实际上是对比这颗树的左右子树,也就是对两棵子树同时进行遍历。
接下来就是考虑使用的遍历方式和遍历顺序,首先是递归遍历。
-
递归
递归的三要素:- 输入输出:输入应该是两侧的两个结点left和right进行对比,所以输入是两个参数,输出即是否对称,为
bool
- 终止条件:不对称即
false
,直接退出递归 - 递归函数体
递归代码如下:
bool compare(TreeNode* left , TreeNode* right){ if(left != NULL && right == NULL) return false; else if(left == NULL && right != NULL) return false; else if(left == NULL && right == NULL) return true; else if(left->val != right->val) return false; //else return compare(left->left, right->right) && compare(left->right, right->left); bool outside = compare(left->left, right->right); // 左子树:左、 右子树:右 bool inside = compare(left->right, right->left); // 左子树:右、 右子树:左 bool isSame = outside && inside; // 左子树:中、 右子树:中(逻辑处理) return isSame; }
先把外侧的结点处理完成,再处理内侧结点,也就是代码中的
outside
和inside
。left->left
就是左子树继续往外走,right->right
就是右子树继续往外走。最后返回值必须两者都是true
才说明是对称。
其次注意if和else if语句中的顺序,首先需要把有可能出现空结点的情况讨论完,再去讨论不是空结点的情况,否则会出现访问空结点的错误。 - 输入输出:输入应该是两侧的两个结点left和right进行对比,所以输入是两个参数,输出即是否对称,为
-
迭代
迭代遍历首先还是要模拟一下入栈和出栈的过程,只有把过程搞明白了,才能写出正确的代码。直接看代码以及注释:bool isSymmetric(TreeNode* root) { queue<TreeNode*> que; //因为是同时遍历左右子树,所以要先把根节点的左右孩子压入 que.push(root->left); que.push(root->right); while(!que.empty()){ //取出结点进行对比。先压入再取出处理也是迭代中必须的手法 TreeNode* l = que.front(); que.pop(); TreeNode* r = que.front(); que.pop(); if(l == NULL && r == NULL) continue; //首先处理都为空的情况,直接进入下一次循环 //再处理不对称的情况 if(l == NULL && r != NULL) return false; else if(l != NULL && r == NULL) return false; else if(l->val != r->val) return false; //最后按照顺序将当前结点的子结点压入栈中 que.push(l->left); que.push(r->right); que.push(l->right); que.push(r->left); } return true; }
迭代相对于递归的写法更复杂一些,我认为将结点成对的入栈,再成对的取出进行对比是本题的核心思想。
最后
因为二叉树是面试中常考的数据结构,对二叉树的处理也是手撕代码中出现频率较高的,需要熟练掌握,所以要经常回来复习一下!