2021/02/13
我是星,亲手点燃黑暗森林的火星
层次遍历是树的遍历方式之一,树的很多相关操作都可以使用层次遍历的思想解决,比如求树的高度,求树的宽度,判断是否是完全二叉树等。
树的层次遍历
层次遍历是指按照树的层级顺序遍历节点。如下面这颗二叉树一共有三层,层次遍历的结果为:1,2,3,4,5,6。
层次遍历的核心是使用一个队列,首先将根节点入队。循环如下操作,直到队列为空:
- 出队,记为
p - 如果
p的左孩子不为空,左孩子入队 - 如果
p的右孩子不为空,右孩子入队
以上面的二叉树为例,层次遍历的过程如下图所示:
层次遍历的Java实现如下:
public void levelOrder() {
if (isEmpty()) {
return;
}
Queue<BinaryNode> queue = new LinkedList<>();
queue.offer(this.root);
BinaryNode<E> node = null;
while (!queue.isEmpty()) {
node = queue.poll();
System.out.print(node.element + " ");
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
}
树的高度
递归法求高度
树的高度很容易想到使用递归的方式求解,一棵树的高度=左右子树中较高的子树的高度+1,左右子树的高度又可以继续使用这个公式求解,这就是递归的方法。递归的实现也非常容易:
public int heightWithRecursion(BinaryNode root) {
if (root == null) {
return 0;
}
int leftHeight = heightWithRecursion(root.left);
int rightHeight = heightWithRecursion(root.right);
return (leftHeight > rightHeight ? leftHeight : rightHeight) + 1;
}
利用层次遍历求高度
由于层次遍历是按层遍历,每遍历完一层的节点,高度就增加1,因此可以用来求树的高度。
在层次遍历的基础上,为了记录高度,需要一个变量(我定义为int levelSize)来记录某一层还未遍历的节点个数,默认初始为1(第一层只有一个根节点),层次遍历队列每出栈一个元素,则将levelSize减1。当levelSize==0时,代表某一层遍历完成了,此时高度增加1,并将当前队列的元素个数赋给levelSize,继续层次遍历。
求树的高度只需要在层次遍历的代码基础上稍作修改:
public int height(BinaryNode<E> root) {
if (isEmpty()) {
return 0;
}
int levelSize = 1;
int height = 0;
Queue<BinaryNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
BinaryNode<E> node = queue.poll();
levelSize--;
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
if (levelSize == 0) {
height++;
levelSize = queue.size();
}
}
return height;
}
树的宽度
树的宽度是指:节点个数最多的那一层的节点个数。和求高度类似,可以利用层次遍历来解决,并且和求高度的过程很相似。
在求高度的代码中,我们增加了一个变量levelSize来记录某一层还未遍历的节点个数,在树不为空的情况下,令默认宽度width=1,在levelSize==0时,此时队列的元素个数即为下一层的节点总数,这时可以和width比较,如果大于width,则修改新的宽度。
只需要在修改levelSize==0时的逻辑即可:
public int width() {
if (isEmpty()) {
return 0;
}
int width = 1;
int levelSize = 1;
Queue<BinaryNode> queue = new LinkedList<>();
queue.offer(this.root);
BinaryNode<E> node = null;
while (!queue.isEmpty()) {
node = queue.poll();
levelSize--;
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
if (levelSize == 0) {
// 这时的levelSize是下一层的节点个数
levelSize = queue.size();
if (levelSize > width) {
width = levelSize;
}
}
}
return width;
}
判断一棵树是否位完全二叉树
判断一棵树是否是完全二叉树,同样可以利用层次遍历的思想,只不过某个节点如果没有左或右孩子时,也要进行相应的操作,具体规则如下:
- 如果节点有左、右孩子,正常加入队列
- 如果一个节点没有左孩子,有右孩子,那么一定不是完全二叉树
- 如果一个节点的左孩子为空,那么所有在此之后遍历的节点必须全是叶子节点,才是完全二叉树
上面的规则中,最后一个规则需要判断剩余的节点是否全是叶子节点,可以增加一个变量boolean mustAllLeaf,默认为false。
在层次遍历的基础上,修改代码:
public boolean isCompleteBinaryTree() {
if (this.root == null) {
return false;
}
Queue<BinaryNode> nodeQueue = new LinkedList<>();
nodeQueue.offer(root);
boolean mustAllLeaf = false;
while (!nodeQueue.isEmpty()) {
BinaryNode<E> currentNode = nodeQueue.poll();
// 如果必须全为叶子节点,而存在非叶子节点,直接判定非完全二叉树
if (mustAllLeaf && !isLeaf(currentNode)) {
return false;
}
if (currentNode.left != null) {
nodeQueue.offer(currentNode.left);
} else if (currentNode.right != null) {
// 无左孩子有右孩子直接判定非完全二叉树
return false;
}
if (currentNode.right != null) {
nodeQueue.offer(currentNode.right);
} else {
mustAllLeaf = true;
}
}
return false;
}