目录
树的层次遍历问题
1 层次遍历简介
二叉树的层次遍历是一个比较简单的问题,层次遍历又叫广度优先,从根节点开始,先访问根节点下面一层全部元素,再访问之后的层次,类似金字塔一层层访问,基本过程如下:
2 树的层次遍历
2.1 二叉树普通遍历
给定一个二叉树,将其按层遍历得到节点值。(逐层地,从左到右访问所有节点)
例如给定的二叉树为:
返回结果:
[
[3],
[9,20],
[15,7]
]
做法比较容易,先将根节点放到队列中,访问完当前节点后就将其左右节点进入队列,然后不断遍历队列。那如何判断某一层访问完了呢?只需用一个变量size标记一下即可,size表示某一层元素个数,只要出队,size就减一,减到0说明该层元素访问完。这时队列中剩余元素个数恰好就是下一层元素的个数,因此重新将size标记为下一层的元素个数就可以继续处理新的一行。最后,我们把每层遍历到的节点都放入到一个结果集并将其返回即可。
具体实现代码如下:
public static List<List<Integer>> level102Order(TreeNode root) {
if (root == null) {
return new ArrayList<List<Integer>>();
}
List<List<Integer>> res = new ArrayList<List<Integer>>(); // 存放结果队列
LinkedList<TreeNode> queue = new LinkedList<TreeNode>(); // 存放遍历节点的队列
//将根节点放入队列中,然后不断遍历队列
queue.add(root);
while (queue.size() > 0) {
//获取当前队列的长度,这个长度相当于 当前这一层的节点个数
int size = queue.size();
ArrayList<Integer> tmp = new ArrayList<Integer>();
//将队列中的元素都拿出来(也就是获取这一层的节点),放到临时list中
//如果节点的左/右子树不为空,也放入队列中
for (int i = 0; i < size; ++i) {
TreeNode t = queue.remove();
tmp.add(t.val);
if (t.left != null) {
queue.add(t.left);
}
if (t.right != null) {
queue.add(t.right);
}
}
//将临时list加入最终返回结果中
res.add(tmp);
}
return res;
}
2.2 二叉树自底向上遍历
给定一个二叉树,返回其节点值自底向上的层序遍历(按从叶子节点所在层到根节点所在层,逐层从左向右遍历)例如给定的二叉树为:
返回结果:
[
[15,7],
[9,20],
[3]
]
从上到下输出每一层的节点值,只需在遍历完一层结点之后,将存储该层节点值的列表添加到结果列表头部即可。
为了降低在列表头部添加一层节点值的列表的时间复杂度。结果列表可以使用链表结构,在链表头添加一层节点值的列表的时间复杂度为O(1). 具体实现代码如下:
public static List<List<Integer>> levelOrderBottom(TreeNode root) {
// 存储输出结果的链表
List<List<Integer>> levelOrder = new LinkedList<List<Integer>>();
if (root == null) {
return levelOrder;
}
Queue<TreeNode> queue = new LinkedList<TreeNode>(); // 存放遍历节点的队列
queue.offer(root);
while (!queue.isEmpty()) {
List<Integer> level = new ArrayList<Integer>(); // 存放该层节点值的列表
// 获取当层节点数
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
level.add(node.val);
TreeNode left = node.left, right = node.right;
// 若节点左右孩子非空,将其加入对列
if (left != null) {
queue.offer(left);
}
if (right != null) {
queue.offer(right);
}
}
levelOrder.add(0, level);// 将该层的节点值列表存入结果列表
}
return levelOrder;
}
2.3 二叉树锯齿形遍历
给定一个二叉树,返回其节点值的锯齿形层序遍历(先从左往右,再从右往左进行下一层遍历,层与层之间交替进行)例如给定如下二叉树:
返回结果:
[
[3]
[20,9],
[15,7],
]
这种遍历为第一种方法的变种,只是按层数奇偶来决定每一层的输出顺序,如果为偶数,从左往右输出,否则从右往左输出。我们可以利用【双端队列】来维护当层节点值输出的顺序。对当层节点我们可以维护一个变量isOrderLeft记录是从左到右还是从右到左。
如果从左往右输出,我们每次将遍历到的元素插入至双端队列的末尾。
如果从右往左输出,我们每次将遍历到的元素插入至双端队列的头部。
具体实现代码如下:
public static List<List<Integer>> zigzagLevelOrder(TreeNode root) {
// 存放输出结果的列表
List<List<Integer>> ans = new LinkedList<List<Integer>>();
if (root == null) {
return ans;
}
Queue<TreeNode> queue = new LinkedList<TreeNode>(); // 存放遍历节点的链表
queue.offer(root);
boolean isOrderLeft = true; // 记录遍历顺序的变量
while (!queue.isEmpty()) {
// 存储当层节点值的双端队列
Deque<Integer> levelList = new LinkedList<Integer>();
int size = queue.size();
for (int i = 0; i < size; ++i) {
TreeNode curNode = queue.poll();
if (isOrderLeft) {
// isOrderLeft为true,则插入到双端队列末尾,则实现从左往右输出
levelList.offerLast(curNode.val);
} else {
// isOrderLeft为false,则插入到双端队列头部,则实现从右往左输出
levelList.offerFirst(curNode.val);
}
if (curNode.left != null) {
queue.offer(curNode.left);
}
if (curNode.right != null) {
queue.offer(curNode.right);
}
}
ans.add(new LinkedList<Integer>(levelList));
isOrderLeft = !isOrderLeft;
}
return ans;
}
2.4 N叉树的层次遍历
给定一个N叉树,返回其节点值的层序遍历。(从左往右,逐层遍历)
例如给定如下N叉树:
返回结果:
[
[1]
[3,2,4],
[4,6]
]
这个其实也只是第一种的扩展,与二叉树基本一样,借助队列便可实现,实现代码如下:
public static List<List<Integer>> nLevelOrder(NTreeNode root) {
List<List<Integer>> value = new ArrayList<>();
Deque<NTreeNode> q = new ArrayDeque<>();
if (root != null)
q.addLast(root);
while (!q.isEmpty()) {
Deque<NTreeNode> next = new ArrayDeque<>();
List<Integer> nd = new ArrayList<>();
while (!q.isEmpty()) {
NTreeNode cur = q.pollFirst();
nd.add(cur.val);
for (NTreeNode chd : cur.children) {
if (chd != null)
next.add(chd);
}
}
q = next;
value.add(nd);
}
return value;
}
3 处理每层元素的题目
3.1 在每个树行中找最大值
给定一棵二叉树的根节点root,找出该二叉树中每一层的最大值。
这里其实就是在遍历完得到一层之后用一个变量来记录该层的最大值,实现代码如下:
public List<Integer> largestValues(TreeNode root) {
List<Integer> res = new ArrayList<>(); // 存放结果的数组
Deque<TreeNode> deque = new ArrayDeque(); // 存放树节点的队列
if(root != null) deque.addLast(root); // 放入树的根节点
while(!deque.isEmpty()){
int size = deque.size(); // 存每层的节点数量
int levelMax = Integer.MIN_VALUE; // 存每层最大值
for(int i = 0; i < size; i++){
TreeNode node = deque.poll();
levelMax = Math.max(node.val, levelMax); // 更新层最大值
// 将出队节点的左右孩子进队
if(node.left != null) deque.addLast(node.left);
if(node.right != null) deque.addLast(node.right);
}
res.add(levelMax); // 将每层最大值添加到结果数组
}
return res;
}
3.2 二叉树的右视图
给定一个二叉树的根节点root,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
其实也很简单,利用BFS进行层次遍历,然后记录下每层的最后一个节点值。
public List<Integer> rightSideView(TreeNode root) {
// 存放结果的列表
List<Integer> res = new ArrayList<>();
if(root==null) return res;
// 存放遍历的节点
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
for(int i = 0;i < size;i++){
TreeNode node = queue.poll(); // 最后记录到每层的最右元素
if(node.left!=null) queue.offer(node.left);
if(node.right!=null) queue.offer(node.right);
if(i==size-1) res.add(node.val); // 遍历到最后一个节点则为最右的元素
}
}
return res;
}