题目描述
给出一个完全二叉树,求出该树的节点个数。
完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,
并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。
1. 普适方法
public int countNodes(TreeNode root) {
if (root == null){
return 0;
}
return countNodes(root.left) + countNodes(root.right) + 1;
}
2. 根据完全二叉树的性质简化遍历次数
这是一棵完全二叉树:除最后一层外,其余层全部铺满;且最后一层向左停靠
如果根节点的左子树深度等于右子树深度,则说明左子树为满二叉树
如果根节点的左子树深度大于右子树深度,则说明右子树为满二叉树
// depth为子树的深度;为了加快幂的运算速度,可以使用移位操作符
如果知道子树是满二叉树,那么就可以轻松得到该子树的节点数目:(1<<depth) - 1;
接着我们只需要接着对另一子树递归即可
class Solution {
public int countNodes(TreeNode root) {
if(root == null){
return 0;
}
//注意这里只能用root.left;因为2^n正好将根节点给算进去了,如果左子树是满二叉树,那么节点总数是2^n-1,加上根节点正好为2^n
int left = countLevel(root.left);
//同理,用root.right;
int right = countLevel(root.right);
if(left == right){
return countNodes(root.right) + (1<<left);
}else{
return countNodes(root.left) + (1<<right);
}
}
private int countLevel(TreeNode root){
int level = 0;
while(root != null){//while(root.left != null)是用来计算深度-1,仅仅差了.left,只是为了告诉自己编程多么简单。
level++;
root = root.left;
}
return level;
}
}
3. 二分查找
根据完全二叉树的性质,我们可以清楚的知道:总节点数 = 倒数第二层以上的节点数 + 最后一层的节点数
怎么判断最后一层的节点是否存在,比如说上图中的6。
给定最后一层某节点的位置索引 index,将他和分界线比大小,就可以判断该节点在左子树还是右子树,例如:现在查找6这个节点,索引为3,大于分界线2,所以6在右子树;对右子树重复操作即可,剩下的工作就交给迭代了~~~
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
// 求二叉树的深度
public int countLevels(TreeNode root) {
int levels = 0;
while (root.left!=null) {
root = root.left;
levels++;
}
return levels;
}
/*
* 功能: 判断最后一层第index个索引是否存在
* root: 二叉树根节点
* index:判断最后一层索引为index的节点是否存在, 索引范围是[1, 2^depth]
* depth:倒数第二层的深度, 这是因为满二叉树最后一层的节点数等于 2^depth
*/
public boolean is_exist(TreeNode root, int index, int depth) {
TreeNode node = root;
while (depth!=0) {
// 最后一层分界线
int mid = ((1 << depth) >> 1);
if (index > mid) {
// 如果在右子树,需要更新索引值
index -= mid;
node = node.right;
}
else {
node = node.left;
}
//因为已经判断出是否在左右子树了,所以--的目的是将分界线除以2。
depth--;
}
return node != null;
}
public int countNodes(TreeNode root) {
// 3. 二分查找
if (root == null) return 0;
// 倒数第二层二叉树深度
int depth = countLevels(root);
int start = 1, end = (1 << depth), mid = 0;
while (start <= end) {
mid = start + ((end - start) >> 1);
if (is_exist(root, mid, depth)) {
start = mid + 1;
}else {
end = mid - 1;
}
}
// start - 1为最后一层节点数
int ret = (1 << depth) - 1 + start - 1;
return ret;
}
}
还是二分查找,另一种思路。
12的二进制为1100,第二层第一个节点是4,二进制表示为100,所以&操作为1,这样子可以判断在左子树还是右子树,并且index值不用变化。
具体做法是求最后一层的中间节点mid,对上一层的第一个节点做&操作,如果&操作结果是0,说明在左子节点上,如果&操作为1,说明在右子节点上。
class Solution {
public int countNodes(TreeNode root) {
if (root == null) {
return 0;
}
int level = 0;
TreeNode node = root;
while (node.left != null) {
level++;
node = node.left;
}
int low = 1 << level, high = (1 << (level + 1)) - 1;
//一定要用<=
while (low <= high) {
int mid = (high - low) / 2 + low;
if (exists(root, level, mid)) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return low - 1;
}
public boolean exists(TreeNode root, int level, int k) {
int bits = 1 << (level - 1);
TreeNode node = root;
while (node != null && bits > 0) {
//要加双括号
if ((bits & k) == 0) {
node = node.left;
} else {
node = node.right;
}
bits >>= 1;
}
return node != null;
}
}