CC++ & Algorithm

二叉树的定义与性质

困难0
语言版本:C++
概述:二叉树是一种特殊的树,每个节点最多有两个子节点,分别叫左孩子和右孩子。

二叉树的定义与性质:让“树枝”变得有条理

什么是二叉树?

想象一下你正在组织一场“知识竞赛”,每个选手只能最多跟两个人比赛(比如一答一对,输了淘汰)。或者像你家的家族树,每对父母最多有两个孩子。这样的“分叉”结构在计算机里就叫二叉树

二叉树是一种很常见的树结构,它的特点是:每个节点(就像家族里的一个人)最多只能有两个“孩子”,分别叫左孩子右孩子。你可以没有孩子(叶子节点),也可以只有一个左孩子或只有一个右孩子,但不能超过两个。

生活中很多地方都有二叉树的身影:

  • 文件目录:一个文件夹下最多只能有两个子文件夹(虽然现实中文件夹可以有几十个,但简化后可以用来理解二叉结构)。
  • 体育比赛:淘汰赛中一次比赛分出两组,再往下分两组,最后决出冠军。
  • 猜数字游戏:每次问“比50大吗?”然后分成左右两个范围。

二叉树的核心思想就是:二分,每次把问题或数据分成两半,条理清晰,查询很快。


二叉树的有趣性质(几个小规律)

二叉树就像数学里的“2的次方”一样整齐,有几条特别好用的性质:

  1. 第 i 层最多有多少个节点?

    • 公式:第 i 层最多有 2^(i-1) 个节点(根节点算第 1 层)。
    • 例子:第 1 层(根)最多 2^(0) = 1 个节点;第 2 层最多 2^(1) = 2 个节点;第 3 层最多 2^(2) = 4 个节点;第 4 层最多 8 个…… 是不是像细胞分裂?
  2. 深度为 h 的二叉树最多有多少个节点?

    • 公式:深度为 h 的二叉树最多有 2^h - 1 个节点
    • 解释:把每一层最多的节点加起来:1 + 2 + 4 + … + 2^(h-1) = 2^h - 1。
    • 例子:深度为 3 的二叉树(从上到下共 3 层),最多 2^3 - 1 = 7 个节点。如果深度是 4,最多 15 个节点。
  3. 叶子节点数 = 度为 2 的节点数 + 1(这条性质以后会用到,现在先知道就好)

    • 是指一个节点有几个孩子。度为 2 就是有左右两个孩子。
    • 这条性质在计算一些题目时很有用,比如已知叶子节点个数,可以反推有 2 个孩子的节点个数。

你可能会问:“有没有刚好每个节点都有左右孩子的树?” 有的!如果一棵二叉树,每一层节点都是满的(除了最后一层可能不满),这叫满二叉树。如果最后一层从左到右连续,没有缺空,这叫完全二叉树。这些概念在以后学堆(Heap)时会用到。


用 Python 表示二叉树节点

在代码里,二叉树就像一条链条,每个节点是一个小盒子,盒子左边连着左孩子,右边连着右孩子。我们用 leftright 两个“指针”表示它们。如果某个孩子不存在,就用 None(空)表示。

下面是最基本的节点类,用来创建树:

class BinaryTreeNode:
    """二叉树的节点类"""
    def __init__(self, data):
        self.data = data          # 节点里存放的值(比如名字、数字)
        self.left = None          # 左孩子,初始时没有
        self.right = None         # 右孩子,初始时没有

创建节点的方法很简单:node = BinaryTreeNode("A"),然后手动把左右孩子挂上去。

# 创建一棵简单二叉树:根 A,左孩子 B,右孩子 C
root = BinaryTreeNode("A")        # 根节点A
root.left = BinaryTreeNode("B")   # 左孩子B
root.right = BinaryTreeNode("C")  # 右孩子C

# 给B加左孩子D和右孩子E
root.left.left = BinaryTreeNode("D")
root.left.right = BinaryTreeNode("E")

# 打印信息
print("根:", root.data)                    # 输出 A
print("左孩子:", root.left.data)           # 输出 B
print("右孩子:", root.right.data)          # 输出 C
print("左孩子的左孩子:", root.left.left.data)  # 输出 D
print("左孩子的右孩子:", root.left.right.data)  # 输出 E

这段代码构造了一棵这样的二叉树:

    A
   / \
  B   C
 / \
D   E

注意:C 节点还没有孩子,所以 root.right.leftNone(空),不能直接访问它的 data,否则会报错。


