在算法与数据结构的浩瀚宇宙中,树结构宛如一颗璀璨的明星,以其独特的层次化组织和高效的数据处理能力,在众多领域熠熠生辉。从经典的二叉树、红黑树,到应用广泛的 B 树、Trie 树,每一种树结构都承载着独特的设计思想与算法逻辑。它们不仅是解决搜索、排序、存储等问题的 “秘密武器”,更在数据库索引优化、自然语言处理、文件系统管理等场景中发挥着不可替代的作用。本文将带您深入树结构的奇妙世界,一同领略其精妙设计与无限潜力。
满二叉树(Full Binary Tree)是一种特殊的二叉树结构,其定义如下:
定义:
满二叉树是指一棵二叉树中,除了叶子节点外,每一个节点都有恰好两个子节点(左子节点和右子节点),且所有叶子节点都在同一层上。
特点:
h
,则它的节点总数为 2^h - 1
。2^(h-1)
。2^(h-1) - 1
。示例:
以下是一个深度为 3 的满二叉树:
A
/ \
B C
/ \ / \
D E F G
其中,节点 A
、B
、C
都有两个子节点,而叶子节点 D
、E
、F
、G
都在同一层。
与其他树的区别:
应用场景:
满二叉树常用于堆(Heap)结构、哈夫曼编码(Huffman Coding)等算法中,因为其结构规整,便于计算和操作。
定义
完全二叉树(Complete Binary Tree)是一种特殊的二叉树结构,其中除了最后一层外,每一层都被完全填满,并且最后一层的所有节点都尽可能集中在左侧。
特点
性质
示例
1
/ \
2 3
/ \ /
4 5 6
与非完全二叉树的区别
应用场景
平衡二叉树(AVL树)是一种自平衡的二叉搜索树(BST),其中任何节点的两个子树的高度差最多为1。AVL树得名于其发明者Adelson-Velsky和Landis。
平衡条件:对于AVL树中的每个节点,其左子树和右子树的高度差(平衡因子)的绝对值不超过1。
高度平衡:由于严格的平衡条件,AVL树的高度始终保持在O(log n),其中n是树中的节点数。
插入:
删除:
查找:
当插入或删除导致树不平衡时,需要通过旋转恢复平衡。主要有四种旋转情况:
左旋(Left Rotation):
右旋(Right Rotation):
左右旋(Left-Right Rotation):
右左旋(Right-Left Rotation):
二叉搜索树(Binary Search Tree,BST)是一种特殊的二叉树数据结构,其中每个节点最多有两个子节点(左子节点和右子节点),并且满足以下性质:
查找(Search):
插入(Insert):
删除(Delete):
红黑树是一种自平衡的二叉查找树,它在计算机科学中应用广泛,尤其是在关联容器(如C++中的std::map
和std::set
)的实现中。红黑树通过特定的规则确保树在插入和删除操作后保持近似平衡,从而保证操作的时间复杂度为O(log n)。
红黑树必须满足以下五个性质:
红黑树的核心操作包括插入、删除和查找,并通过旋转和重新着色来维护红黑树的性质。
插入:
删除:
查找:
std::map
和std::set
。以下是红黑树的简单实现框架(仅展示插入和旋转操作):
enum Color { RED, BLACK };
struct Node {
int data;
Color color;
Node *left, *right, *parent;
Node(int data) : data(data), color(RED), left(nullptr), right(nullptr), parent(nullptr) {}
};
class RedBlackTree {
private:
Node *root;
void leftRotate(Node *x);
void rightRotate(Node *y);
void fixInsert(Node *z);
public:
RedBlackTree() : root(nullptr) {}
void insert(int data);
};
void RedBlackTree::leftRotate(Node *x) {
Node *y = x->right;
x->right = y->left;
if (y->left != nullptr) {
y->left->parent = x;
}
y->parent = x->parent;
if (x->parent == nullptr) {
root = y;
} else if (x == x->parent->left) {
x->parent->left = y;
} else {
x->parent->right = y;
}
y->left = x;
x->parent = y;
}
void RedBlackTree::rightRotate(Node *y) {
Node *x = y->left;
y->left = x->right;
if (x->right != nullptr) {
x->right->parent = y;
}
x->parent = y->parent;
if (y->parent == nullptr) {
root = x;
} else if (y == y->parent->left) {
y->parent->left = x;
} else {
y->parent->right = x;
}
x->right = y;
y->parent = x;
}
void RedBlackTree::fixInsert(Node *z) {
while (z != root && z->parent->color == RED) {
// 修复操作的具体实现
}
root->color = BLACK;
}
void RedBlackTree::insert(int data) {
Node *z = new Node(data);
// 插入逻辑
fixInsert(z);
}
B树(B-Tree)是一种自平衡的树数据结构,用于在磁盘或其他直接存取的存储设备上高效地存储和检索数据。它广泛应用于数据库和文件系统中,因为它能够减少磁盘I/O操作的次数。
t-1
个键,最多包含 2t-1
个键(t
是B树的最小度数)。O(log n)
,其中 n
是键的数量。O(n)
。B+树是一种多路平衡查找树,是B树的一种变体。它主要用于数据库和文件系统中,能够高效地进行查找、顺序访问、插入和删除操作。
查找:
插入:
删除:
B*树是B树的一种变体,旨在提高节点的空间利用率和查询效率。它通过调整节点的分裂策略和兄弟节点之间的数据重新分配来优化性能。
m
个关键字和m+1
个子节点指针。⌈(2m-1)/3⌉
个关键字(确保2/3填充率)。主要用于需要高磁盘I/O效率的场景,如数据库索引和文件系统。
哈夫曼树是一种带权路径长度最短的二叉树,也称为最优二叉树。它主要用于数据压缩领域,如哈夫曼编码。
假设有字符集 {A, B, C, D}
,对应的权值(频率)为 {5, 1, 6, 3}
:
A(5)
、B(1)
、C(6)
、D(3)
。B(1)
和 D(3)
,生成新节点 BD(4)
。A(5)
和 BD(4)
,生成新节点 ABD(9)
。ABD(9)
和 C(6)
,生成根节点 ABCD(15)
。字典树,又称前缀树或Trie树,是一种树形数据结构,用于高效地存储和检索字符串集合中的键。字典树的核心思想是利用字符串的公共前缀来减少查询时间,通常用于搜索引擎、拼写检查、自动完成等场景。
插入:
查找:
前缀匹配:
线段树是一种二叉树数据结构,用于高效处理区间查询(如区间求和、区间最小值/最大值等)和区间更新操作。它将一个区间划分为若干个子区间,每个节点存储对应区间的信息。
[l, r]
:
l = r
)。[l, r]
的左子节点为 [l, mid]
,右子节点为 [mid+1, r]
(mid = (l + r) / 2
)。O(log n)
。构建(Build):
void build(int node, int l, int r, vector<int>& data) {
if (l == r) {
tree[node] = data[l];
return;
}
int mid = (l + r) / 2;
build(2*node, l, mid, data);
build(2*node+1, mid+1, r, data);
tree[node] = tree[2*node] + tree[2*node+1]; // 以区间求和为例
}
区间查询(Query):
[l, r]
完全包含于查询区间 [ql, qr]
,则直接返回节点值。int query(int node, int l, int r, int ql, int qr) {
if (qr < l || ql > r) return 0; // 无交集
if (ql <= l && qr >= r) return tree[node]; // 完全包含
int mid = (l + r) / 2;
return query(2*node, l, mid, ql, qr) + query(2*node+1, mid+1, r, ql, qr);
}
更新(Update):
void update(int node, int l, int r, int pos, int val) {
if (l == r) {
tree[node] = val;
return;
}
int mid = (l + r) / 2;
if (pos <= mid) update(2*node, l, mid, pos, val);
else update(2*node+1, mid+1, r, pos, val);
tree[node] = tree[2*node] + tree[2*node+1];
}
O(4n)
(保守估计,实际为 2n-1
但通常开 4 倍空间)。O(n)
。O(log n)
。并查集树(Disjoint Set Union Tree,简称DSU Tree)是一种用于处理不相交集合合并与查询问题的数据结构。它主要用于解决动态连通性问题,比如判断两个元素是否属于同一集合,或者将两个集合合并为一个集合。
通常通过数组或树结构实现,每个节点指向其父节点,根节点指向自己(代表集合的标识)。
find(x)
时,将x
到根节点的路径上的所有节点的父节点设为根。rank
数组记录树的深度,合并时比较rank
。int parent[MAX_N], rank[MAX_N];
void init(int n) {
for (int i = 0; i < n; ++i) {
parent[i] = i; // 初始时每个元素自成一集合
rank[i] = 0; // 初始秩为0
}
}
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]); // 路径压缩
}
return parent[x];
}
void unionSets(int x, int y) {
int rootX = find(x), rootY = find(y);
if (rootX == rootY) return; // 已属于同一集合
// 按秩合并
if (rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
} else {
parent[rootX] = rootY;
if (rank[rootX] == rank[rootY]) {
rank[rootY]++;
}
}
}
后缀树是一种压缩的字典树(Trie),用于存储一个字符串的所有后缀。它是一种线性空间的数据结构,能够高效地解决许多字符串处理问题。
后缀树可以通过Ukkonen算法在线性时间内构建(时间复杂度为O(n),其中n是字符串长度)。该算法通过逐步扩展字符并维护隐式后缀树来实现高效构建。
对于字符串"banana"
,其后缀树会包含以下后缀:
banana
anana
nana
ana
na
a
struct SuffixTreeNode {
map<char, SuffixTreeNode*> children; // 子节点
int start; // 边的起始索引
int end; // 边的结束索引
SuffixTreeNode* suffixLink; // 后缀链接
};
SuffixTreeNode* buildSuffixTree(string s) {
// 实现Ukkonen算法构建后缀树
// ...
}