树 (Tree)
概念
树是一种非线性数据结构,由一组称为节点 (Node) 的元素组成,这些节点通过称为边 (Edge) 的连接关系组织起来,形成层次结构。树结构在计算机科学中被广泛用于表示具有层级关系的数据,例如文件系统、组织结构、XML/HTML 文档等。
基本术语:
- 节点 (Node):树的基本组成单元,可以包含数据和指向其他节点的引用(子节点)。
- 根节点 (Root):树中最顶层的节点,它没有父节点。一棵非空树有且仅有一个根节点。
- 父节点 (Parent):一个节点的直接上层节点。
- 子节点 (Child):一个节点的直接下层节点。
- 兄弟节点 (Sibling):具有相同父节点的节点。
- 叶节点 (Leaf Node / External Node):没有子节点的节点。
- 内部节点 (Internal Node / Non-leaf Node):至少有一个子节点的节点。
- 边 (Edge):连接两个节点的线,表示它们之间的关系(通常是父子关系)。
- 路径 (Path):从一个节点到另一个节点所经过的边的序列。
- 节点的度 (Degree of a Node):一个节点拥有的子树的个数(即其子节点的数量)。
- 树的度 (Degree of a Tree):树中所有节点的度的最大值。
- 层 (Level):节点的层级。根节点在第 1 层(或第 0 层,取决于约定),其子节点在第 2 层(或第 1 层),以此类推。
- 深度 (Depth of a Node):从根节点到该节点的路径长度(边的数量)。根节点的深度为 0。
- 高度 (Height of a Node):从该节点到其最远叶节点路径的长度。叶节点的高度为 0。
- 树的高度 (Height of a Tree):根节点的高度,也就是树中最长路径的长度。
- 子树 (Subtree):树中某个节点及其所有后代节点组成的结构,本身也是一棵树。
- 森林 (Forest):零棵或多棵不相交的树的集合。
二叉树 (Binary Tree)
二叉树是树的一种特殊形式,其每个节点最多有两个子节点,分别称为左子节点 (Left Child) 和右子节点 (Right Child)。二叉树的子节点具有左右次序,不能任意颠倒。
概念与性质
Python 实现节点 (示例)
通常使用类来表示二叉树的节点:
class TreeNode:
def __init__(self, value):
self.value = value # 节点存储的数据
self.left = None # 左子节点引用
self.right = None # 右子节点引用
# 创建节点示例
root = TreeNode(10)
root.left = TreeNode(5)
root.right = TreeNode(15)
root.left.left = TreeNode(3)
root.left.right = TreeNode(7)
root.right.right = TreeNode(18)
# 10
# / \
# 5 15
# / \ \
# 3 7 18
二叉树的相关操作
1. 遍历 (Traversal)
遍历是指按照某种特定的顺序访问树中的所有节点,并且每个节点只访问一次。常见的二叉树遍历方式有:
-
前序遍历 (Pre-order Traversal): 根节点 -> 左子树 -> 右子树 (根-左-右)
def preorder_traversal(node):
if node:
print(node.value, end=' ') # 访问根节点
preorder_traversal(node.left) # 遍历左子树
preorder_traversal(node.right) # 遍历右子树
print("Pre-order:")
preorder_traversal(root) # Output: 10 5 3 7 15 18
print()
-
中序遍历 (In-order Traversal): 左子树 -> 根节点 -> 右子树 (左-根-右)
def inorder_traversal(node):
if node:
inorder_traversal(node.left) # 遍历左子树
print(node.value, end=' ') # 访问根节点
inorder_traversal(node.right) # 遍历右子树
print("In-order:")
inorder_traversal(root) # Output: 3 5 7 10 15 18
print()
-
后序遍历 (Post-order Traversal): 左子树 -> 右子树 -> 根节点 (左-右-根)
- 常用于计算表达式树或释放树节点(先释放子节点再释放父节点)。
def postorder_traversal(node):
if node:
postorder_traversal(node.left) # 遍历左子树
postorder_traversal(node.right)# 遍历右子树
print(node.value, end=' ') # 访问根节点
print("Post-order:")
postorder_traversal(root) # Output: 3 7 5 18 15 10
print()
-
层序遍历 (Level-order Traversal / Breadth-First Traversal): 从上到下,从左到右,逐层访问节点。
from collections import deque
def levelorder_traversal(node):
if not node:
return
queue = deque([node])
while queue:
current_node = queue.popleft()
print(current_node.value, end=' ')
if current_node.left:
queue.append(current_node.left)
if current_node.right:
queue.append(current_node.right)
print("Level-order:")
levelorder_traversal(root) # Output: 10 5 15 3 7 18
print()
2. 查找节点 (Searching)
在普通二叉树中查找节点通常需要遍历树。对于特殊的二叉树如二叉搜索树,查找效率会更高。
def find_node(node, key):
if not node:
return None
if node.value == key:
return node
found_left = find_node(node.left, key)
if found_left:
return found_left
found_right = find_node(node.right, key)
return found_right
found = find_node(root, 7)
if found:
print(f"Found node with value: {found.value}")
else:
print("Node not found.")
3. 插入节点 (Inserting)
在普通二叉树中插入节点没有固定规则,取决于具体应用。通常会插入到某个叶节点的位置。对于二叉搜索树,插入有特定规则。
4. 删除节点 (Deleting)
删除节点也比较复杂,需要考虑被删除节点是否有子节点,以及如何调整树的结构。对于二叉搜索树,删除操作有标准算法。
特殊类型的二叉树
-
满二叉树 (Full Binary Tree):在一棵二叉树中,如果所有分支节点都存在左子树和右子树,并且所有叶子节点都在同一层上,这样的二叉树称为满二叉树。
- 特点:深度为
k 且有 2^k - 1 个节点的二叉树是满二叉树。
- 或者说,除了叶节点外,每个节点的度都为 2。
-
完全二叉树 (Complete Binary Tree):对一棵具有 n 个节点的二叉树按层序编号,如果编号为 i (1 ≤ i ≤ n) 的节点与满二叉树中编号为 i 的节点在二叉树中的位置完全相同,则这棵二叉树称为完全二叉树。
- 特点:
- 叶子节点只可能出现在最下两层。
- 最下层的叶子节点一定集中在左部连续位置。
- 倒数第二层的叶子节点(如果有的话)一定集中在右部连续位置。
- 如果某个节点的度为1,则它只有左孩子(即不存在只有右孩子没有左孩子的情况)。
- 同样深度的满二叉树一定是完全二叉树,但完全二叉树不一定是满二叉树。
- 完全二叉树由于其结构特性,非常适合用数组(顺序存储)来表示,常用于实现堆 (Heap)。
-
二叉搜索树 (Binary Search Tree - BST) (也称二叉查找树或二叉排序树):
- 它或者是一棵空树,或者是具有下列性质的二叉树:
- 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值。
- 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值。
- 任意节点的左、右子树也分别为二叉搜索树。
- 没有键值相等的节点(或者约定相等节点放在左子树或右子树)。
- 特点:中序遍历二叉搜索树可以得到一个有序的序列。这使得 BST 非常适合进行查找、插入和删除操作(平均时间复杂度为 O(log n),最坏情况下为 O(n) 当树退化成链表时)。
树结构是算法和数据结构中的核心内容,理解其基本概念和操作对于解决复杂问题至关重要。