新手常犯的错误

  1. 忘记左右孩子可能为空

    # 错误写法
    print(root.right.left.data)   # 如果 right 没有左孩子,直接报错
    

    正确做法:先判断节点是否存在,例如 if root.right.left: print(root.right.left.data)

  2. 混淆左孩子和右孩子的顺序

    • 二叉树中左右是有意义的,比如在排序树中,左孩子比父节点小,右孩子比父节点大。如果挂反了,树的结构就错了。
  3. 认为每个节点必须有左右两个孩子

    • 实际上,节点可以有 0 个、1 个(左或右)、2 个孩子。只挂一个孩子也是合法的二叉树。
  4. 忘记初始化左右孩子为 None

    • 如果不写 self.left = None,那么节点创建时 left 可能是一个未定义的变量,之后赋值时容易出错。所以一定要在 __init__ 里初始化。
  5. 用列表或链表的思维访问节点

    • 比如想直接 root[0] 取左孩子,那是不行的。必须用 root.left 这样的属性。

完整可运行的示例:创建一棵二叉树并统计节点个数

下面这个程序创建了一棵稍大一点的二叉树(模拟一个家族树),然后写了一个函数来计算树里一共有多少个节点,并打印所有节点值。

class BinaryTreeNode:
    """二叉树的节点类"""
    def __init__(self, data):
        self.data = data          # 节点值
        self.left = None          # 左孩子
        self.right = None         # 右孩子

def count_nodes(node):
    """统计以node为根的二叉树中节点个数"""
    if node is None:              # 如果是空节点,返回0
        return 0
    # 总数 = 左子树个数 + 右子树个数 + 1(自己)
    return 1 + count_nodes(node.left) + count_nodes(node.right)

def print_tree(node, indent=0):
    """打印二叉树(缩进显示层次)"""
    if node is None:
        return
    # 先打印右子树(让它显示在最右边)
    print_tree(node.right, indent + 4)
    print(" " * indent + str(node.data))
    # 再打印左子树
    print_tree(node.left, indent + 4)

# 创建一棵树:模拟一家人的名字
#        爷爷
#       /    \
#     爸爸    叔叔
#    /   \      \
#  哥哥  姐姐  表妹
#
root = BinaryTreeNode("爷爷")          # 根节点:爷爷
root.left = BinaryTreeNode("爸爸")     # 左孩子:爸爸
root.right = BinaryTreeNode("叔叔")    # 右孩子:叔叔
root.left.left = BinaryTreeNode("哥哥")  # 爸爸的左孩子:哥哥
root.left.right = BinaryTreeNode("姐姐") # 爸爸的右孩子:姐姐
root.right.right = BinaryTreeNode("表妹")# 叔叔的右孩子:表妹(叔叔没有左孩子)

print("=== 家族树(用缩进表示层次) ===")
print_tree(root)

print("\n总共有多少个成员?")
total = count_nodes(root)
print("节点总数:", total)               # 输出 6

运行结果:

=== 家族树(用缩进表示层次) ===
        表妹
    叔叔
爷爷
        姐姐
    爸爸
        哥哥

总共有多少个成员?
节点总数: 6

注意:打印的树是“横着”的,根在左边,右孩子在上面,左孩子在下面。这是为了方便在文本里显示。


接下来可以学什么?

  • 树的遍历:前序、中序、后序——就像按不同顺序走访家族成员。
  • 二叉搜索树:左小右大,查找非常快,就像在字典里找单词。
  • 堆(Heap):一种特殊的完全二叉树,用来实现优先队列(比如游戏中的任务优先级)。
  • 递归算法:二叉树天生适合用递归处理(比如上面的 count_nodes 函数)。

二叉树是很多高效算法的基础,先把这个“树桩”打牢,以后的学习就会顺利多啦!

例题精讲

1单选题

一棵深度为5的满二叉树共有多少个节点?

A15
B16
C31
D32
2判断题

在二叉树中,任何一个节点的度(即子节点个数)都小于等于2。

3填空题
请补全以下二叉树节点类的Python代码,使初始化时左右子节点均为空。

class TreeNode:
    def __init__(self, val):
        self.val = val
        self.left = ___
        self.right = ___
4单选题

关于完全二叉树,以下说法正确的是?

A完全二叉树一定是满二叉树
B完全二叉树中,叶子节点只可能出现在最后两层
C完全二叉树中,所有节点都有两个孩子
D完全二叉树的深度与节点数之间没有确定关系
5判断题

在任意一棵二叉树中,叶子节点(度为0的节点)的个数等于度为2的节点个数加1。