1. 树结构基础概念回顾
树是数据结构中最重要且应用最广泛的结构之一。在计算机科学中,树是一种非线性的分层数据结构,由节点和边组成。每个节点可以有零个或多个子节点,但只有一个父节点(根节点除外)。
树结构之所以如此重要,是因为它完美模拟了现实世界中许多分层关系。比如文件系统的目录结构、公司组织架构、生物分类系统等,都可以用树来表示。在编程中,树结构常用于实现数据库索引、编译器语法分析、路由算法等场景。
1.1 树的基本术语
理解树结构需要掌握一些基本术语:
- 根节点(Root):树的最顶层节点,没有父节点
- 子节点(Child):一个节点的直接下级节点
- 父节点(Parent):一个节点的直接上级节点
- 叶子节点(Leaf):没有子节点的节点
- 内部节点:至少有一个子节点的节点
- 度(Degree):一个节点拥有的子节点数量
- 深度(Depth):从根到该节点的路径长度
- 高度(Height):从该节点到最远叶子节点的路径长度
- 层级(Level):根节点为第1层,其子节点为第2层,以此类推
1.2 树的常见类型
根据节点的排列方式和限制条件,树可以分为多种类型:
- 二叉树:每个节点最多有两个子节点(左子节点和右子节点)
- 二叉搜索树(BST):左子树所有节点值小于根节点,右子树所有节点值大于根节点
- 平衡二叉树:任何节点的左右子树高度差不超过1
- 完全二叉树:除最后一层外,其他层节点都达到最大数量
- 满二叉树:所有非叶子节点都有两个子节点,所有叶子节点在同一层
- B树/B+树:多路平衡查找树,常用于数据库和文件系统
- 堆(Heap):特殊的完全二叉树,分为最大堆和最小堆
- 字典树(Trie):用于高效存储和检索字符串集合
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 树的存储表示方法
在实际编程中,我们需要将抽象的树结构转化为具体的数据表示。主要有以下几种存储方式:
2.1 链式存储法
这是最直观的表示方法,每个节点包含数据和指向子节点的指针。
c复制struct TreeNode {
int data;
struct TreeNode *left;
struct TreeNode *right;
};
对于多叉树,可以使用子节点指针数组或链表:
c复制#define MAX_CHILDREN 10
struct TreeNode {
int data;
struct TreeNode *children[MAX_CHILDREN];
};
2.2 数组存储法
对于完全二叉树,可以使用数组紧凑存储:
- 根节点存储在索引1处
- 对于索引i的节点:
- 左子节点索引为2i
- 右子节点索引为2i+1
- 父节点索引为i/2
这种表示法节省指针空间,且可以利用CPU缓存局部性提高访问效率。
2.3 左孩子右兄弟表示法
这是一种将多叉树转化为二叉树表示的方法:
c复制struct TreeNode {
int data;
struct TreeNode *firstChild; // 第一个孩子节点
struct TreeNode *nextSibling; // 下一个兄弟节点
};
这种表示法可以统一处理二叉树和多叉树,简化算法实现。
3. 树的遍历算法
树的遍历是指按照某种顺序访问树中的所有节点。根据访问顺序的不同,主要分为以下几种遍历方式:
3.1 深度优先遍历(DFS)
深度优先遍历沿着树的深度遍历节点,尽可能深的搜索树的分支。
3.1.1 前序遍历(Pre-order)
访问顺序:根节点 → 左子树 → 右子树
c复制void preOrder(struct TreeNode* root) {
if (root == NULL) return;
printf("%d ", root->data); // 访问根节点
preOrder(root->left); // 遍历左子树
preOrder(root->right); // 遍历右子树
}
应用场景:复制树结构、计算前缀表达式等。
3.1.2 中序遍历(In-order)
访问顺序:左子树 → 根节点 → 右子树
c复制void inOrder(struct TreeNode* root) {
if (root == NULL) return;
inOrder(root->left); // 遍历左子树
printf("%d ", root->data); // 访问根节点
inOrder(root->right); // 遍历右子树
}
应用场景:二叉搜索树的中序遍历可以得到有序序列。
3.1.3 后序遍历(Post-order)
访问顺序:左子树 → 右子树 → 根节点
c复制void postOrder(struct TreeNode* root) {
if (root == NULL) return;
postOrder(root->left); // 遍历左子树
postOrder(root->right); // 遍历右子树
printf("%d ", root->data); // 访问根节点
}
应用场景:删除树结构、计算后缀表达式等。
3.2 广度优先遍历(BFS)
广度优先遍历按层次从上到下、从左到右访问节点,也称为层次遍历。
c复制void levelOrder(struct TreeNode* root) {
if (root == NULL) return;
struct TreeNode* queue[1000];
int front = 0, rear = 0;
queue[rear++] = root;
while (front < rear) {
struct TreeNode* node = queue[front++];
printf("%d ", node->data);
if (node->left != NULL)
queue[rear++] = node->left;
if (node->right != NULL)
queue[rear++] = node->right;
}
}
应用场景:计算树的高度、查找最短路径等。
3.3 遍历算法的非递归实现
递归实现简洁但可能面临栈溢出问题,以下是使用栈的非递归实现:
3.3.1 前序遍历非递归实现
c复制void preOrderIterative(struct TreeNode* root) {
if (root == NULL) return;
struct TreeNode* stack[1000];
int top = -1;
stack[++top] = root;
while (top >= 0) {
struct TreeNode* node = stack[top--];
printf("%d ", node->data);
if (node->right != NULL)
stack[++top] = node->right;
if (node->left != NULL)
stack[++top] = node->left;
}
}
3.3.2 中序遍历非递归实现
c复制void inOrderIterative(struct TreeNode* root) {
struct TreeNode* stack[1000];
int top = -1;
struct TreeNode* curr = root;
while (curr != NULL || top >= 0) {
while (curr != NULL) {
stack[++top] = curr;
curr = curr->left;
}
curr = stack[top--];
printf("%d ", curr->data);
curr = curr->right;
}
}
3.3.3 后序遍历非递归实现
后序遍历的非递归实现较为复杂,需要记录节点的访问状态:
c复制void postOrderIterative(struct TreeNode* root) {
if (root == NULL) return;
struct TreeNode* stack[1000];
int top = -1;
struct TreeNode* prev = NULL;
stack[++top] = root;
while (top >= 0) {
struct TreeNode* curr = stack[top];
if (prev == NULL || prev->left == curr || prev->right == curr) {
if (curr->left != NULL)
stack[++top] = curr->left;
else if (curr->right != NULL)
stack[++top] = curr->right;
else {
printf("%d ", curr->data);
top--;
}
}
else if (curr->left == prev) {
if (curr->right != NULL)
stack[++top] = curr->right;
else {
printf("%d ", curr->data);
top--;
}
}
else if (curr->right == prev) {
printf("%d ", curr->data);
top--;
}
prev = curr;
}
}
4. 树的应用实例
树结构在计算机科学中有着广泛的应用,下面介绍几个典型应用场景。
4.1 二叉搜索树(BST)的实现
二叉搜索树是一种特殊的二叉树,其中每个节点的值大于其左子树所有节点的值,小于其右子树所有节点的值。
4.1.1 BST的查找操作
c复制struct TreeNode* searchBST(struct TreeNode* root, int val) {
if (root == NULL || root->data == val)
return root;
if (val < root->data)
return searchBST(root->left, val);
else
return searchBST(root->right, val);
}
时间复杂度:平均O(log n),最坏O(n)(当树退化为链表时)
4.1.2 BST的插入操作
c复制struct TreeNode* insertBST(struct TreeNode* root, int val) {
if (root == NULL) {
struct TreeNode* newNode = (struct TreeNode*)malloc(sizeof(struct TreeNode));
newNode->data = val;
newNode->left = newNode->right = NULL;
return newNode;
}
if (val < root->data)
root->left = insertBST(root->left, val);
else if (val > root->data)
root->right = insertBST(root->right, val);
return root;
}
4.1.3 BST的删除操作
删除操作需要考虑三种情况:
- 要删除的节点是叶子节点
- 要删除的节点只有一个子节点
- 要删除的节点有两个子节点
c复制struct TreeNode* deleteBST(struct TreeNode* root, int val) {
if (root == NULL) return root;
if (val < root->data)
root->left = deleteBST(root->left, val);
else if (val > root->data)
root->right = deleteBST(root->right, val);
else {
// 情况1:只有一个子节点或没有子节点
if (root->left == NULL) {
struct TreeNode* temp = root->right;
free(root);
return temp;
}
else if (root->right == NULL) {
struct TreeNode* temp = root->left;
free(root);
return temp;
}
// 情况2:有两个子节点
struct TreeNode* temp = minValueNode(root->right);
root->data = temp->data;
root->right = deleteBST(root->right, temp->data);
}
return root;
}
// 辅助函数:找到子树中的最小节点
struct TreeNode* minValueNode(struct TreeNode* node) {
struct TreeNode* current = node;
while (current && current->left != NULL)
current = current->left;
return current;
}
4.2 堆(Heap)的实现
堆是一种特殊的完全二叉树,满足堆性质:
- 最大堆:每个节点的值都大于或等于其子节点的值
- 最小堆:每个节点的值都小于或等于其子节点的值
4.2.1 堆的表示
通常使用数组表示堆:
c复制#define MAX_HEAP_SIZE 1000
struct MaxHeap {
int array[MAX_HEAP_SIZE];
int size;
};
4.2.2 堆的插入操作
c复制void insertMaxHeap(struct MaxHeap* heap, int item) {
if (heap->size >= MAX_HEAP_SIZE) return;
heap->array[heap->size] = item;
int current = heap->size;
heap->size++;
// 上滤操作
while (current != 0 && heap->array[current] > heap->array[(current-1)/2]) {
swap(&heap->array[current], &heap->array[(current-1)/2]);
current = (current-1)/2;
}
}
4.2.3 堆的删除操作
堆的删除通常指删除堆顶元素:
c复制int extractMax(struct MaxHeap* heap) {
if (heap->size <= 0) return INT_MIN;
int max = heap->array[0];
heap->array[0] = heap->array[heap->size-1];
heap->size--;
// 下滤操作
maxHeapify(heap, 0);
return max;
}
void maxHeapify(struct MaxHeap* heap, int idx) {
int largest = idx;
int left = 2*idx + 1;
int right = 2*idx + 2;
if (left < heap->size && heap->array[left] > heap->array[largest])
largest = left;
if (right < heap->size && heap->array[right] > heap->array[largest])
largest = right;
if (largest != idx) {
swap(&heap->array[idx], &heap->array[largest]);
maxHeapify(heap, largest);
}
}
4.3 字典树(Trie)的实现
字典树是一种用于高效存储和检索字符串集合的树形数据结构。
4.3.1 Trie节点的定义
c复制#define ALPHABET_SIZE 26
struct TrieNode {
struct TrieNode* children[ALPHABET_SIZE];
bool isEndOfWord;
};
4.3.2 Trie的插入操作
c复制void insertTrie(struct TrieNode* root, const char* key) {
struct TrieNode* current = root;
for (int i = 0; key[i] != '\0'; i++) {
int index = key[i] - 'a';
if (current->children[index] == NULL)
current->children[index] = getNode();
current = current->children[index];
}
current->isEndOfWord = true;
}
4.3.3 Trie的搜索操作
c复制bool searchTrie(struct TrieNode* root, const char* key) {
struct TrieNode* current = root;
for (int i = 0; key[i] != '\0'; i++) {
int index = key[i] - 'a';
if (current->children[index] == NULL)
return false;
current = current->children[index];
}
return (current != NULL && current->isEndOfWord);
}
5. 树结构的性能优化
在实际应用中,基础的树结构可能面临性能问题,需要采用优化策略。
5.1 平衡二叉搜索树
普通BST在极端情况下可能退化为链表,导致操作时间复杂度降为O(n)。平衡BST通过旋转操作保持树的平衡。
5.1.1 AVL树
AVL树是最早的自平衡二叉搜索树,通过平衡因子(左右子树高度差)控制平衡。
c复制struct AVLNode {
int data;
struct AVLNode* left;
struct AVLNode* right;
int height;
};
int height(struct AVLNode* node) {
if (node == NULL) return 0;
return node->height;
}
int max(int a, int b) {
return (a > b) ? a : b;
}
struct AVLNode* rightRotate(struct AVLNode* y) {
struct AVLNode* x = y->left;
struct AVLNode* T2 = x->right;
x->right = y;
y->left = T2;
y->height = max(height(y->left), height(y->right)) + 1;
x->height = max(height(x->left), height(x->right)) + 1;
return x;
}
struct AVLNode* leftRotate(struct AVLNode* x) {
struct AVLNode* y = x->right;
struct AVLNode* T2 = y->left;
y->left = x;
x->right = T2;
x->height = max(height(x->left), height(x->right)) + 1;
y->height = max(height(y->left), height(y->right)) + 1;
return y;
}
int getBalance(struct AVLNode* node) {
if (node == NULL) return 0;
return height(node->left) - height(node->right);
}
struct AVLNode* insertAVL(struct AVLNode* node, int data) {
if (node == NULL)
return newNode(data);
if (data < node->data)
node->left = insertAVL(node->left, data);
else if (data > node->data)
node->right = insertAVL(node->right, data);
else
return node;
node->height = 1 + max(height(node->left), height(node->right));
int balance = getBalance(node);
// 左左情况
if (balance > 1 && data < node->left->data)
return rightRotate(node);
// 右右情况
if (balance < -1 && data > node->right->data)
return leftRotate(node);
// 左右情况
if (balance > 1 && data > node->left->data) {
node->left = leftRotate(node->left);
return rightRotate(node);
}
// 右左情况
if (balance < -1 && data < node->right->data) {
node->right = rightRotate(node->right);
return leftRotate(node);
}
return node;
}
5.1.2 红黑树
红黑树是另一种自平衡二叉搜索树,通过颜色标记和旋转操作保持平衡。
c复制enum Color { RED, BLACK };
struct RBNode {
int data;
bool color;
struct RBNode *left, *right, *parent;
};
struct RBNode* insertRB(struct RBNode* root, int data) {
struct RBNode* node = newNode(data);
// 普通BST插入
root = BSTInsert(root, node);
// 修复红黑树性质
fixViolation(root, node);
return root;
}
void fixViolation(struct RBNode* &root, struct RBNode* &pt) {
struct RBNode* parent_pt = NULL;
struct RBNode* grand_parent_pt = NULL;
while ((pt != root) && (pt->color != BLACK) &&
(pt->parent->color == RED)) {
parent_pt = pt->parent;
grand_parent_pt = pt->parent->parent;
// Case A: 父节点是祖父节点的左子节点
if (parent_pt == grand_parent_pt->left) {
struct RBNode* uncle_pt = grand_parent_pt->right;
// Case 1: 叔叔节点是红色
if (uncle_pt != NULL && uncle_pt->color == RED) {
grand_parent_pt->color = RED;
parent_pt->color = BLACK;
uncle_pt->color = BLACK;
pt = grand_parent_pt;
} else {
// Case 2: pt是父节点的右子节点
if (pt == parent_pt->right) {
leftRotate(root, parent_pt);
pt = parent_pt;
parent_pt = pt->parent;
}
// Case 3: pt是父节点的左子节点
rightRotate(root, grand_parent_pt);
swap(parent_pt->color, grand_parent_pt->color);
pt = parent_pt;
}
}
// Case B: 父节点是祖父节点的右子节点
else {
struct RBNode* uncle_pt = grand_parent_pt->left;
// Case 1: 叔叔节点是红色
if ((uncle_pt != NULL) && (uncle_pt->color == RED)) {
grand_parent_pt->color = RED;
parent_pt->color = BLACK;
uncle_pt->color = BLACK;
pt = grand_parent_pt;
} else {
// Case 2: pt是父节点的左子节点
if (pt == parent_pt->left) {
rightRotate(root, parent_pt);
pt = parent_pt;
parent_pt = pt->parent;
}
// Case 3: pt是父节点的右子节点
leftRotate(root, grand_parent_pt);
swap(parent_pt->color, grand_parent_pt->color);
pt = parent_pt;
}
}
}
root->color = BLACK;
}
5.2 B树和B+树
B树和B+树是多路平衡查找树,特别适合磁盘等外部存储设备。
5.2.1 B树的特点
- 每个节点最多有m个子节点
- 除根节点外,每个非叶子节点至少有⌈m/2⌉个子节点
- 根节点至少有2个子节点(除非它是叶子节点)
- 所有叶子节点位于同一层
5.2.2 B树的插入操作
c复制#define ORDER 5 // B树的阶
struct BTreeNode {
int keys[ORDER-1];
struct BTreeNode* children[ORDER];
int numKeys;
bool isLeaf;
};
void insertBTree(struct BTreeNode** root, int key) {
struct BTreeNode* rootRef = *root;
// 如果根节点已满,需要分裂
if (rootRef->numKeys == ORDER-1) {
struct BTreeNode* newRoot = newBTreeNode(false);
newRoot->children[0] = rootRef;
splitChild(newRoot, 0, rootRef);
// 决定新键应该插入哪个子节点
int i = 0;
if (newRoot->keys[0] < key)
i++;
insertNonFull(newRoot->children[i], key);
*root = newRoot;
} else {
insertNonFull(rootRef, key);
}
}
void insertNonFull(struct BTreeNode* node, int key) {
int i = node->numKeys-1;
if (node->isLeaf) {
// 找到合适位置并插入
while (i >= 0 && node->keys[i] > key) {
node->keys[i+1] = node->keys[i];
i--;
}
node->keys[i+1] = key;
node->numKeys++;
} else {
// 找到合适的子节点
while (i >= 0 && node->keys[i] > key)
i--;
// 检查子节点是否已满
if (node->children[i+1]->numKeys == ORDER-1) {
splitChild(node, i+1, node->children[i+1]);
if (node->keys[i+1] < key)
i++;
}
insertNonFull(node->children[i+1], key);
}
}
void splitChild(struct BTreeNode* parent, int i, struct BTreeNode* fullChild) {
struct BTreeNode* newChild = newBTreeNode(fullChild->isLeaf);
newChild->numKeys = ORDER/2 - 1;
// 复制后半部分键到新节点
for (int j = 0; j < ORDER/2 - 1; j++)
newChild->keys[j] = fullChild->keys[j + ORDER/2];
// 如果不是叶子节点,复制子节点指针
if (!fullChild->isLeaf) {
for (int j = 0; j < ORDER/2; j++)
newChild->children[j] = fullChild->children[j + ORDER/2];
}
fullChild->numKeys = ORDER/2 - 1;
// 为父节点创建空间给新子节点
for (int j = parent->numKeys; j >= i+1; j--)
parent->children[j+1] = parent->children[j];
parent->children[i+1] = newChild;
// 移动父节点的键
for (int j = parent->numKeys-1; j >= i; j--)
parent->keys[j+1] = parent->keys[j];
parent->keys[i] = fullChild->keys[ORDER/2 - 1];
parent->numKeys++;
}
5.2.3 B+树与B树的区别
- B+树的所有数据都存储在叶子节点,内部节点只存储键值
- B+树的叶子节点通过指针连接,便于范围查询
- B+树的查询性能更稳定,因为每次查询都要走到叶子节点
6. 树结构的实际应用问题
在实际开发中,树结构经常用于解决各种算法问题。下面介绍几个典型问题及其解决方案。
6.1 二叉树的最大深度
c复制int maxDepth(struct TreeNode* root) {
if (root == NULL) return 0;
int leftDepth = maxDepth(root->left);
int rightDepth = maxDepth(root->right);
return (leftDepth > rightDepth) ? leftDepth + 1 : rightDepth + 1;
}
6.2 判断二叉树是否对称
c复制bool isSymmetric(struct TreeNode* root) {
if (root == NULL) return true;
return isMirror(root->left, root->right);
}
bool isMirror(struct TreeNode* left, struct TreeNode* right) {
if (left == NULL && right == NULL) return true;
if (left == NULL || right == NULL) return false;
return (left->data == right->data) &&
isMirror(left->left, right->right) &&
isMirror(left->right, right->left);
}
6.3 二叉树的最近公共祖先
c复制struct TreeNode* lowestCommonAncestor(struct TreeNode* root,
struct TreeNode* p,
struct TreeNode* q) {
if (root == NULL || root == p || root == q) return root;
struct TreeNode* left = lowestCommonAncestor(root->left, p, q);
struct TreeNode* right = lowestCommonAncestor(root->right, p, q);
if (left != NULL && right != NULL) return root;
return (left != NULL) ? left : right;
}
6.4 从前序和中序遍历序列构造二叉树
c复制struct TreeNode* buildTree(int* preorder, int preorderSize,
int* inorder, int inorderSize) {
if (preorderSize == 0 || inorderSize == 0) return NULL;
struct TreeNode* root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
root->data = preorder[0];
int rootIndex = 0;
while (inorder[rootIndex] != root->data) rootIndex++;
root->left = buildTree(preorder + 1, rootIndex,
inorder, rootIndex);
root->right = buildTree(preorder + 1 + rootIndex, preorderSize - 1 - rootIndex,
inorder + rootIndex + 1, inorderSize - 1 - rootIndex);
return root;
}
6.5 二叉树的序列化与反序列化
c复制// 序列化:将二叉树转换为字符串
void serializeHelper(struct TreeNode* root, char* str, int* index) {
if (root == NULL) {
str[(*index)++] = '#';
str[(*index)++] = ',';
return;
}
char numStr[20];
sprintf(numStr, "%d", root->data);
strcpy(str + *index, numStr);
*index += strlen(numStr);
str[(*index)++] = ',';
serializeHelper(root->left, str, index);
serializeHelper(root->right, str, index);
}
char* serialize(struct TreeNode* root) {
char* str = (char*)malloc(10000 * sizeof(char));
int index = 0;
serializeHelper(root, str, &index);
str[index] = '\0';
return str;
}
// 反序列化:将字符串转换为二叉树
struct TreeNode* deserializeHelper(char* str, int* index) {
if (str[*index] == '#') {
*index += 2; // 跳过'#'和','
return NULL;
}
int num = 0;
while (str[*index] != ',') {
num = num * 10 + (str[*index] - '0');
(*index)++;
}
(*index)++; // 跳过','
struct TreeNode* root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
root->data = num;
root->left = deserializeHelper(str, index);
root->right = deserializeHelper(str, index);
return root;
}
struct TreeNode* deserialize(char* data) {
int index = 0;
return deserializeHelper(data, &index);
}
7. 树结构的扩展与变种
除了传统的树结构,还有许多扩展和变种形式适用于特定场景。
7.1 线段树(Segment Tree)
线段树是一种用于处理区间查询的高效数据结构。
c复制struct SegmentTreeNode {
int start, end;
int sum; // 可以是min、max或其他聚合值
struct SegmentTreeNode *left, *right;
};
struct SegmentTreeNode* buildSegmentTree(int* nums, int start, int end) {
if (start > end) return NULL;
struct SegmentTreeNode* root = (struct SegmentTreeNode*)malloc(sizeof(struct SegmentTreeNode));
root->start = start;
root->end = end;
if (start == end) {
root->sum = nums[start];
return root;
}
int mid = start + (end - start) / 2;
root->left = buildSegmentTree(nums, start, mid);
root->right = buildSegmentTree(nums, mid + 1, end);
root->sum = root->left->sum + root->right->sum;
return root;
}
void updateSegmentTree(struct SegmentTreeNode* root, int index, int val) {
if (root->start == root->end) {
root->sum = val;
return;
}
int mid = root->start + (root->end - root->start) / 2;
if (index <= mid)
updateSegmentTree(root->left, index, val);
else
updateSegmentTree(root->right, index, val);
root->sum = root->left->sum + root->right->sum;
}
int querySegmentTree(struct SegmentTreeNode* root, int start, int end) {
if (root->end < start || root->start > end) return 0;
if (start <= root->start && root->end <= end) return root->sum;
return querySegmentTree(root->left, start, end) +
querySegmentTree(root->right, start, end);
}
7.2 树状数组(Fenwick Tree)
树状数组是一种支持单点更新和前缀查询的高效数据结构。
c复制struct FenwickTree {
int size;
int* tree;
};
struct FenwickTree* createFenwickTree(int size) {
struct FenwickTree* ft = (struct FenwickTree*)malloc(sizeof(struct FenwickTree));
ft->size = size;
ft->tree = (int*)calloc(size + 1, sizeof(int));
return ft;
}
void updateFenwickTree(struct FenwickTree* ft, int index, int delta) {
while (index <= ft->size) {
ft->tree[index] += delta;
index += index & -index;
}
}
int queryFenwickTree(struct FenwickTree* ft, int index) {
int sum = 0;
while (index > 0) {
sum += ft->tree[index];
index -= index & -index;
}
return sum;
}
7.3 并查集(Disjoint Set Union)
并查集是一种用于处理不相交集合合并与查询的数据结构。
c复制struct DSU {
int* parent;
int* rank;
int size;
};
struct DSU* createDSU(int size) {
struct DSU* dsu = (struct DSU*)malloc(sizeof(struct DSU));
dsu->parent = (int*)malloc(size * sizeof(int));
dsu->rank = (int*)calloc(size, sizeof(int));
dsu->size = size;
for (int i = 0; i < size; i++)
dsu->parent[i] = i;
return dsu;
}
int findDSU(struct DSU* dsu, int x) {
if (dsu->parent[x] != x)
dsu->parent[x] = findDSU(dsu, dsu->parent[x]);
return dsu->parent[x];
}
void unionDSU(struct DSU* dsu, int x, int y) {
int xRoot = findDSU(dsu, x);
int yRoot = findDSU(dsu, y);
if (xRoot == yRoot) return;
if (dsu->rank[xRoot] < dsu->rank[yRoot])
dsu->parent[xRoot] = yRoot;
else if (dsu->rank[xRoot] > dsu->rank[yRoot])
dsu->parent[yRoot] = xRoot;
else {
dsu->parent[yRoot] = xRoot;
dsu->rank[xRoot]++;
}
}
8. 树结构的性能分析与比较
不同的树结构适用于不同场景,了解它们的性能特点有助于正确选择数据结构。
8.1 时间复杂度比较
| 数据结构 | 查找 | 插入 | 删除 | 空间复杂度 |
|---|---|---|---|---|
| 普通二叉树 | O(n) | O(n) | O(n) | O(n) |
| 二叉搜索树 | O(h) | O(h) | O(h) | O(n) |
| AVL树 | O(log n) | O(log n) | O(log n) | O(n) |
| 红黑树 | O(log n) | O(log n) | O(log n) | O(n) |
| B树(阶m) | O(logm n) | O(logm n) | O(logm n) | O(n) |
| B+树 | O(logm n) | O(logm n) | O(logm n) | O(n) |
| 堆 | O(1) | O(log n) | O(log n) | O(n) |
| 字典树 | O(L) | O(L) | O(L) | O(L*n) |
注:h为树高,n为节点数,m为B树阶数,L为字符串长度
8.2 适用场景分析
- 二叉搜索树:适合内存中的有序数据存储,实现简单但性能不稳定
- AVL树:适合查找密集型应用,保证严格平衡
- 红黑树:适合插入删除频繁的场景,如STL中的map/set
- B/B+树:适合磁盘存储和数据库索引,减少I/O操作
- 堆:适合优先级队列、Top K问题等
- 字典树:适合字符串检索、自动补全等场景
- 线段树:适合区间查询、区间更新问题
- 树状数组:适合前缀和查询
