05树

Lingfeng2025-07-05

05树

1. 二叉树

1.1 性质

Theorem (性质 1)

在二叉树的第层上至多有个结点。

Theorem (性质 2)

深度为的二叉树至多有个结点。

Theorem (性质 3)

对任何一棵二叉树,如果其叶子结点数为,度为 2 的结点数为,则

证明
已知叶子结点数为,度为 2 的结点数为,设度为 1 的结点数为,显然

注意到除了根结点以外,其他结点都有一个分支进入,这些分支是由度为 1 或 2 的结点引出,因此
联合即有

Theorem (性质 4)

具有个结点的完全二叉树的深度为

Theorem (性质 5)

对于有个结点的完全二叉树,按层序编号(从第 1 层到第层,每层从左到右),则对任一结点:

  1. ,则为根结点,无双亲
  2. ,则无左孩,为叶子结点;否则左孩为
  3. ,则无右孩;否则右孩为

Example (例题 1)

一棵有 124 个叶结点的完全二叉树,最多有多少结点?请概要说明推算过程。

由题意,因此,由于是完全二叉树,,因此最多

Example (例题 2)

个结点,不计次序,能构成多少个二叉树?

考虑,由递推有

解为
若记次序,则为

1.2 存储结构

顺序存储

tree = [None] * (1 << h - 1)
root = 1

def left_child(i):
	return 2 * i + 1

def right_child(i):
	return 2 * i + 2

def parent(i):
	return (i - 1) // 2

链式存储

@dataclass
class Node:
    val: Any
    left: "Node" = None
    right: "Node" = None

或者使用字典

tree = {}
tree[node] = [left, right]

1.3 二叉树遍历

先序遍历

递归实现:

def preorder(node):
	if not node:
		return
	print(node.val)
	preorder(node.left)
	preorder(node.right)

栈实现:

def preorder(root):
    if not root:
        return

    stack = [root]
    while stack:
        node = stack.pop()
        print(stack.pop())

        if node.right:
            stack.append(node.right)
        if node.left:
            stack.append(node.left)

中序遍历

递归实现:

def inorder(node):
	if not node:
		return
	inorder(node.left)
	print(node.val)
	inorder(node.right)

栈实现:

def go_left(curr, stack):
	while curr:
		stack.append(curr)
		curr = curr.left

def inorder(root):
    if not root:
        return

    curr = root
    while curr or stack:
        go_left(curr, stack)

        node = stack.pop()
        print(node.val)

        curr = node.right

后序遍历

递归实现:

def postorder(node):
	if not node:
		return
	postorder(node.left)
	postorder(node.right)
	print(node.val)
  • 中序=后序无右孩
  • 前序=中序无左孩
  • 前序=后序只有根结点或没有

栈实现:

模拟前序遍历,左右根->根右左

def postorder(root):
	stack = [root]
	result = []

	while stack:
		node = stack.pop()
		result.append(node)

		if l := node.left:
			stack.append(l)
		if r := node.right:
			stack.append(r)

	return result[::-1]
  • 前序遍历:从根开始,先记录当前结点,然后向左走直到尽头,回退时访问右子树。
  • 中序遍历:从左子树走到底,回退时访问结点,再走右子树。
  • 后序遍历:从左子树走到底,回退时访问右子树,最后访问根结点。

2. 线索二叉树

Theorem (性质 6)

个结点有个非空指针,个空指针。

利用空链域记录前驱和后继:

@dataclass
class Node:
    val: Any
    left: "Node" = None
    right: "Node" = None
    left_thread: bool
    right_thread: bool

在遍历过程中记录线索:

pre = None
def set_thread(node):
	global pre
	if node.left is None:
		node.left_thread = True
		node.left = pre

	if pre is not None and pre.right is None:
		pre.right_thread = True
		pre.right = node

	pre = node

2.1 中序遍历

def inorder(node):
	if node is None:
		return

	if not node.left_thread:
		inorder(node.left)
	set_thread(node)
	if not node.right_thread:
		inorder(node.right)
  • 前驱结点
    • 如果结点的 左子树 不为空,则前驱结点是 左子树中最右的结点(即左子树的最大值)。
    • 如果结点的 左子树 为空,则前驱结点是 通过线索指针获取的,即 左孩
  • 后继结点
    • 如果结点的 右子树 不为空,则后继结点是 右子树中最左的结点(即右子树的最小值)。
    • 如果结点的 右子树 为空,则后继结点是 通过线索指针获取的,即 右孩

