概述
- 树是一种层次结构的数据结构,它由节点(
Node
)和边(Edge
)组成
特点
层次关系
- 树形结构具有明确的层次关系,最上层的节点称为根节点(
Root
) - 每个节点都有零个或多个子节点(
Child
),没有父节点(Parent
)的节点就是根节点
无环性
- 树中不存在环路,即从一个节点沿着树的边不能回到自身
连通性
- 树中的任意两个节点之间有且只有一条路径
递归定义
- 树是一种递归定义的数据结构
- 一个树可以看作是由根节点和若干子树组成,每棵子树又是一个树
树的分类总结
普通树
- 树是一种数据结构,包含很多状态,如二叉树、三叉树、四叉树等
- 这些状态描述的是每个节点可以有多少子节点
二叉树(Binary Tree)形态
-
每个节点最多有两个子节点,通常称为左子节点和右子节点
-
普通二叉树:
- 没有特定的性质约束,任意结构的二叉树
-
完全二叉树(
Complete Binary Tree
)- 除了最后一层,其他每一层都是满的
- 最后一层的节点从左到右依次排列(缺失的节点都在右边)
-
满二叉树(
Full Binary Tree
)- 每个节点要么是叶子节点(没有子节点),要么有两个子节点
- 所有叶子节点都在同一层次上
二叉搜索树(Binary Search Tree, BST)
- 一种特殊的二叉树,满足左子节点的值小于父节点的值,右子节点的值大于父节点的值
- 对于任意节点
n
,其左子树中的所有节点的值都小于n
的值,其右子树中的所有节点的值都大于n
的值 - 这种结构允许快速的查找、插入和删除操作,时间复杂度为
O(log n)
(在平衡的情况下)
- 对于任意节点
平衡二叉树(Balanced Binary Tree)
-
在二叉搜索树的基础上,进一步要求任一节点的左子树和右子树的高度差不超过一定的值(通常是
1
)- 通过平衡操作(如旋转)保持树的高度接近最优,使得查找、插入和删除操作的时间复杂度为
O(log n)
- 通过平衡操作(如旋转)保持树的高度接近最优,使得查找、插入和删除操作的时间复杂度为
-
常见的平衡二叉搜索树有:
AVL
树- 红黑树
多叉树
- 每个节点的子节点数量可以超过
2
个
多路搜索树
- 定义
- 多路搜索树是一种树数据结构,其中每个节点可以有多个子节点(超过两个)
- 节点可以包含多个键值,并且子节点的数量与键值数量有关
- 主要用于高效的查找、插入和删除操作
- 特点
- 每个节点可以包含多个键值
- 每个节点可以有多个子节点
平衡多路搜索树
- 平衡多路搜索树是在多路搜索树的基础上,进一步要求树保持平衡,以确保操作的时间复杂度为
O(log n)
- 通过特定的规则和操作(如节点分裂和合并)来保持树的高度平衡
- 常见的平衡多路搜索树有:
2-3
树2-3-4
树- B树(
B-Tree
) B+
树(B+ Tree
)
图示
二叉树操作
插入
- 从根节点开始,比较插入值和当前节点的值
- 若插入值小于当前节点的值,则移到左子节点继续比较,若大于则移到右子节点
- 直到找到一个空位置,将新节点插入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
struct TreeNode { int value; TreeNode* left; TreeNode* right; TreeNode(int x) : value(x), left(NULL), right(NULL) {} }; TreeNode* insert(TreeNode* root, int key) { if (!root) return new TreeNode(key); if (key < root->value) root->left = insert(root->left, key); else if (key > root->value) root->right = insert(root->right, key); return root; } |
删除
- 找到要删除的节点
- 若该节点无子节点,直接删除
- 若该节点有一个子节点,用子节点替代该节点
- 若该节点有两个子节点,找到其右子树中的最小节点(或左子树中的最大节点),用该节点的值替代要删除的节点,然后删除该节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
TreeNode* findMin(TreeNode* root) { while (root->left) root = root->left; return root; } TreeNode* deleteNode(TreeNode* root, int key) { if (!root) return root; if (key < root->value) { root->left = deleteNode(root->left, key); } else if (key > root->value) { root->right = deleteNode(root->right, key); } else { if (!root->left) { TreeNode* temp = root->right; delete root; return temp; } else if (!root->right) { TreeNode* temp = root->left; delete root; return temp; } TreeNode* temp = findMin(root->right); root->value = temp->value; root->right = deleteNode(root->right, temp->value); } return root; } |
查找
- 从根节点开始,比较查找值和当前节点的值
- 若查找值等于当前节点的值,返回该节点
- 若查找值小于当前节点的值,移到左子节点继续查找
- 若查找值大于当前节点的值,移到右子节点继续查找
1 2 3 4 5 6 7 |
TreeNode* search(TreeNode* root, int key) { if (!root || root->value == key) return root; if (key < root->value) return search(root->left, key); else return search(root->right, key); } |
遍历
- 遍历是指按某种顺序访问树中的所有节点,常见的遍历方法有以下几种:
- 前序遍历(
Preorder Traversal
):- 先访问根节点,然后访问左子树,最后访问右子树
- 根左右
1 2 3 4 5 6 7 |
void preorder(TreeNode* root) { if (root) { cout << root->value << " "; preorder(root->left); preorder(root->right); } } |
- 中序遍历(
Inorder Traversal
):- 先访问左子树,然后访问根节点,最后访问右子树
- 对于二叉搜索树,中序遍历的结果是有序的
- 左根右
1 2 3 4 5 6 7 |
void inorder(TreeNode* root) { if (root) { inorder(root->left); cout << root->value << " "; inorder(root->right); } } |
- 后序遍历(
Postorder Traversal
):- 先访问左子树,然后访问右子树,最后访问根节点
- 左右根
1 2 3 4 5 6 7 |
void postorder(TreeNode* root) { if (root) { postorder(root->left); postorder(root->right); cout << root->value << " "; } } |
- 层次遍历(
Level Order Traversal
):- 按层次从上到下、从左到右访问节点,通常使用队列实现
1 2 3 4 5 6 7 8 9 10 11 12 |
void levelOrder(TreeNode* root) { if (!root) return; queue<TreeNode*> q; q.push(root); while (!q.empty()) { TreeNode* node = q.front(); q.pop(); cout << node->value << " "; if (node->left) q.push(node->left); if (node->right) q.push(node->right); } } |
树高度
- 树的高度是指从根节点到叶子节点的最长路径上的节点数
1 2 3 4 5 6 |
int height(TreeNode* root) { if (!root) return 0; int leftHeight = height(root->left); int rightHeight = height(root->right); return max(leftHeight, rightHeight) + 1; } |
树的节点数
- 统计树中节点的总数
1 2 3 4 |
int countNodes(TreeNode* root) { if (!root) return 0; return 1 + countNodes(root->left) + countNodes(root->right); } |
树的叶子节点数
- 统计树中叶子节点(没有子节点的节点)的数量
1 2 3 4 5 |
int countLeafNodes(TreeNode* root) { if (!root) return 0; if (!root->left && !root->right) return 1; return countLeafNodes(root->left) + countLeafNodes(root->right); } |
判断树是否平衡
- 判断树是否平衡,即任意节点的左右子树高度差不超过
1
1 2 3 4 5 6 7 8 9 10 11 12 13 |
bool isBalanced(TreeNode* root) { return checkHeight(root) != -1; } int checkHeight(TreeNode* root) { if (!root) return 0; int leftHeight = checkHeight(root->left); if (leftHeight == -1) return -1; int rightHeight = checkHeight(root->right); if (rightHeight == -1) return -1; if (abs(leftHeight - rightHeight) > 1) return -1; return max(leftHeight, rightHeight) + 1; } |
树的直径
- 树的直径是指树中任意两个节点之间最长路径上的节点数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
int diameter(TreeNode* root, int& height) { if (!root) { height = 0; return 0; } int leftHeight = 0, rightHeight = 0; int leftDiameter = diameter(root->left, leftHeight); int rightDiameter = diameter(root->right, rightHeight); height = max(leftHeight, rightHeight) + 1; return max({leftHeight + rightHeight + 1, leftDiameter, rightDiameter}); } int diameter(TreeNode* root) { int height = 0; return diameter(root, height); } |
镜像树
- 将树变成其镜像树,即左右子树交换
1 2 3 4 5 6 7 |
TreeNode* mirror(TreeNode* root) { if (!root) return NULL; swap(root->left, root->right); mirror(root->left); mirror(root->right); return root; } |
AVL树
概述
AVL
树是最早发明的自平衡二叉搜索树,它以两位发明者G.M. Adelson-Velsky
和E.M. Landis
的名字命名AVL
树的特点是每个节点的左右子树的高度差(平衡因子)不超过1
- 高度平衡的二叉搜索树
用途
- 数据库索引
AVL
树常用于数据库管理系统中的索引结构- 数据库索引需要高效的查找、插入和删除操作,而
AVL
树通过严格的平衡性保证了这些操作的时间复杂度为O(log n)
,适合处理频繁的动态更新和查询
- 内存管理
- 在内存管理系统中,
AVL
树可以用来管理空闲内存块 - 内存分配和回收操作需要快速查找和更新空闲块的信息,而
AVL
树的平衡性和高效性使其成为理想的选择
- 在内存管理系统中,
- 实时系统
AVL
树适合用于实时系统中的任务调度和事件管理- 实时系统需要保证操作的确定性和高效性,而
AVL
树通过保持树的平衡性,确保了操作的时间复杂度稳定在对数级别,适合用于严格的时间要求场景
- 路由算法
- 在网络路由算法中,
AVL
树可以用于管理路由表 - 路由表需要频繁更新和查找操作,
AVL
树的平衡性和高效的查找性能能够保证路由操作的快速响应
- 在网络路由算法中,
- 字符串处理
AVL
树可以用于字符串处理中的符号表实现,尤其是在编译器设计中- 符号表需要快速查找和更新变量或函数的信息,
AVL
树能够高效管理这些符号,保证编译器的性能
- 动态集合操作
AVL
树适用于实现动态集合操作的数据结构- 在一些算法竞赛和在线评测系统中,
AVL
树用于实现集合数据结构,处理动态数据集的问题,保证了操作的高效性和稳定性
- 操作系统中的文件系统
- 在一些文件系统中,
AVL
树用于管理文件目录结构 - 文件系统需要高效的文件查找和目录更新操作,
AVL
树能够提供高效的查找和更新性能,保证文件系统的响应速度
- 在一些文件系统中,
- 游戏开发
- 在游戏开发中,
AVL
树可以用于管理游戏对象的碰撞检测 - 游戏中的对象通常需要频繁的插入、删除和查找操作,
AVL
树的平衡性保证了这些操作的高效性,从而提高游戏的性能和响应速度
- 在游戏开发中,
- 高精度计时器
AVL
树适用于实现高精度计时器- 在一些系统中,需要管理大量的定时事件,
AVL
树通过平衡性保证了插入、删除和查找操作的高效性,适合用于高精度定时管理
- 网络防火墙
- 在网络防火墙中,
AVL
树可以用于管理规则集 - 防火墙规则需要频繁的查找和更新操作,
AVL
树能够高效管理规则,保证防火墙的性能和安全性
- 在网络防火墙中,
插入操作
- 按照二叉搜索树的规则插入新节点
- 插入后从插入点开始向上回溯,更新每个节点的高度并检查平衡因子
- 若某个节点失衡(平衡因子绝对值大于1),则需要通过旋转操作进行调整
AVL
树的旋转操作有四种:单右旋、单左旋、双右旋和双左旋
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
enum class Color : int { red, black, }; struct Node { int val; // 节点的值 Color color; // 节点的颜色 Node* left; // 左子节点 Node* right; // 右子节点 Node* parent; // 父节点 Node(int value) : val(value), color(Color::red), left(nullptr), right(nullptr), parent(nullptr) {} }; // 左旋转函数 void left_rotate(Node*& root, Node* pt) { Node* y = pt->right; // y为pt的右子节点 pt->right = y->left; // 将y的左子树设为pt的右子树 if (y->left != nullptr) { y->left->parent = pt; // 如果y的左子节点非空,更新其父节点为pt } y->parent = pt->parent; // 将y的父节点设为pt的父节点 if (pt->parent == nullptr) { root = y; // 如果pt是根节点,更新根为y } else if (pt == pt->parent->left) { pt->parent->left = y; // 如果pt是左子节点,更新父节点的左子节点为y } else { pt->parent->right = y; // 如果pt是右子节点,更新父节点的右子节点为y } y->left = pt; // 将pt设为y的左子节点 pt->parent = y; // 更新pt的父节点为y } // 右旋转函数 void right_rotate(Node*& root, Node* pt) { Node* y = pt->left; // y为pt的左子节点 pt->left = y->right; // 将y的右子树设为pt的左子树 if (y->right != nullptr) { y->right->parent = pt; // 如果y的右子节点非空,更新其父节点为pt } y->parent = pt->parent; // 将y的父节点设为pt的父节点 if (pt->parent == nullptr) { root = y; // 如果pt是根节点,更新根为y } else if (pt == pt->parent->left) { pt->parent->left = y; // 如果pt是左子节点,更新父节点的左子节点为y } else { pt->parent->right = y; // 如果pt是右子节点,更新父节点的右子节点为y } y->right = pt; // 将pt设为y的右子节点 pt->parent = y; // 更新pt的父节点为y } // 修复红黑树的插入 void fix_insert(Node*& root, Node* pt) { Node* parent_pt = nullptr; // 父节点 Node* grand_parent_pt = nullptr; // 祖父节点 while ((pt != root) && (pt->parent->color == Color::red)) { parent_pt = pt->parent; grand_parent_pt = parent_pt->parent; if (parent_pt == grand_parent_pt->left) { Node* uncle_pt = grand_parent_pt->right; // 叔叔节点 // 情况1:叔叔是红色 if (uncle_pt != nullptr && uncle_pt->color == Color::red) { grand_parent_pt->color = Color::red; parent_pt->color = Color::black; uncle_pt->color = Color::black; pt = grand_parent_pt; // 将pt上移到祖父节点继续修复 } else { // 情况2:pt是右子节点,需要左旋转 if (pt == parent_pt->right) { pt = parent_pt; left_rotate(root, pt); parent_pt = pt->parent; grand_parent_pt = parent_pt->parent; } // 情况3:pt是左子节点,右旋转 parent_pt->color = Color::black; grand_parent_pt->color = Color::red; right_rotate(root, grand_parent_pt); } } else { // 对称的情况,parent_pt是grand_parent_pt的右子节点 Node* uncle_pt = grand_parent_pt->left; // 情况1:叔叔是红色 if (uncle_pt != nullptr && uncle_pt->color == Color::red) { grand_parent_pt->color = Color::red; parent_pt->color = Color::black; uncle_pt->color = Color::black; pt = grand_parent_pt; // 将pt上移到祖父节点继续修复 } else { // 情况2:pt是左子节点,需要右旋转 if (pt == parent_pt->left) { pt = parent_pt; right_rotate(root, pt); parent_pt = pt->parent; grand_parent_pt = parent_pt->parent; } // 情况3:pt是右子节点,左旋转 parent_pt->color = Color::black; grand_parent_pt->color = Color::red; left_rotate(root, grand_parent_pt); } } } root->color = Color::black; // 将根节点设为黑色 } // 插入节点 void insert(Node*& root, Node* pt) { Node* parent = nullptr; Node* current = root; // 标准的二叉搜索树插入 while (current != nullptr) { parent = current; if (pt->val < current->val) current = current->left; else if (pt->val > current->val) current = current->right; else return; // 不允许插入重复值 } pt->parent = parent; // 设置新节点的父节点 if (parent == nullptr) { root = pt; // 树为空,新节点作为根节点 } else if (pt->val < parent->val) { parent->left = pt; // 新节点为左子节点 } else { parent->right = pt; // 新节点为右子节点 } fix_insert(root, pt); // 修复红黑树性质 } |
删除操作
- 按照二叉搜索树的规则删除节点
- 删除后从删除点开始向上回溯,更新每个节点的高度并检查平衡因子
- 若某个节点失衡,则通过旋转操作进行调整
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
struct TreeNode { int value; TreeNode* left; TreeNode* right; int height; TreeNode(int x) : value(x), left(NULL), right(NULL), height(1) {} }; int getHeight(TreeNode* node) { return node ? node->height : 0; } int getBalance(TreeNode* node) { return node ? getHeight(node->left) - getHeight(node->right) : 0; } TreeNode* rightRotate(TreeNode* y) { TreeNode* x = y->left; TreeNode* T2 = x->right; x->right = y; y->left = T2; y->height = max(getHeight(y->left), getHeight(y->right)) + 1; x->height = max(getHeight(x->left), getHeight(x->right)) + 1; return x; } TreeNode* leftRotate(TreeNode* x) { TreeNode* y = x->right; TreeNode* T2 = y->left; y->left = x; x->right = T2; x->height = max(getHeight(x->left), getHeight(x->right)) + 1; y->height = max(getHeight(y->left), getHeight(y->right)) + 1; return y; } TreeNode* minValueNode(TreeNode* node) { TreeNode* current = node; while (current->left != NULL) current = current->left; return current; } TreeNode* deleteNode(TreeNode* root, int key) { if (!root) return root; if (key < root->value) root->left = deleteNode(root->left, key); else if (key > root->value) root->right = deleteNode(root->right, key); else { if (!root->left || !root->right) { TreeNode* temp = root->left ? root->left : root->right; if (!temp) { temp = root; root = NULL; } else *root = *temp; delete temp; } else { TreeNode* temp = minValueNode(root->right); root->value = temp->value; root->right = deleteNode(root->right, temp->value); } } if (!root) return root; root->height = 1 + max(getHeight(root->left), getHeight(root->right)); int balance = getBalance(root); if (balance > 1 && getBalance(root->left) >= 0) return rightRotate(root); if (balance > 1 && getBalance(root->left) < 0) { root->left = leftRotate(root->left); return rightRotate(root); } if (balance < -1 && getBalance(root->right) <= 0) return leftRotate(root); if (balance < -1 && getBalance(root->right) > 0) { root->right = rightRotate(root->right); return leftRotate(root); } return root; } |
红黑树
概述
- 红黑树是一种近似平衡的二叉搜索树,它通过约束节点的颜色(红或黑)和树的结构,保证树的平衡
- 近似平衡的二叉搜索树
特点
- 点是红色或黑色
- 根节点是黑色
- 所有叶子节点(
NULL
节点)是黑色 - 每个红色节点的两个子节点都是黑色(从每个叶子到根的所有路径上不能有两个连续的红色节点)
- 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点
用途
- 数据库实现
- 红黑树在数据库系统中被广泛应用,尤其是在实现索引结构时
- 红黑树通过保证树的平衡性,使得插入、删除和查找操作的时间复杂度保持在
O(log n)
级别,这对于需要频繁进行数据操作的数据库系统来说是非常重要的
- 内存管理
- 在操作系统的内存管理中,红黑树用于管理空闲内存块
- 内存分配和回收操作需要高效的插入和删除操作,而红黑树的平衡性质使得这些操作能够在对数时间内完成
例如,Linux
内核中的Slab
分配器使用红黑树来管理空闲内存块
STL
C++
标准模板库(STL
)中的关联容器,如std::map
和std::set
,通常通过红黑树实现- 这些容器需要支持高效的插入、删除和查找操作,而红黑树的特性正好满足这些要求
- 多级缓存
- 在一些多级缓存系统中,红黑树用于管理缓存条目
- 红黑树的高效查找和更新操作使得它适合作为缓存的底层数据结构,保证缓存命中和替换操作的高效性
- 动态集合操作
- 红黑树可以用于实现动态集合数据结构,支持集合的插入、删除和查找操作
- 例如,在一些算法竞赛和在线评测系统中,红黑树用于实现集合数据结构,处理动态数据集的问题
- 网络路由表
- 在网络路由器中,红黑树可以用于实现路由表
- 路由表需要高效的查找操作来确定数据包的转发路径,而红黑树能够提供高效的查找性能
- 此外,红黑树的自平衡特性保证了路由表的性能稳定
- 多任务调度
- 在操作系统的调度器中,红黑树用于管理任务队列
- 任务的插入、删除和查找操作需要高效的实现,以保证系统的响应速度和任务调度的公平性
- 红黑树的平衡特性和对数级别的操作时间复杂度使其成为调度器实现的理想选择
- 事件驱动系统
- 在一些事件驱动系统中,红黑树用于管理定时器事件
- 定时器事件需要按照时间顺序进行处理,红黑树能够高效地插入和删除事件,同时保持事件的有序性,保证系统能够按时处理事件
左旋图示
1 2 3 4 5 6 7 8 9 10 11 |
左旋前: x \ y 左旋后: y / x |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
左旋前: X / \ a b / \ c d 左旋后: b / \ X d / \ a c |
左旋总结
- 先保存目标节点
x
的右子节点y
- 左旋的第一步就是将
x
的右子节点y
保存起来,因为y
会在后续操作中成为新的父节点
- 左旋的第一步就是将
- 让
y
的左子节点成为x
的右子节点- 如果
y
有左子节点,这个左子节点在左旋后会变成x
的右子节点 - 这样可以保持树结构的完整性
- 如果
- 判断
x
的情况,让y
代替x
的位置- 如果
x
是根节点(即x
的父节点是nullptr
),就让y
成为新的根节点 - 如果
x
是它父节点的右子节点,那么y
需要成为它父节点的右子节点 - 如果
x
是它父节点的左子节点,那么y
需要成为它父节点的左子节点
- 如果
- 让
x
重新成为树的一部分- 让
x
成为y
的左子节点,这一步是旋转的关键,确保x
没有被移出树的结构 - 将
x
的父节点设置为y
,以完成旋转后的父子关系调整
- 让
右旋图示
1 2 3 4 5 6 7 8 9 10 11 |
右旋前: y / x 右旋后: x \ y |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
右旋前: X / \ a b / \ c d 右旋后: a / \ c x / \ d b |
右旋总结
- 右旋是左旋的镜像过程
- 先保存目标节点
x
的左子节点y
- 让
y
的右子节点成为x
的左子节点 - 判断
x
的情况,让y
代替x
的位置- 和左旋一样
- 让
x
重新成为树的一部分- 让
x
成为y
的右子节点 - 更新
x
的父节点为y
- 让
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 |
#include <iostream> using namespace std; // 定义红黑树的颜色 enum Color { RED, BLACK }; // 红黑树的节点结构 struct Node { int data; // 节点存储的值 Color color; // 节点的颜色 Node *left, *right, *parent; // 左右子节点和父节点指针 // 构造函数 Node(int data) { this->data = data; color = RED; // 新插入的节点默认为红色 left = right = parent = nullptr; } }; // 红黑树类 class RBTree { private: Node *root; Node *TNULL; // 哨兵节点(表示空节点) // 前序遍历 void preOrderHelper(Node *node) { if (node != TNULL) { cout << node->data << " "; preOrderHelper(node->left); preOrderHelper(node->right); } } // 中序遍历 void inOrderHelper(Node *node) { if (node != TNULL) { inOrderHelper(node->left); cout << node->data << " "; inOrderHelper(node->right); } } // 后序遍历 void postOrderHelper(Node *node) { if (node != TNULL) { postOrderHelper(node->left); postOrderHelper(node->right); cout << node->data << " "; } } // 搜索树中键值为 k 的节点 Node* searchTreeHelper(Node* node, int key) { if (node == TNULL || key == node->data) { return node; } if (key < node->data) { return searchTreeHelper(node->left, key); } return searchTreeHelper(node->right, key); } // 修复红黑树在插入后的平衡 void fixInsert(Node *k) { Node *u; while (k->parent->color == RED) { if (k->parent == k->parent->parent->right) { u = k->parent->parent->left; // 叔叔节点 if (u->color == RED) { // 情况 3.1 u->color = BLACK; k->parent->color = BLACK; k->parent->parent->color = RED; k = k->parent->parent; } else { if (k == k->parent->left) { // 情况 3.2.2 k = k->parent; rightRotate(k); } // 情况 3.2.1 k->parent->color = BLACK; k->parent->parent->color = RED; leftRotate(k->parent->parent); } } else { u = k->parent->parent->right; // 叔叔节点 if (u->color == RED) { // 镜像情况 3.1 u->color = BLACK; k->parent->color = BLACK; k->parent->parent->color = RED; k = k->parent->parent; } else { if (k == k->parent->right) { // 镜像情况 3.2.2 k = k->parent; leftRotate(k); } // 镜像情况 3.2.1 k->parent->color = BLACK; k->parent->parent->color = RED; rightRotate(k->parent->parent); } } if (k == root) { break; } } root->color = BLACK; } // 左旋 void leftRotate(Node *x) { Node *y = x->right; x->right = y->left; if (y->left != TNULL) { y->left->parent = x; } y->parent = x->parent; if (x->parent == nullptr) { this->root = y; } else if (x == x->parent->left) { x->parent->left = y; } else { x->parent->right = y; } y->left = x; x->parent = y; } // 右旋 void rightRotate(Node *x) { Node *y = x->left; x->left = y->right; if (y->right != TNULL) { y->right->parent = x; } y->parent = x->parent; if (x->parent == nullptr) { this->root = y; } else if (x == x->parent->right){ x->parent->right = y; } else { x->parent->left = y; } y->right = x; x->parent = y; } // 删除修复平衡 void fixDelete(Node *x) { Node *s; while (x != root && x->color == BLACK) { if (x == x->parent->left) { s = x->parent->right; if (s->color == RED) { // 情况 3.1 s->color = BLACK; x->parent->color = RED; leftRotate(x->parent); s = x->parent->right; } if (s->left->color == BLACK && s->right->color == BLACK) { // 情况 3.2 s->color = RED; x = x->parent; } else { if (s->right->color == BLACK) { // 情况 3.3 s->left->color = BLACK; s->color = RED; rightRotate(s); s = x->parent->right; } // 情况 3.4 s->color = x->parent->color; x->parent->color = BLACK; s->right->color = BLACK; leftRotate(x->parent); x = root; } } else { s = x->parent->left; if (s->color == RED) { // 镜像情况 3.1 s->color = BLACK; x->parent->color = RED; rightRotate(x->parent); s = x->parent->left; } if (s->left->color == BLACK && s->right->color == BLACK) { // 镜像情况 3.2 s->color = RED; x = x->parent; } else { if (s->left->color == BLACK) { // 镜像情况 3.3 s->right->color = BLACK; s->color = RED; leftRotate(s); s = x->parent->left; } // 镜像情况 3.4 s->color = x->parent->color; x->parent->color = BLACK; s->left->color = BLACK; rightRotate(x->parent); x = root; } } } x->color = BLACK; } // 删除节点 void rbTransplant(Node *u, Node *v){ if (u->parent == nullptr) { root = v; } else if (u == u->parent->left){ u->parent->left = v; } else { u->parent->right = v; } v->parent = u->parent; } // 删除树中值为 key 的节点 void deleteNodeHelper(Node *node, int key) { Node *z = TNULL; Node *x, *y; while (node != TNULL){ if (node->data == key) { z = node; } if (node->data <= key) { node = node->right; } else { node = node->left; } } if (z == TNULL) { cout << "未找到键值: " << key << endl; return; } y = z; Color y_original_color = y->color; if (z->left == TNULL) { x = z->right; rbTransplant(z, z->right); } else if (z->right == TNULL) { x = z->left; rbTransplant(z, z->left); } else { y = minimum(z->right); y_original_color = y->color; x = y->right; if (y->parent == z) { x->parent = y; } else { rbTransplant(y, y->right); y->right = z->right; y->right->parent = y; } rbTransplant(z, y); y->left = z->left; y->left->parent = y; y->color = z->color; } delete z; if (y_original_color == BLACK){ fixDelete(x); } } // 查找最小节点 Node* minimum(Node *node) { while (node->left != TNULL) { node = node->left; } return node; } // 打印树 void printHelper(Node *root, string indent, bool last) { if (root != TNULL) { cout << indent; if (last) { cout << "R----"; indent += " "; } else { cout << "L----"; indent += "| "; } string sColor = root->color == BLACK ? "BLACK":"RED"; cout << root->data << "(" << sColor << ")" << endl; printHelper(root->left, indent, false); printHelper(root->right, indent, true); } } public: // 构造函数 RBTree() { TNULL = new Node(0); TNULL->color = BLACK; TNULL->left = TNULL->right = TNULL->parent = nullptr; root = TNULL; } // 前序遍历 void preorder() { preOrderHelper(this->root); } // 中序遍历 void inorder() { inOrderHelper(this->root); } // 后序遍历 void postorder() { postOrderHelper(this->root); } // 搜索树 Node* searchTree(int k) { return searchTreeHelper(this->root, k); } // 查找树的最小值节点 Node* minimum() { return minimum(this->root); } // 插入节点 void insert(int key) { Node *node = new Node(key); node->parent = nullptr; node->left = node->right = TNULL; node->color = RED; // 新节点必须是红色 Node *y = nullptr; Node *x = this->root; while (x != TNULL) { y = x; if (node->data < x->data) { x = x->left; } else if (node->data > x->data) { x = x->right; } else { // 不允许插入重复值 delete node; return; } } node->parent = y; if (y == nullptr) { root = node; } else if (node->data < y->data) { y->left = node; } else { y->right = node; } // 如果新节点的父节点为空,则直接返回 if (node->parent == nullptr){ node->color = BLACK; return; } // 如果祖父节点为空,则直接返回 if (node->parent->parent == nullptr) { return; } // 修复红黑树 fixInsert(node); } // 删除节点 void deleteNode(int data) { deleteNodeHelper(this->root, data); } // 打印红黑树 void prettyPrint() { if (root) { printHelper(this->root, "", true); } } }; int main() { RBTree tree; // 插入节点 tree.insert(55); tree.insert(40); tree.insert(65); tree.insert(60); tree.insert(75); tree.insert(57); cout << "红黑树结构:" << endl; tree.prettyPrint(); cout << "\n在红黑树中删除节点 40" << endl; tree.deleteNode(40); cout << "删除后红黑树结构:" << endl; tree.prettyPrint(); return 0; } |
插入操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
enum class Color : int { red, black, }; struct Node { int val; // 节点的值 Color color; // 节点的颜色 Node* left; // 左子节点 Node* right; // 右子节点 Node* parent; // 父节点 Node(int value) : val(value), color(Color::red), left(nullptr), right(nullptr), parent(nullptr) {} }; // 左旋转函数 void left_rotate(Node*& root, Node* pt) { Node* y = pt->right; // y为pt的右子节点 pt->right = y->left; // 将y的左子树设为pt的右子树 if (y->left != nullptr) { y->left->parent = pt; // 如果y的左子节点非空,更新其父节点为pt } y->parent = pt->parent; // 将y的父节点设为pt的父节点 if (pt->parent == nullptr) { root = y; // 如果pt是根节点,更新根为y } else if (pt == pt->parent->left) { pt->parent->left = y; // 如果pt是左子节点,更新父节点的左子节点为y } else { pt->parent->right = y; // 如果pt是右子节点,更新父节点的右子节点为y } y->left = pt; // 将pt设为y的左子节点 pt->parent = y; // 更新pt的父节点为y } // 右旋转函数 void right_rotate(Node*& root, Node* pt) { Node* y = pt->left; // y为pt的左子节点 pt->left = y->right; // 将y的右子树设为pt的左子树 if (y->right != nullptr) { y->right->parent = pt; // 如果y的右子节点非空,更新其父节点为pt } y->parent = pt->parent; // 将y的父节点设为pt的父节点 if (pt->parent == nullptr) { root = y; // 如果pt是根节点,更新根为y } else if (pt == pt->parent->left) { pt->parent->left = y; // 如果pt是左子节点,更新父节点的左子节点为y } else { pt->parent->right = y; // 如果pt是右子节点,更新父节点的右子节点为y } y->right = pt; // 将pt设为y的右子节点 pt->parent = y; // 更新pt的父节点为y } // 修复红黑树的插入 void fix_insert(Node*& root, Node* pt) { Node* parent_pt = nullptr; // 父节点 Node* grand_parent_pt = nullptr; // 祖父节点 while ((pt != root) && (pt->parent->color == Color::red)) { parent_pt = pt->parent; grand_parent_pt = parent_pt->parent; if (parent_pt == grand_parent_pt->left) { Node* uncle_pt = grand_parent_pt->right; // 叔叔节点 // 情况1:叔叔是红色 if (uncle_pt != nullptr && uncle_pt->color == Color::red) { grand_parent_pt->color = Color::red; parent_pt->color = Color::black; uncle_pt->color = Color::black; pt = grand_parent_pt; // 将pt上移到祖父节点继续修复 } else { // 情况2:pt是右子节点,需要左旋转 if (pt == parent_pt->right) { pt = parent_pt; left_rotate(root, pt); parent_pt = pt->parent; grand_parent_pt = parent_pt->parent; } // 情况3:pt是左子节点,右旋转 parent_pt->color = Color::black; grand_parent_pt->color = Color::red; right_rotate(root, grand_parent_pt); } } else { // 对称的情况,parent_pt是grand_parent_pt的右子节点 Node* uncle_pt = grand_parent_pt->left; // 情况1:叔叔是红色 if (uncle_pt != nullptr && uncle_pt->color == Color::red) { grand_parent_pt->color = Color::red; parent_pt->color = Color::black; uncle_pt->color = Color::black; pt = grand_parent_pt; // 将pt上移到祖父节点继续修复 } else { // 情况2:pt是左子节点,需要右旋转 if (pt == parent_pt->left) { pt = parent_pt; right_rotate(root, pt); parent_pt = pt->parent; grand_parent_pt = parent_pt->parent; } // 情况3:pt是右子节点,左旋转 parent_pt->color = Color::black; grand_parent_pt->color = Color::red; left_rotate(root, grand_parent_pt); } } } root->color = Color::black; // 将根节点设为黑色 } // 插入节点 void insert(Node*& root, Node* pt) { Node* parent = nullptr; Node* current = root; // 标准的二叉搜索树插入 while (current != nullptr) { parent = current; if (pt->val < current->val) current = current->left; else if (pt->val > current->val) current = current->right; else return; // 不允许插入重复值 } pt->parent = parent; // 设置新节点的父节点 if (parent == nullptr) { root = pt; // 树为空,新节点作为根节点 } else if (pt->val < parent->val) { parent->left = pt; // 新节点为左子节点 } else { parent->right = pt; // 新节点为右子节点 } fix_insert(root, pt); // 修复红黑树性质 } |
删除操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
#include <iostream> using namespace std; enum Color { RED, BLACK }; // 定义节点颜色 struct TreeNode { int value; // 节点值 Color color; // 节点颜色 TreeNode* left; // 左子节点 TreeNode* right; // 右子节点 TreeNode* parent; // 父节点 TreeNode(int val) : value(val), color(RED), left(NULL), right(NULL), parent(NULL) {} }; // 左旋操作 void leftRotate(TreeNode*& root, TreeNode* x) { TreeNode* y = x->right; // 将y设为x的右子节点 x->right = y->left; // y的左子节点成为x的右子节点 if (y->left != NULL) y->left->parent = x; // 更新y左子节点的父节点为x y->parent = x->parent; // y的父节点更新为x的父节点 if (x->parent == NULL) root = y; // 如果x是根节点,更新根为y else if (x == x->parent->left) x->parent->left = y; // 如果x是左子节点,更新父节点的左子节点为y else x->parent->right = y; // 如果x是右子节点,更新父节点的右子节点为y y->left = x; // x成为y的左子节点 x->parent = y; // 更新x的父节点为y } // 右旋操作 void rightRotate(TreeNode*& root, TreeNode* y) { TreeNode* x = y->left; // 将x设为y的左子节点 y->left = x->right; // x的右子节点成为y的左子节点 if (x->right != NULL) x->right->parent = y; // 更新x右子节点的父节点为y x->parent = y->parent; // x的父节点更新为y的父节点 if (y->parent == NULL) root = x; // 如果y是根节点,更新根为x else if (y == y->parent->left) y->parent->left = x; // 如果y是左子节点,更新父节点的左子节点为x else y->parent->right = x; // 如果y是右子节点,更新父节点的右子节点为x x->right = y; // y成为x的右子节点 y->parent = x; // 更新y的父节点为x } // 替换节点u为节点v void rbTransplant(TreeNode*& root, TreeNode* u, TreeNode* v) { if (u->parent == NULL) root = v; // u是根节点,更新根为v else if (u == u->parent->left) u->parent->left = v; // u是左子节点,更新父节点的左子节点为v else u->parent->right = v; // u是右子节点,更新父节点的右子节点为v if (v != NULL) v->parent = u->parent; // 更新v的父节点为u的父节点 } // 找到以node为根的子树中的最小值节点 TreeNode* minValueNode(TreeNode* node) { while (node->left != NULL) node = node->left; // 一直向左遍历找到最小值 return node; } // 修复红黑树删除后的性质 void fixDelete(TreeNode*& root, TreeNode* x) { while (x != root && (x == NULL || x->color == BLACK)) { if (x == x->parent->left) { TreeNode* w = x->parent->right; // w是x的兄弟节点 if (w->color == RED) { w->color = BLACK; // 情况1:w是红色 x->parent->color = RED; leftRotate(root, x->parent); w = x->parent->right; } if ((w->left == NULL || w->left->color == BLACK) && (w->right == NULL || w->right->color == BLACK)) { w->color = RED; // 情况2:w的子节点都是黑色 x = x->parent; } else { if (w->right == NULL || w->right->color == BLACK) { if (w->left != NULL) w->left->color = BLACK; // 情况3:w的左子节点是红色,右子节点是黑色 w->color = RED; rightRotate(root, w); w = x->parent->right; } w->color = x->parent->color; // 情况4:w的右子节点是红色 x->parent->color = BLACK; if (w->right != NULL) w->right->color = BLACK; leftRotate(root, x->parent); x = root; } } else { // 对称的情况 TreeNode* w = x->parent->left; if (w->color == RED) { w->color = BLACK; x->parent->color = RED; rightRotate(root, x->parent); w = x->parent->left; } if ((w->left == NULL || w->left->color == BLACK) && (w->right == NULL || w->right->color == BLACK)) { w->color = RED; x = x->parent; } else { if (w->left == NULL || w->left->color == BLACK) { if (w->right != NULL) w->right->color = BLACK; w->color = RED; leftRotate(root, w); w = x->parent->left; } w->color = x->parent->color; x->parent->color = BLACK; if (w->left != NULL) w->left->color = BLACK; rightRotate(root, x->parent); x = root; } } } if (x != NULL) x->color = BLACK; // 将x涂黑 } // 搜索树中值为key的节点 TreeNode* searchTree(TreeNode* node, int key) { if (node == NULL || node->value == key) return node; // 找到节点或到达叶子节点 if (key < node->value) return searchTree(node->left, key); // 在左子树中查找 return searchTree(node->right, key); // 在右子树中查找 } // 删除节点z void deleteNode(TreeNode*& root, TreeNode* z) { TreeNode* y = z; // y为待删除节点或其后继节点 Color y_original_color = y->color; // 保存y的原始颜色 TreeNode* x; // x为替代y位置的节点 if (z->left == NULL) { x = z->right; // x为z的右子节点 rbTransplant(root, z, z->right); // 用z的右子节点替换z } else if (z->right == NULL) { x = z->left; // x为z的左子节点 rbTransplant(root, z, z->left); // 用z的左子节点替换z } else { y = minValueNode(z->right); // y为z的后继节点 y_original_color = y->color; // 保存y的原始颜色 x = y->right; // x为y的右子节点 if (y->parent == z) { if (x != NULL) x->parent = y; // 更新x的父节点为y } else { rbTransplant(root, y, y->right); // 用y的右子节点替换y y->right = z->right; // y的右子节点更新为z的右子节点 y->right->parent = y; // 更新右子节点的父节点为y } rbTransplant(root, z, y); // 用y替换z y->left = z->left; // y的左子节点更新为z的左子节点 y->left->parent = y; // 更新左子节点的父节点为y y->color = z->color; // y的颜色更新为z的颜色 } if (y_original_color == BLACK) { fixDelete(root, x); // 如果删除的节点是黑色,修复红黑树 } delete z; // 删除节点z } // 根据键值删除节点 void deleteByKey(TreeNode*& root, int key) { TreeNode* z = searchTree(root, key); // 查找要删除的节点 if (z == NULL) return; // 未找到节点 deleteNode(root, z); // 删除节点 } |
问题与理解
-
C++
标准模板库(STL
)中的std::map
和std::set
选择使用红黑树而不是AVL
树,为什么?-
插入和删除操作的平衡调整开销
红黑树:
红黑树的插入和删除操作通常涉及较少的旋转和颜色变换,插入操作最多需要两次旋转,删除操作也在常数次旋转内完成
AVL
树:
AVL
树需要在插入和删除操作中进行高度的重新平衡,这通常涉及更多次的旋转操作(插入最多需要两次旋转,删除可能需要多次旋转和重新平衡)
比较:
由于红黑树在这些操作上的旋转次数和调整代价较低,在需要频繁插入和删除操作的场景下,红黑树的性能通常优于AVL
树 -
平衡条件的差异
红黑树:
红黑树的平衡条件较为宽松,只要求从根节点到任意叶节点的最长路径不超过最短路径的两倍
这样的宽松条件允许红黑树保持相对较少的旋转操作,同时仍然保证O(log n)
的时间复杂度
AVL
树:
AVL
树要求每个节点的左右子树高度差不超过1
,这使得树高度非常接近最优,但也导致插入和删除操作需要更多的旋转和调整
比较:
红黑树的宽松平衡条件使得它在处理大量插入和删除操作时,性能更加稳定和高效 -
实际性能考虑
在大多数应用场景中,查找操作的时间复杂度在O(log n)
已经足够快速,而插入和删除操作的实际性能差异会对整体系统性能产生更大的影响
红黑树在这种平衡性和调整开销上的折中使得它在实际应用中表现出更好的性能 -
历史原因和标准化
红黑树的算法和实现细节在计算机科学领域得到了广泛研究和应用,其性能和稳定性在实际应用中得到了验证
尽管AVL
树在查找操作中具有较好的平衡性,但其在插入和删除操作中的调整开销较大,使得它在某些场景下不如红黑树高效
-
2-3
树简介
概述
2-3
树是一种自平衡多路搜索树,每个节点可以有2
个子节点(2
节点)或3
个子节点(3
节点)2-3
树保持平衡性,所有叶子节点在同一层次上
特点
- 2节点:
- 包含一个键值和两个子节点
- 3节点:
- 包含两个键值和三个子节点
- 平衡性:
- 树保持高度平衡,所有叶子节点都在同一层次上
- 操作效率:
- 查找、插入和删除操作的时间复杂度为
O(log n)
- 查找、插入和删除操作的时间复杂度为
结构
- 2节点:
- 形式为
[K]
,其中K
是键值。 - 有两个子节点,左子节点的所有键值小于
K
,右子节点的所有键值大于K
- 形式为
- 3节点:
- 形式为
[K1, K2]
,其中K1
和K2
是键值,且K1 < K2
。 - 有三个子节点:
第一个子节点的所有键值小于K1
第二个子节点的所有键值介于K1
和K2
之间
第三个子节点的所有键值大于K2
- 形式为
操作
- 查找:
- 从根节点开始,根据键值的比较选择相应的子节点,递归进行,直到找到目标键值或达到叶子节点
- 插入:
- 找到适当的叶子节点进行插入
- 如果插入导致节点超载(变成4节点),则需要分裂节点,并将中间键值提升到父节点中
- 分裂可能会递归地向上进行,直到根节点
- 删除:
- 找到目标键值并删除
- 如果删除导致节点不足(少于
1
个键值),需要从兄弟节点借用键值或合并节点 - 合并或借用可能会递归地向上进行,直到根节点
用途
- 主要用于实现
B
树或红黑树 - 数据库实现
- 文件系统
- 内存管理
- 磁盘存储
- 动态集合操作
- 索引和排序
- 编译器实现
- 实时系统
- 高度并发系统
- 游戏开发
示例
- 假设有如下
2-3
树: - 根节点:
[10, 20]
- 这是一个
3
节点,有两个键值10
和20
- 有三个子节点
- 第一个子节点
[5, 7]
:- 所有键值都小于
10
- 所有键值都小于
- 第二个子节点
[12, 15]
:- 所有键值都介于
10
和20
之间
- 所有键值都介于
- 第三个子节点
[25, 30]
:- 所有键值都大于
20
- 所有键值都大于
1 2 3 |
[10, 20] / | \ [5, 7] [12, 15] [25, 30] |
2-3树和B树
-
相似点
- 平衡性:两者都保持高度平衡,确保所有叶子节点在同一层次上
- 操作:插入和删除操作都可能导致节点的分裂和合并,以维持树的平衡
- 用途:都适用于需要高效查找、插入和删除操作的应用场景,如数据库和文件系统
-
区别点
-
节点类型
2-3
树:每个节点可以是2
节点或3
节点B
树:每个节点可以包含更多的键值和子节点,具体数量取决于树的阶 -
灵活性
2-3
树:节点的类型和子节点数量较固定,只有2
节点和3
节点
B
树:节点的键值和子节点数量更灵活,可以根据树的阶调整
-
2-3-4
树简介
概述
2-3-4
树是2-3
树的扩展,每个节点可以有2
个、3
个或4
个子节点
特点
- 保持高度平衡,所有叶子节点在同一层次上
- 每个节点最多可以有三个键值和四个子节点
2
节点:- 包含一个键值和两个子节点
3
节点:- 包含两个键值和三个子节点
4
节点:- 包含三个键值和四个子节点
结构
2
节点:- 形式为
[K]
,其中K
是键值 - 有两个子节点,左子节点的所有键值小于
K
,右子节点的所有键值大于K
- 形式为
3
节点:- 形式为
[K1, K2]
,其中K1
和K2
是键值,且K1 < K2
- 有三个子节点:
第一个子节点的所有键值小于K1
第二个子节点的所有键值介于K1
和K2
之间
第三个子节点的所有键值大于K2
- 形式为
4
节点:- 形式为
[K1, K2, K3]
,其中K1 < K2 < K3
- 有四个子节点:
第一个子节点的所有键值小于K1
第二个子节点的所有键值介于K1
和K2
之间
第三个子节点的所有键值介于K2
和K3
之间
第四个子节点的所有键值大于K3
- 形式为
操作
- 插入:
- 如果插入导致节点超载(变成
5
节点),则需要分裂节点,并将中间键值提升到父节点中
- 如果插入导致节点超载(变成
- 删除:
- 如果删除导致节点不足(少于
1
个键值),需要从兄弟节点借用键值或合并节点
- 如果删除导致节点不足(少于
用途
- 主要用于实现
B
树或红黑树
示例
- 假设有如下2-3-4树:
- 根节点
[10, 20, 30]
- 这是一个
4
节点,有三个键值10
、20
和30
- 有四个子节点
- 根节点
- 第一个子节点
[5, 7]
:- 所有键值都小于
10
- 所有键值都小于
- 第二个子节点
[12, 15]
:- 所有键值都介于
10
和20
之间
- 所有键值都介于
- 第三个子节点
[22, 25]
:- 所有键值都介于
20
和30
之间
- 所有键值都介于
- 第四个子节点
[35, 40, 45]
:- 所有键值都大于
30
- 所有键值都大于
1 2 3 |
[10, 20, 30] / | | \ [5, 7] [12, 15] [22, 25] [35, 40, 45] |
B
树(B-树)简介
概述
B
树是一种自平衡的多路搜索树,其中每个节点可以包含多个键和多个子节点B
树的阶(degree
)m
是一个重要的参数,定义了每个节点的最大子节点数量
特点
- 平衡性:
- 所有叶子节点在同一层次上
- 多路性:
- 每个节点可以有多个子节点
- 键的数量:
- 每个节点包含的键的数量在一定范围内(一般为
⌈m/2⌉ - 1
到m - 1
之间)
- 每个节点包含的键的数量在一定范围内(一般为
- 子节点数量:
- 非叶子节点的子节点数量在
⌈m/2⌉
到m
之间
- 非叶子节点的子节点数量在
- 高效性:
- 插入、删除和查找操作的时间复杂度为
O(log n)
- 插入、删除和查找操作的时间复杂度为
操作
- 查找
- 从根节点开始,根据键值的比较选择相应的子节点,递归进行,直到找到目标键或达到叶子节点
- 插入
- 找到适当的叶子节点进行插入,如果节点满了,则需要分裂节点并调整树的结构
- 删除
- 找到目标键并删除,如果删除导致节点不足,需要合并或重新分配键以保持树的平衡
结构
- 根节点至少有两个子节点,除非树为空或只有一个节点
- 每个节点包含的键值按升序排列,节点内的键值分隔其子节点
B
树通过节点的分裂和合并保持平衡,使得树的高度尽可能低,从而提高操作效率
示例
- 假设有一个阶为
m=4
的B
树:- 根节点包含
3
个键:[10, 20, 30]
- 根节点有
4
个子节点,分别在这3
个键之间分隔开
- 根节点包含
- 左子节点:
[1, 5, 7]
- 这是根节点
[10]
左边的子节点。所有值都小于10
- 第二个子节点:
[12, 15]
- 这是根节点
[10, 20]
之间的子节点 - 所有值都介于
10
和20
之间
- 第三个子节点:
[22, 25]
- 这是根节点
[20, 30]
之间的子节点 - 所有值都介于
20
和30
之间
- 右子节点:
[35, 40, 45]
- 这是根节点
[30]
右边的子节点 - 所有值都大于
30
1 2 3 |
[10, 20, 30] / | | \ [1, 5, 7] [12, 15] [22, 25] [35, 40, 45] |
理解与总结
- 在
B
树中,如果一个节点有n
个有序的键值,那么它必须有n+1
个子节点 - 每个子节点的值范围由父节点的键值分割而成
- 说白了,就是按父节点的有序键值,分成
n+1
个范围
- 说白了,就是按父节点的有序键值,分成
应用
- 数据库索引:
B
树广泛用于数据库管理系统中的索引结构,以高效地管理大量数据
- 文件系统:
- 许多文件系统使用B树来组织文件和目录,提供快速的文件访问和管理
- 存储系统:
B
树在存储系统中用于管理数据块和索引,优化存储和检索性能
B+
树简介
概述
- B树的一种变体
- 所有键值都存储在叶子节点,内部节点只存储键值用于索引
节点类型
- 内部节点(非叶子节点):
- 只存储键值和子节点指针,用于索引
- 叶子节点:
- 存储所有实际数据,并通过链表连接,以便于顺序访问
特点
- 保持高度平衡,所有叶子节点在同一层次上
- 所有数据存储在叶子节点,叶子节点之间通过链表连接,便于范围查询和顺序访问
- 内部节点用于快速索引,提高查找效率
操作效率
- 查找、插入和删除操作的时间复杂度为
O(log n)
- 顺序访问和范围查询性能优越
结构
- 内部节点:
- 包含
m-1
个键值和m
个子节点指针,键值用于索引,子节点指针用于指向子节点 - 形式为
[K1, K2, ..., Km-1]
,其中K1 < K2 < ... < Km-1
- 有
m
个子节点:
第一个子节点的所有键值小于K1
第i
个子节点的所有键值介于Ki-1
和Ki
之间
第m
个子节点的所有键值大于Km-1
- 包含
- 叶子节点:
- 包含
l
个键值和数据指针,所有叶子节点按键值的顺序通过链表连接 - 形式为
[D1, D2, ..., Dl]
,其中D1 < D2 < ... < Dl
- 叶子节点之间通过链表连接,便于顺序访问
- 包含
操作
- 查找:
- 从根节点开始,根据键值的比较选择相应的子节点,递归进行,直到达到叶子节点
- 在叶子节点中找到目标键值对应的数据
- 插入:
- 找到适当的叶子节点进行插入
- 如果插入导致叶子节点超载,则需要分裂叶子节点,并将中间键值提升到父节点中
- 分裂可能会递归地向上进行,直到根节点
- 删除:
- 找到目标键值并在叶子节点中删除
- 如果删除导致叶子节点不足,需要从兄弟节点借用键值或合并节点
- 合并或借用可能会递归地向上进行,直到根节点
示例
- 假设有如下
B+
树(假设阶为4
): - 根节点
[10, 20, 30]
:- 这是一个内部节点,有三个键值
10
、20
和30
- 有四个子节点
- 第一个子节点
[1, 5, 7]
:
所有键值都小于10
- 第二个子节点
[12, 15]
:
所有键值都介于10
和20
之间 - 第三个子节点
[22, 25]
:
所有键值都介于20
和30
之间 - 第四个子节点
[35, 40, 45]
:
所有键值都大于30
- 这是一个内部节点,有三个键值
- 叶子节点
[1, 5, 7]
、[12, 15]
、[22, 25]
、[35, 40, 45]
:- 存储所有实际数据
- 叶子节点按键值顺序通过链表连接,便于顺序访问
1 2 3 |
[10, 20, 30] / | | \ [1, 5, 7] [12, 15] [22, 25] [35, 40, 45] |
B树与B+树比较
- 数据存储位置
B
树:
数据存储在所有节点(叶子节点和内部节点)
内部节点不仅用于索引,还可以存储实际的数据B+
树:
所有实际数据都存储在叶子节点
内部节点只存储键值用于索引,不存储实际的数据
- 叶子节点链表
B
树:
叶子节点之间没有链表连接B+
树:
叶子节点通过链表相互连接,这使得范围查询和顺序访问更加高效
- 树高
- 都保持平衡,确保树的高度尽可能低,查找、插入和删除操作的时间复杂度为
O(log n)
- 由于
B+
树的叶子节点存储了所有数据,树的高度可能略大于B
树,但一般差别不大
- 都保持平衡,确保树的高度尽可能低,查找、插入和删除操作的时间复杂度为
- 操作:查找
B
树:
从根节点开始,依次比较并下探到子节点,直到找到目标数据B+
树:
从根节点开始,依次比较并下探到子节点,直到达到叶子节点,在叶子节点找到目标数据
- 操作:插入
B
树:
找到适当的位置插入数据,如果节点满了,需要分裂节点,并将中间键值提升到父节点B+
树:
找到适当的叶子节点插入数据,如果叶子节点满了,需要分裂叶子节点,并将中间键值提升到父节点
- 操作:删除
B
树:
找到目标数据并删除,如果删除导致节点不足,需要从兄弟节点借用或合并节点B+
树:
找到目标数据并在叶子节点删除,如果删除导致叶子节点不足,需要从兄弟节点借用或合并节点,并可能需要调整父节点的索引
B树与B+树示例
B
树(假设阶为4
)
1 2 3 4 |
内部节点: [10, 20, 30] / | | \ [5] [15] [25] [35] |
B+
树(假设阶为4
)
1 2 3 4 5 6 7 |
内部节点: [10, 20, 30] / | | \ 叶子节点: [1, 5, 7] [12, 15] [22, 25] [35, 40, 45] 链表连接: [1, 5, 7] -> [12, 15] -> [22, 25] -> [35, 40, 45] |
B树与B+树优缺点
- B树
- 优点:可以在较少的节点读取中找到数据,适用于较少范围查询的应用
- 缺点:范围查询效率较低,内部节点和叶子节点结构相同,可能导致较高的复杂性
- B+树
- 优点:叶子节点通过链表连接,范围查询和顺序访问性能优越,内部节点更简洁,适用于频繁的范围查询和顺序访问的应用
- 缺点:查找数据时需要到达叶子节点,可能会稍微增加查找路径长度
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ 大话数据结构_基础概念11/01
- ♥ 大话数据结构_赫夫曼树与应用01/11
- ♥ 大话数据结构_树11/02
- ♥ 大话数据结构_图遍历01/27
- ♥ 大话数据结构_算法相关&&AVL&&B树相关02/20
- ♥ 大话数据结构_线索二叉树11/04