如何优雅地求完全二叉树的节点个数

176 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第29天,点击查看活动详情 >>

如何优雅地求完全二叉树的节点个数

二叉树是一种非常常考而且高频的数据结构,在本篇文章中,我们将一起来学习一下如何优雅地求完全二叉树的节点个数。

话不多说,咱们出发吧!

前置知识准备

什么是完全二叉树?

完全二叉树比普通的二叉树多了点限制,它是指除了最底下那一层可以不填满之外,其他各层都要填满,即每个结点都要有两个子节点。

注意:完全二叉树在最底层的虽然不要求全部填满,但是要求如果有节点的话,都是在左边集中的。

什么是满二叉树?

这次明明要学习的是完全二叉树,那么为什么还要介绍满二叉树呢?其实最主要是因为在后面的解法中我们会用到满二叉树的一个结论。

如果理解了完全二叉树的话,那么满二叉树就更加好理解了。

满二叉树是指各层都要填满,即叶子结点只出现在最底层,且二叉树各层都填满。

求完全二叉树的节点个数

普通解法

当然啦,完全二叉树是一种特殊的二叉树,要求完全二叉树的节点数,完全可以用二叉树的求解方法来,比如其中一种方法:在遍历二叉树的过程中,统计节点个数。

二叉树的遍历是会遍历到所有节点的,自然也能统计出该二叉树的所有节点个数。

但是呢,

本次最主要想介绍方法并不是这种,因为如果把完全二叉树当做二叉树来求解,明显就没能做到条件的充分利用。

下面我们来学习一种是专门针对完全二叉树的解法。

针对完全二叉树的解法

首先我们要明白如果对完全二叉树进行拆分的话,在下面肯定是会存在满二叉树的,最差的情况就是知道叶子结点才找到满二叉树(单个结点也是满二叉树)

下面我们来看看具体实现:

递归终止条件:

if (root == null) {
    return 0;
}

求以当前结点为根节点,其左右子树的深度:

// 求左子树深度
while (left != null) {  
	left = left.left;
	L_Height++;
}
// 求右子树深度
while (right != null) { 
	right = right.right;
	R_Height++;
}

当左右子树的深度相等,说明以当前结点为根节点的树是满二叉树。如果是满二叉树,则返回整个满二叉树的所有节点数:

/**
 * 当左右子树的深度相等,说明以当前结点为根节点的树是满二叉树
 * 如果是满二叉树,则返回整个满二叉树的所有节点数
 */
if (L_Height == R_Height) {
    /**
     * (2 << L_Height) - 1; 等效于 2 * 2 ^ L_Height
     * 是计算满二叉树的所有结点数的公式
     * // 注意(2<<1) 相当于2^2,所以L_Height初始为0
     */
    return (2 << L_Height) - 1; 
}

经过了以上分析,我们很容易就可以写出完整的求解代码:

class Solution {
	public int countNodes(TreeNode root) {
		if (root == null) {
			return 0;
		}
		TreeNode left = root.left;
		TreeNode right = root.right;
		int L_Height = 0, R_Height = 0; // 这里初始为0是有目的的,为了下面求指数方便
		while (left != null) {  // 求左子树深度
			left = left.left;
			L_Height++;
		}
		while (right != null) { // 求右子树深度
			right = right.right;
			R_Height++;
		}
		/**
		 * 当左右子树的深度相等,说明以当前结点为根节点的树是满二叉树
		 * 如果是满二叉树,则返回整个满二叉树的所有节点数
		 */
		if (L_Height == R_Height) {
			/**
			 * (2 << L_Height) - 1; 等效于 2 * 2 ^ L_Height
			 * 是计算满二叉树的所有结点数的公式
                         * // 注意(2<<1) 相当于2^2,所以L_Height初始为0
			 */
			return (2 << L_Height) - 1; 
		}
		/**
		 * 如果不是满二叉树的话,就直接处理左右子树的节点数即可
		 */
		return countNodes(root.left) + countNodes(root.right) + 1;
	}
}

总结:

上面已经介绍了基于完全二叉树的结点个数求解过程,下面我们再来总结提炼几点:

其实在二叉树中递归的做法都是从叶子结点层往上处理(从底层往上处理、计数) 因为一个结点的树肯定是满二叉树,所以在递归到完全二叉树底层的时候,肯定会出现满二叉树的 这里的情况又两种: 1、如果当前递归遇到了满二叉树,则计算整个满二叉树的所有节点数,并返回 2、如果当前递归不是满二叉树,则只需处理左右子树的结点数即可 这里的处理是指: 如果左子树是满二叉树(可能只有一个结点),则 left = 左边整个二叉树的节点数 如果右子树是空节点,则 right = 0; 其他情况以此类推 注意:上面说的处理是就底层而言的,当递归返回到高层的时候,left 和 right 记录的是 从当前左右节点往下的所有节点数 总的来说的,通过不断递归返回将满二叉树和非满二叉树的两种情况的结点数整合到了一起。

\