2.2 前序遍历

def preorder(node):
	if node is None:
		return

	set_thread(node)
	if not node.left_thread:
		inorder(node.left)
	if not node.right_thread:
		inorder(node.right)
  • 前驱结点
    • 前序遍历难以找到前驱
  • 后继结点
    • 如果结点的 左子树 不为空,则后继结点是 左孩
    • 如果结点的 左子树 为空,则后继结点是 通过线索指针获取的 或者 只有右孩,因此都是右孩
@property
def succ(self):
	if not self.left_thread:
		return self.left
	return self.right

2.3 后序遍历

def postorder(node):
	if node is None:
		return

	if not node.left_thread:
		inorder(node.left)
	if not node.right_thread:
		inorder(node.right)
	set_thread(node)
  • 前驱结点
    • 如果结点的 右子树 不为空,则前驱结点是 右孩
    • 如果结点的 右子树 为空,则前驱结点是 通过线索指针获取的 或者 只有左孩,因此都是左孩
  • 后继结点
    • 后序遍历难以找到后继。
@property
def pred(self):
	if not self.right_thread:
		return self.right
	return self.left

3. 树

3.1 树的存储结构

  • 双亲表示法:以一组连续空间存储树的结点,同时在每个结点中附设一个指示器指示其双亲结点在表中的位置。
  • 孩子链表表示法:以单链表将所有的双亲相同的孩子结点链接在一起,并将该链表的头指针和双亲构成树中的一个结点。
  • 孩子兄弟表示法:以二叉链表作树的存储结构,表中结点的两个链域分别指向该结点的“最左“孩子结点和下一个兄弟结点。

3.2 遍历

  • 先根遍历:若树不空,则先访问根结点,然后依次先根遍历各棵子树。
  • 后根遍历:若树不空,则先依次后根遍历各棵子树,然后访问根结点。
  • 层次遍历:若树不空,则自上而下自左至右访问树中每个结点。

4. 森林

4.1 森林与二叉树的转换

  • 增加一个根结点,作为原森林中各树根结点的父结点
  • 将新树转换成二叉树
  • 删除二叉树的根结点

4.2 遍历

  • 先序遍历:依次从左至右对森林中的每一棵树进行先根遍历。
  • 中序遍历:依次从左至右对森林中的每一棵树进行后根遍历。

5. 赫夫曼树

Definition (赫夫曼树)

假设有个权值,构造 一棵有个叶子结点的二叉树,每个叶子结点的权值为,则其中带权路径长度最小的二叉树称做最优二叉树或赫夫曼树。

  1. 根据给定的个权值构成棵二叉树的集合,其中每棵二叉树中只有一个权值为的根结点。
  2. 中选取两棵根结点权值最小的树作为左、右子树构造一棵新的二叉树,且置新二叉树的根结点的权值为其左、右子树根结点的权值之和。
  3. 中删除这两棵树,同时将新得到的二叉树加入集合 中。
  4. 重复 (2) 和 (3) ,直到 中只含一棵树为止。
def build_huffman_tree(weights):
    heap = [Node(weight) for weight in weights]
	heapq.heapify(heap)

    while len(heap) > 1:
        left = heapq.heappop(heap)
        right = heapq.heappop(heap)

        merged_node = Node(left.weight + right.weight)
        merged_node.left = left
        merged_node.right = right

        heapq.heappush(heap, merged_node)

    return heap[0]

时间复杂度为 ,空间复杂度为

Theorem (带权路径和)

赫夫曼树中,带权路径长度等于所有非叶子节点的权重之和

Theorem (k 叉赫夫曼树)

对于 n 结点 k 叉赫夫曼树,需满足

必须被整除,否则添加 0 权重补偿。

证明
设叶子结点为,非叶子结点为,总边数为,显然有

由于赫夫曼树是满叉树,因此
此时联立有
故证毕。

Last Updated 7/19/2025, 6:33:12 AM