赞
踩
题目链接:222. 完全二叉树的节点个数
这个就不多说了,毕竟各位年轻人不讲码德,笔者在此点到为止。
★ C/C++
class Solution {
public:
int countNodes(TreeNode* root) {
if(!root)
return 0;
int left = countNodes(root->left);
int right = countNodes(root->right);
return left + right + 1;
}
};
时间复杂度: O ( n ) O(n) O(n) 设节点数为 n n n,考虑最坏情况下,完全二叉树为满二叉树,算法运行时间 T ( n ) = 2 T ( n − 1 2 ) + O ( 1 ) T(n) = 2T(\frac{n-1}{2}) + O(1) T(n)=2T(2n−1)+O(1),我们将它放大为: T ( n ) ≤ 2 T ( n 2 ) + O ( 1 ) T(n) \le 2T(\frac{n}{2}) + O(1) T(n)≤2T(2n)+O(1),符合主定理的 C a s e 1 Case 1 Case1 (主定理的描述见此链接的"知识梳理"部分),即 T ( n ) = O ( n ) T(n) = O(n) T(n)=O(n);另一个角度,由于需要访问树中所有节点,每次访问开销为 O ( 1 ) O(1) O(1),故总开销为 O ( n ) O(n) O(n)。
空间复杂度: O ( l g n ) O(lgn) O(lgn) 空间开销主要为系统栈的开销,由于采用的是深度优先策略,故系统栈最大开销时对应 树的高度,对于完全二叉树而言,最好、最坏、平均情况均为 O ( l g n ) O(lgn) O(lgn) (可以想想为什么)。
当然,也可以采用广度优先策略,这里就不多说了,详见 LeetCode 题解。
充分利用 完全二叉树 的性质:高度为 h h h 时,第 i ∈ [ 0 , h − 1 ] i \in [0, h-1] i∈[0,h−1] 层节点数达到最大值 2 i 2^i 2i,第 h h h 层节点 全部靠左,数目在 [ 1 , 2 h ] [1, 2^h] [1,2h] 之间。
由此,如果我们知道了 树的高度 h h h, 那么除了最后一层外其余层的节点数我们可以在 O ( 1 ) O(1) O(1) 时间内计算出来。对于完全二叉树,树的高度可以从获取 最左边的节点 来得知, 这个过程的时间开销为 O ( l g n ) O(lgn) O(lgn)。
有了树高 h h h,我们就可以利用 完全二叉树 的性质,自顶向下、自左向右 地对各个节点进行 编号: 1 , 2 , 3 , . . . , k 1, 2, 3, ..., k 1,2,3,...,k, 其中 k k k 总节点数,为编号为 k k k 的节点, k ∈ [ 2 h , 2 h + 1 − 1 ] k \in [2^h, 2^{h+1}-1] k∈[2h,2h+1−1],然后我们需要解决的最后一个问题是:确定 ⌈ \lceil ⌈ 编号为 k k k 的节点存在时 ⌋ \rfloor ⌋ k k k 的最大值。
一个简单的方式:从同高度满二叉树的最大编号开始,按照递减的编号顺序,检验此编号的节点是否在该树中存在。从以上算法的描述中,我们可以注意到这个过程有两步,且很容易获得它们的时间开销:
这样我们的算法总开销反而比 ⌈ \lceil ⌈ 思路一 ⌋ \rfloor ⌋ 还高,为 O ( n l g n ) O(nlgn) O(nlgn)。因此,我们可以分别分析以上两个步骤,看看是否存在优化的空间。
根据完全二叉树的性质,最后一层的编号是从左到右 连续 的,具有 随机存取 特性,因此我们可以采用 二分查找 替换 线性查找,使得复杂度降至 O ( l g n ) O(lgn) O(lgn)。
我们来讨论 ⌈ \lceil ⌈ 检验编号是否存在 ⌋ \rfloor ⌋ 这一算法的实现方案,实际上,我们可以根据 编号的二进制编码 来找寻树中对应节点,采用这种方式的依据是:树为完全二叉树,编号对应的二进制数与节点的位置之间存在明确的联系 —— 节点对应的二进制数从 按高位的第2位 开始,每一位对应 0 或 1 ,可以分别映射为 向左节点移动 或 向右节点移动,这对于除了第 0 0 0 层外任意层的节点来说都成立。
以一个高度为 3 的完全二叉树为例,每个编码确实符合以上述映射关系。
1 1
/ \
2 3 10, 11
/ \ /\
4 5 6 7 100, 101, 110, 111
/\ /
8 9 10 1000, 1001, 1010
可能有年轻人会问,为什么只有完全二叉树可以,其他类型的树不行呢?
我的理解是:其他类型树的节点编号没有上述那么明确的映射关系,即编号的二进制编码不能指导我们找到节点所在位置;至于完全二叉树,它是满二叉树于最后一层从右向左依次剔除一些节点后形成的树。 满二叉树标识了特定规模问题的 全集,假定问题规模为
n
n
n,则全集中非空子集的总数为
2
n
−
1
2^n-1
2n−1,也就是满二叉树的节点总数。而根据二项式定理,我们有:
2
n
=
(
1
+
1
)
n
=
∑
i
=
0
n
(
n
i
)
×
1
a
n
−
i
×
1
b
i
2^n = (1+1)^n = \sum\limits_{i=0}^{n}\tbinom{n}{i}\times1_a^{n-i} \times 1_b^{i}
2n=(1+1)n=i=0∑n(in)×1an−i×1bi
式中的 1 a 1_a 1a、 1 b 1_b 1b 构成了两种不同的状态,我们利用这两种状态,分别模拟 向左子树移动 和 向右子树移动 …
大意了,扯远了。这段我这个老同志自己也没搞得很清楚,这些都是瞎哔哔的,就当图一乐了(若有哪位年轻人能解释清楚,欢迎在评论区分享~ ),但至少知道这么编码可行就好了。
确定了编码方案可行,再来看看这个算法的具体过程。可以举个具体例子:你可以想象有一只蜘蛛位于一个树形蜘蛛网上(见上一个图例),它想要确认编号 10 10 10 的网节点是否存在,于是从根节点出发,然后根据 10 10 10 的二进制编码 1010 1010 1010 来找寻,从第 2 2 2 位起:
按照上述过程来实现算法是非常简单的,详见代码的 exists()
函数,或去看官方题解,这里就不作赘述了。(虽然是用 Python3 写的,但基本思路不会随语言而变化,改用其他语言实现是很容易的)
★ Python3
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: '''利用完全二叉树的编码特性,判断第k个节点是否存在''' def exists(self, root: TreeNode, level: int, k: int) -> bool: bits: int = 1 << (level-1) while root and bits > 0: if (k&bits) > 0: root = root.right else: root = root.left bits >>= 1 return root != None def countNodes(self, root: TreeNode) -> int: if not root: return 0 node: TreeNode = root level: int = 0 while node.left: level += 1 node = node.left low: int = 1 << level high: int = (1 << (level+1)) - 1 while low < high: mid: int = (low+high+1) // 2 if self.exists(root, level, mid): low = mid else: high = mid-1 return low
说明:
注意运算符优先级!(没错,我说的就是(1 << (level+1)) - 1
,其中的括号一个都不能少;还有按位与)
方便起见,我把百度百科上的优先级表搬了过来,省去年轻人 O ( 1 ) O(1) O(1) 的查找时间(笑):
二分查找 low = mid
,high = mid-1
以及 返回值 别弄错了
时间复杂度: O ( l g 2 n ) O(lg^2n) O(lg2n) 时间开销主要在二分查找的代码块中,二分查找开销为 Θ ( l g n ) \Theta(lgn) Θ(lgn),其中每一轮都有一个检查节点是否存在的过程,因为检查的是最后一层的节点,故开销为 Θ ( l g n ) \Theta(lgn) Θ(lgn),因此总开销为 O ( l g n × l g n ) O(lgn \times lgn) O(lgn×lgn)。
空间复杂度: O ( 1 ) O(1) O(1)
最后,可能又有年轻人问了:你这冗长而丑陋的 O ( l g 2 n ) O(lg^2n) O(lg2n),难道比我 递归 的 O ( n ) O(n) O(n) 要快吗?
没有错哈。这里,我作为讲码德的老同志,先给个结论:
对
于
任
意
常
量
α
>
0
,
恒
有
:
l
g
b
n
=
o
(
n
α
)
⟺
lim
n
→
∞
l
g
b
n
n
α
=
0
,
其
中
b
为
任
意
实
常
量
对于任意常量 \alpha \gt 0, \quad 恒有: lg^bn = o(n^\alpha) \\ \iff \lim\limits_{n \rightarrow \infty} \frac{lg^bn}{n^\alpha} = 0,\quad 其中 b为任意实常量
对于任意常量α>0,恒有:lgbn=o(nα)⟺n→∞limnαlgbn=0,其中b为任意实常量
具体的证明
⌈
\lceil
⌈ 非常复杂
⌋
\rfloor
⌋ ,就先交给各位年轻人了。(证明可见《CLRS》第三版 原版 P55-P57 或 中文版 P32-P33)
最后的最后,劝各位年轻人耗子尾汁,多讲码德,不要再犯 ⌈ \lceil ⌈ 思路一 ⌋ \rfloor ⌋ 这种小聪明,不要老在自己的 ⌈ \lceil ⌈小天地 ⌋ \rfloor ⌋ 里搞窝里斗,来骗,来偷袭,我这种 10101 10101 10101 岁的老同志。
谢谢朋友们 (\抱拳)
笔者水平有限,如果描述有什么不妥的地方,欢迎各位大佬在评论区批评指正~
…
…
…
仔细想想,算了,还是给各位年轻人省去查找的时间,在这里简单地证明一下吧。
对 于 所 有 使 得 a > 1 的 实 常 量 a 和 b , 多 项 式 的 增 长 比 不 过 指 数 : lim n → ∞ n b a n = 0 我 们 用 l g n 替 代 上 式 的 n , 用 2 α 替 代 a ( α > 0 ) 从 而 有 lim n → ∞ l g b n n α = 0 , 证 毕 对于所有使得 a \gt 1的实常量a和b ,\\ 多项式的增长比不过指数:\lim\limits_{n \rightarrow \infty}\frac{n^b}{a^n} = 0 \\ 我们用 lgn替代上式的n,用2^\alpha替代a\quad (\alpha>0)\\ 从而有\lim\limits_{n \rightarrow \infty} \frac{lg^bn}{n^\alpha} = 0,证毕 对于所有使得a>1的实常量a和b,多项式的增长比不过指数:n→∞limannb=0我们用lgn替代上式的n,用2α替代a(α>0)从而有n→∞limnαlgbn=0,证毕
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。