层序二叉树(含完全二叉树)O(1)判断节点所属子树的方法
嘿,这个问题问到点子上了!层序完全二叉树的结构化特性刚好能让我们避开遍历,用数学方法实现O(1)时间复杂度的判断。我来一步步给你讲明白:
一、基础逻辑(针对层序存储的完全二叉树)
首先得明确:层序完全二叉树的节点是严格按「从上到下、从左到右」的顺序排列的,我们可以给每个节点分配一个全局编号(整棵树的根为1,左子节点是2,右子节点是3,下一层左到右依次是4、5、6、7,以此类推)。这个编号和节点位置有固定的数学关系:
- 编号为
n的节点,父节点编号是floor(n/2) - 左子节点编号是
2n,右子节点是2n+1
如果你的树节点值是按层序顺序赋值的(比如整树根是2,左子3、右子4,再下一层左到右5、6、7、8...),那节点值和全局编号的对应关系很简单:节点值 = 整树根值 + 全局编号 - 1。
要判断某个值v属于当前根节点(值为root_val)的左/右子树,核心就是先把值转换成全局编号,再利用编号的特性判断。
二、O(1)时间复杂度的实现方案
不用遍历的关键,是利用二进制编码的路径特性——完全二叉树的全局编号转成二进制后,去掉最高位的1,剩下的每一位其实就是从根到该节点的路径(0代表左,1代表右)。结合这个特性,我们可以直接定位:
步骤1:把节点值转成全局编号
假设整棵树的根节点值是global_root(比如你例子里的整树根是2),那任意节点值对应的全局编号可以这么算:
全局编号 = 节点值 - global_root + 1
拿你的例子验证:
- 根节点2的全局编号:
2 - 2 + 1 = 1 - 节点15的全局编号:
15 - 2 + 1 = 14 - 节点3的全局编号:
3 - 2 + 1 = 2 - 节点10的全局编号:
10 - 2 + 1 = 9
步骤2:先确认v是当前根的后代
首先得排除v根本不是当前根节点后代的情况。我们可以把两个全局编号转成二进制字符串:
- 如果
v的二进制长度比当前根的短,肯定不是后代 - 否则,看
v的二进制前缀是否和当前根的二进制完全一致——一致的话就是后代,否则不是
比如:
- 当前根2的二进制是
1,节点15的二进制是1110,前缀匹配,是后代 - 当前根3的二进制是
10,节点10的二进制是1001,前缀匹配,是后代
步骤3:判断左/右子树
如果确认是后代,我们只需要看v的二进制中,紧跟在当前根二进制后面的第一位:
- 这一位是
0→ 左子树 - 这一位是
1→ 右子树
再拿你的例子验证:
- 根2(二进制
1),v=15(二进制1110):
去掉和根匹配的前缀1,剩下的字符串是110,第一位是1→ 右子树,完全符合你的预期。 - 根3(二进制
10),v=10(二进制1001):
去掉匹配的前缀10,剩下的字符串是01,第一位是0→ 左子树,也和你的例子一致。
伪代码实现
def judge_subtree(global_root, current_root_val, target_val): # 计算全局编号 root_num = current_root_val - global_root + 1 target_num = target_val - global_root + 1 # 转成二进制字符串(去掉Python自带的'0b'前缀) root_bin = bin(root_num)[2:] target_bin = bin(target_num)[2:] root_len = len(root_bin) target_len = len(target_bin) # 先判断是否为后代节点 if target_len <= root_len or target_bin[:root_len] != root_bin: return "该节点不是当前根的后代" # 判断左/右子树 next_bit = target_bin[root_len] return "左子树" if next_bit == '0' else "右子树"
测试一下你给的案例:
judge_subtree(2, 2, 15)→ 返回"右子树"judge_subtree(2, 3, 10)→ 返回"左子树"
补充说明
如果你的树节点值不是连续的,但仍然是严格按层序存储的(比如有固定的映射表对应节点值和全局编号),只需要把步骤1的全局编号计算替换成你的映射规则就行,核心的二进制判断逻辑是通用的。
内容的提问来源于stack exchange,提问作者Barshan Das




