【算法游戏】判断完全二叉树的节点是否存在
HALO!我是CW。
第一次见面,不搞太复杂的内容,有关CV领域的内容会在后续输出(先酿一酿,更香~)。这次先分享个算法方面的技巧,相关的数据结构是coder们都熟悉的二叉树,大家怀着轻松愉快的心情当作玩游戏就好。
OK,大家现在就和CW一起来玩玩吧~!
01
—
游戏规则
假设一棵完全二叉树高度为h(h≥1),其中每个节点按照层序遍历的次序进行编号(从1开始,即根节点的编号为1),比如像下面这样:
当前你只知道根节点的存在(比如你只能看到上图那棵树中编号为1的节点),通过根节点你可以到达树中的任意节点,每个节点的数据结构表示为如下形式:
class TreeNode:
def __init__(self, num, left=None, right=None):
# 节点编号
self.num = num
# 左孩子
self.left = left
# 右孩子
self.right = right
02
—
脑洞
当然,“勤奋”的孩子会用暴力解法,从根节点出发,将所有路径都搜索一遍(也是不怕累哦..)。不过,CW是叫你来玩游戏的,别搞这种无聊的风格,不好玩
想要好玩,当然得动脑!我们可以脑洞一下,看看根据当前情况,有哪些东西是可以利用的,或者说,根据已知条件可以计算出哪些有利于我们通关的辅助条件。
首先,我们要理解游戏规则,从已知条件中明确一些重要信息。比如,完全二叉树有什么特点?CW觉得,最明显的是:
叶子结点只会出现在最底层和次底层,且最底层的叶子结点仅会集中在树的左部。并且,除最底层以外,其余层的节点数一定达到最大值(当然,最底层的节点数也可能达到最大值,此时的完全二叉树也称为满二叉树)。
来张图更形象些:
另外,按层序遍历的次序来编号就是怎样?CW就引用幼儿园的话术来解释下吧:
对树中的节点按从上至下、从左到右的顺序进行编号
例如:
OK,规则中的术语理解得差不多了,我们不妨大胆些。比如,如果我们知道树中的节点数量为n(n≥1),那么树中的节点编号范围就是[1, n],咦!那这岂不是变成了白痴问题了吗..(i大于n的话节点就肯定不存在了)
尽管有些天真,但我们还是可以往这个点上靠:当前已知完全二叉树的高度为h,根据完全二叉树的性质,可知这棵树至少有2^(h-1)个节点、至多有2^h - 1个节点。进一步,这就隐含着:位于最底层的节点,其编号范围为[2^(h-1), 2^h - 1]。
你看啊,看似天真的想法,靠一靠还是能靠出些东西来的。因此,千万不要嘲笑任何一个看似愚蠢的idea,如果你有这种态度,就恰恰说明了你的愚蠢!
既然我们已经计算出最底层节点的编号范围,那么只要确定i与这个范围的关系,差不多就可以通关了:
-
若i ≤ 2^(h-1),那么节点i一定存在; -
若i > 2^h - 1,那么节点i一定不存在; -
若2^(h-1) < i ≤ 2^h - 1,此是节点i位于最底层,需要进一步判断其存在性
于是,我们的目标转为对上述最后一组种情况进行求解。
03
—
基于位运算的路径搜索
当节点i位于最底层时,我们需要从根节点出发,搜索到节点i的路径,若最终返回空,则说明节点不存在。
这里分享一个好玩的trick,使得在路径搜索过程中每一步都能按正确的方向走。
这个trick使用一种很妙的做法:将i转换为二进制表示,除最高位以外,其余每位都指示着从根节点去往节点i的路径方向——0表示往左子树走,1表示往右子树走。
为了形象说明,这里举个例子,比如当前树的高度h=3,让你判断节点i=6是否存在。具体来说,是这样做的:
-
将i转换为二进制,这里i=6,转换结果就是101,去掉最高位剩下01,记为j; -
设置一个模式串,其最高位与j对齐,且为1,其余位都是0,记为bits,于是bits=10; -
从根节点出发,将j与bits按位进行与(and&)操作,结果为0,于是走向左子树,同时bits右移一位,于是bits更新为01; -
继续将j与bits按位进行与操作,结果为1,于是这次向右子树走,bits再右移一位之后已经没有任何位剩下了,于是结束搜索; -
此时,若走到的节点为空,则说明节点i不存在;否则,这个节点就是节点i
以上过程,可用以下代码来表示:
node = root
bits = 1 << (h - 2)
while node and bits:
if bits & j:
node = node.right
else:
node = node.left
bits >>= 1
return node
04
—
通关
现在来将上述过程汇总下,就可以实现通关了。
首先利用已知条件中树的高度h计算出最底层节点的编号范围是[2^(h-1), 2^h - 1],然后根据目标节点的编号i与该区间的关系来执行相应操作:
-
若i ≤ 2^(h-1),则直接通关:节点i存在; -
类似地,若i > 2^h,则同样可以直接通关:节点i不存在; -
若2^(h-1) < i ≤ 2^h,则使用上一节的trick来确定节点i是否存在
05
—
结语
初次见面,没有什么高大上的内容,仅本着好玩且务实(结合代码)的风格和大家学习和交流,文章排版什么的也挺low,还请各位不要介意。
点击下方卡片,让我们成为好朋友一起玩~