队列 + 宽搜BFS
- 1.N 叉树的层序遍历
- 2.二叉树的锯齿形层序遍历
- 3.二叉树最大宽度
- 4.在每个树行中找最大值
点赞👍👍收藏🌟🌟关注💖💖
你的支持是对我最大的鼓励,我们一起努力吧!😃😃
1.N 叉树的层序遍历
题目链接: 429. N 叉树的层序遍历
题目分析:
层序遍历是一层一层的出,需要借助队列使用BFS来完成。比如说遍历到第三层的时候需要从第二层第一个节点的孩子先开始。因此就需要先记录这个节点的信息,遍历完3、4、5回过头再来遍历3的孩子。符合先进先出的思想。所以层序遍历要用到队列来实现。
算法原理:
解法:BFS(层次遍历/宽度优先搜索/宽度优先搜索)
层序遍历步骤,先搞一个队列,如果根节点不为空,现让根节点入队。接下来就是一个循环,当队列不空的时候,先把队头元素拿出来,然后把队头元素的孩子入队。循环下去直到队列为空为止。就把这棵树层序遍历结束了。
但是这道题要求的是一层一层出,因此我们需要一个变量在每次出队之前先统计一下当前队列中有多少个元素,然后每次出这一层的个数就行了。这个变量就是当前这一层元素的个数。
/*
// Definition for a Node.
class Node {
public:
int val;
vector<Node*> children;
Node() {}
Node(int _val) {
val = _val;
}
Node(int _val, vector<Node*> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
vector<vector<int>> ret; //记录最终结果
if(root == nullptr) return ret;
queue<Node*> qu;//层序遍历需要的队列
qu.push(root);
while(!qu.empty())
{
int num = qu.size();//先统计出本层元素的个数
vector<int> tmp; //统计本层的节点
while(num--)
{
Node* front = qu.front();
tmp.push_back(front->val);
qu.pop();
for(auto* cur : front->children)//下一层入队
{
qu.push(cur);
}
}
ret.push_back(tmp);
}
return ret;
}
};
2.二叉树的锯齿形层序遍历
题目链接: 103. 二叉树的锯齿形层序遍历
题目分析:
锯齿形层序遍历 ,先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行
算法原理:
解法:BFS
这道题和上面是一模一样的,只不过有一点小小的改变。发现偶数列是逆序放的。因此可以增加一个标记位,让偶数列的信息逆序即可。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
vector<vector<int>> ret;
if(root == nullptr) return ret;
queue<TreeNode*> qu;
qu.push(root);
int level = 1;
while(!qu.empty())
{
int k = qu.size();
vector<int> tmp;
while(k--)
{
TreeNode* t = qu.front();
qu.pop();
tmp.push_back(t->val);
if(t->left) qu.push(t->left);
if(t->right) qu.push(t->right);
}
if(level % 2 == 0) reverse(tmp.begin(),tmp.end());
ret.push_back(tmp);
++level;
}
return ret;
}
};
3.二叉树最大宽度
题目链接:662. 二叉树最大宽度
题目分析:
每一层的 宽度 被定义为该层最左和最右的非空节点(即,两个端点)之间的长度。将这个二叉树视作与满二叉树结构相同,两端点间会出现一些延伸到这一层的 null 节点,这些 null 节点也计入长度。
最左和最右非空节点中间的nullptr也要加入计算到长度。
算法原理:
解法一:硬来
依旧是创建一个队列,这次入队不一样即使是一个节点的左孩子或者右孩子是null也要入队。然后在每一层出队之前都可以统计这一层有多宽。比如这棵树第三层没有问题宽度就是这一层的节点数,但是第四层就有问题了,实际宽度并不是这一层节点数。那如何统计这一层的实际宽度呢?方法很简单,我们在遍历这一层的时候可以再来一个empty指针,当这个指针遇到不是 null 的时候 宽度+1,如果是空的话就统计就循环把null拿到,如果碰到非空的时候就把 null 的个数 加入到 宽度,然后把null 的个数记为0。如果直到遍历到队列结尾还没有碰到非空 就不要把 null 结点数加入到 宽度。
但是这个方法会超时!
虽然节点有1-3000个,但是如果是系啊没这种左右非常平均有1500个节点。最后一层一共2^1499个节点,那太恐怖了。内存根本放不下。
接下来我们换一种策略。
树不仅可以用链式存储,也可以用数组存储起来,例如堆。
解法二:利用数组存储二叉树的方式,给结点编号
对一个结点i,如何找它的左右孩子,有两种方法如下:
有这个启发之后,那为何不给树每个结点都加上一个编号,孩子是null结点就不用在放入队列了,队列中只存储有孩子的结点和编号,然后这一层把第一个结点的编号和最后一个结点的编号拿出来,然后 最后一个结点的编号 减去 第一个结点的编号 + 1 不就是这一层宽度吗!
queue<pair<TreeNode*,int>>
还是这一层出队之前把第一个结点和最后一个结点拿到,取出里面的编号,做对应的计算。 如 7 - 4 + 1 = 4,14 - 8 + 1 = 7。比较每层宽度取最大的宽度。
上面是用队列来做的,甚至可以不用队列,就用两个数组就可以模拟队列。因此
vector<pair<TreeNode*,int>> 也是可以的。
这里还有一个细节问题:下标有可能会溢出。还是刚才的例子,左边来1500个节点,右边来1500个节点。最后一个节点编号是2^1500 - 1,不管用什么类型都存不下!别说这里int,甚至double都不行。
难道要用字符串来存,然后做高精度加减吗,其实也不用, 因为我们最后是做减法的,即使溢出,但是相减的结果也是正确的。
我们的数据存储其实是可以看作一个环,如char -128 ~ 127
虽然正数会溢出到负数, 但是 我们做减法求的是两个数之间的距离,当两个数相减就会把结果修正,因为求的是之间的距离,这段距离是正的并且不会溢出。
但是前提不能是超过一圈然后到的这个位置。此时减肯定是一个错误。但是这道题告诉题目数据保证答案将会在 32 位 带符号整数范围内。因此pair 内 int 换成 unsigned int
vector<pair<TreeNode*,unsigned int>>
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int widthOfBinaryTree(TreeNode* root) {
queue<pair<TreeNode*,unsigned int>> qu;
unsigned int ret = 0;
if(root == nullptr) return ret;
qu.push(make_pair(root,1));
while(!qu.empty())
{
//先更新这一层的宽度
//pari对象里面的成员会分别存到 first -> x1, second -> y1
auto& [x1,y1] = qu.front();
auto& [x2,y2] = qu.back();
ret = max(ret, y2 - y1 + 1);
//让下一层入队
int k = qu.size();
while(k--)
{
auto [x,y] = qu.front();
qu.pop();
if(x->left) qu.push(make_pair(x->left,2 * y));
if(x->right) qu.push(make_pair(x->right,2 * y +1));
}
}
return ret;
}
};
数组模拟队列
class Solution {
public:
int widthOfBinaryTree(TreeNode* root) {
vector<pair<TreeNode*, unsigned int>> q; // ⽤数组模拟队列
q.push_back({root, 1});
unsigned int ret = 0;
while(q.size())
{
// 先更新这⼀层的宽度
auto& [x1, y1] = q[0];
auto& [x2, y2] = q.back();
ret = max(ret, y2 - y1 + 1);
// 让下⼀层进队
//数组队头不好出,那就不出,再来一个数组记录孩子节点,最后拷贝回去
vector<pair<TreeNode*, unsigned int>> tmp; // 让下⼀层进⼊这个队列
for(auto& [x, y] : q)
{
if(x->left) tmp.push_back({x->left, y * 2});
if(x->right) tmp.push_back({x->right, y * 2 + 1});
}
q = tmp;
}
return ret;
}
};
4.在每个树行中找最大值
题目链接:515. 在每个树行中找最大值
题目描述:
算法原理:
解法:利用层序遍历,统计出每一层的最大值
class Solution {
public:
vector<int> largestValues(TreeNode* root) {
vector<int> ret;
if(root == nullptr) return ret;
queue<TreeNode*> qu;
qu.push(root);
while(qu.size())
{
int k = qu.size();
int tmp = INT_MIN;
while(k--)
{
TreeNode* t = qu.front();
qu.pop();
tmp = max(tmp, t->val);
if(t->left) qu.push(t->left);
if(t->right) qu.push(t->right);
}
ret.push_back(tmp);
}
return ret;
}
};