跳到主要内容C++ 实现 AVL 树:结构、插入与旋转操作详解 | 极客日志C++算法
C++ 实现 AVL 树:结构、插入与旋转操作详解
综述由AI生成介绍 C++ 中 AVL 树的实现。AVL 树是一种自平衡二叉查找树,通过平衡因子控制左右子树高度差不超过 1。内容包括节点结构定义、插入操作(含更新平衡因子)、四种旋转操作(左旋、右旋、左右双旋、右左双旋)以恢复平衡,以及查找、高度计算等辅助功能。代码展示了完整实现及测试用例,时间复杂度为 O(log N)。
古灵精怪21 浏览 从结构到操作:手撕 AVL 树的实现
一、AVL 树介绍
1.1 什么是 AVL 树
AVL 树是一种自平衡的二叉查找树(BST),由 G.M. Adelson-Velsky 和 E.M. Landis 于 1962 年提出。AVL 树的核心特点是它保证树的每个节点的左右子树的高度差(平衡因子)不超过 1,从而保证了 AVL 树在插入、删除和查找操作时的时间复杂度始终为 O(log N)。
1.2 平衡因子的定义
每个节点都有一个 平衡因子 (_bf),它表示节点左子树的高度减去右子树的高度:
平衡因子 = 左子树的高度 - 右子树的高度
- 如果平衡因子为 0,表示左右子树的高度相等。
- 如果平衡因子为 1,表示左子树比右子树高 1。
- 如果平衡因子为 -1,表示右子树比左子树高 1。
1.3 平衡的意义
AVL 树的自平衡特性确保了其查询、插入和删除操作的最坏时间复杂度为 O(log N),避免了普通二叉查找树的退化(比如变成链表)。因此,AVL 树非常适用于需要频繁插入和删除操作的场景。
1.4 AVL 树的操作
AVL 树的主要操作有:
- 插入操作:在树中插入节点,并在插入后调整平衡因子。如果平衡因子为 2 或 -2,执行旋转操作恢复平衡。
- 删除操作:删除节点,并且在删除节点后,可能需要调整树的结构以保持平衡。由于 AVL 树的删除操作涉及复杂的旋转和调整,因此在本文中我们不会详细讲解该操作。如果你希望深入了解,可以参考《算法导论》中的相关章节来获取详细内容。
- 查找操作:通过比较节点值来查找特定的元素。
- 旋转操作:当树失衡时,使用旋转来恢复平衡,常见的旋转有左旋、右旋、左右旋和右左旋。
二、AVL 树的节点结构
在实现 AVL 树之前,首先要定义节点结构。每个节点存储以下信息:
- 键值对 (
_kv):存储数据 (pair<K, V>)。
- 左右子树指针 (
_left, _right):指向左右子树。
- 父节点指针 (
_parent):指向父节点。
- 平衡因子 (
_bf):用于标识该节点的左右子树的高度差。
2.1 节点结构的定义:
template <class K, class V>
struct AVLTreeNode {
pair<K, V> _kv;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
_bf;
( pair<K, V>& kv) : _kv(kv), _left(), _right(), _parent(), _bf() {}
};
int
AVLTreeNode
const
nullptr
nullptr
nullptr
0
三、插入操作
3.1 插入操作概述
- 按二叉查找树规则插入节点;
- 更新每个节点的平衡因子;
- 如果需要,进行旋转操作 来恢复树的平衡。
bool Insert(const pair<K, V>& kv);
3.2 步骤 1:按二叉查找树规则插入节点
在插入节点时,我们根据值的大小,递归地找到插入位置,并在该位置插入新节点:
if (_root == nullptr) {
_root = new Node(kv);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur) {
if (cur->_kv.first < kv.first) {
parent = cur;
cur = cur->_right;
} else if (cur->_kv.first > kv.first) {
parent = cur;
cur = cur->_left;
} else {
return false;
}
}
cur = new Node(kv);
if (parent->_kv.first < kv.first) {
parent->_right = cur;
} else {
parent->_left = cur;
}
cur->_parent = parent;
3.3 步骤 2:更新平衡因子
插入节点后,我们从新插入的节点开始,逐步更新路径上每个节点的平衡因子。插入时会对父节点的平衡因子进行调整:
while (parent) {
if (cur == parent->_left) {
parent->_bf++;
} else {
parent->_bf--;
}
if (parent->_bf == 0) {
break;
} else if (parent->_bf == 1 || parent->_bf == -1) {
cur = parent;
parent = cur->_parent;
} else if (parent->_bf == 2 || parent->_bf == -2) {
if (parent->_bf == 2 && cur->_bf == 1) {
RotateR(parent);
} else if (parent->_bf == -2 && cur->_bf == -1) {
RotateL(parent);
} else if (parent->_bf == 2 && cur->_bf == -1) {
RotateLR(parent);
} else if (parent->_bf == -2 && cur->_bf == 1) {
RotateRL(parent);
} else assert(false);
break;
} else {
assert(false);
}
}
return true;
3.4 步骤 3:旋转操作详解
如果某个节点的平衡因子为 2 或 -2,表示该节点不平衡。我们需要通过旋转操作来恢复平衡。旋转操作有四种:
- 右单旋 (RotateR)
- 左单旋 (RotateL)
- 左右双旋 (RotateLR)
- 右左双旋 (RotateRL)
3.4.1 右单旋
右单旋适用于当左子树较高时,执行旋转操作来恢复平衡。
void RotateR(Node* parent) {
Node* subL = parent->_left;
Node* subLR = subL->_right;
Node* ppNode = parent->_parent;
parent->_left = subLR;
if (subLR) {
subLR->_parent = parent;
}
subL->_right = parent;
parent->_parent = subL;
if (parent == _root) {
subL->_parent = nullptr;
_root = subL;
} else {
subL->_parent = ppNode;
if (parent == ppNode->_right) ppNode->_right = subL;
else ppNode->_left = subL;
}
parent->_bf = 0;
subL->_bf = 0;
}
3.4.2 左单旋
左单旋适用于当右子树较高时,执行旋转操作来恢复平衡。
void RotateL(Node* parent) {
Node* subR = parent->_right;
Node* ppNode = parent->_parent;
Node* subRL = subR->_left;
parent->_right = subRL;
parent->_parent = subR;
if (subRL) {
subRL->_parent = parent;
}
subR->_left = parent;
if (parent == _root) {
_root = subR;
subR->_parent = nullptr;
} else {
subR->_parent = ppNode;
if (parent == ppNode->_right) ppNode->_right = subR;
else ppNode->_left = subR;
}
parent->_bf = 0;
subR->_bf = 0;
}
3.4.3 左右双旋
当左子树的右子树较高时,首先进行左旋,再进行右旋,恢复平衡。
void RotateLR(Node* parent) {
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(subL);
RotateR(parent);
if (bf == 1) {
subLR->_bf = 0;
subL->_bf = 0;
parent->_bf = -1;
} else if (bf == -1) {
subLR->_bf = 0;
subL->_bf = 1;
parent->_bf = 0;
} else if (bf == 0) {
subL->_bf = subLR->_bf = parent->_bf = 0;
} else {
assert(false);
}
}
- 左子树的左子树高:进行 R
- 右子树的右子树高:进行 L
- 左子树的右子树高:进行 LR
- 右子树的左子树高:进行 RL
四、其他操作
4.1 查找操作 (Find)
查找操作与普通的二叉查找树类似。通过不断比较当前节点的键值和目标值,沿树的路径查找目标节点。
Node* Find(const K& key) {
Node* cur = _root;
while (cur) {
if (cur->_kv.first < key) cur = cur->_right;
else if (cur->_kv.first > key) cur = cur->_left;
else return cur;
}
return nullptr;
}
该方法的时间复杂度为 O(log N),因为 AVL 树是平衡的,树的高度是对数级别的。
4.2 计算树的高度 (Height)
树的高度指的是从根节点到最远叶子节点的最长路径上的边数。AVL 树的高度是平衡的,计算时我们需要递归地计算左右子树的高度并返回较大的一个。
int Height() {
return _Height(_root);
}
int _Height(Node* root) {
if (root == nullptr) return 0;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
该方法的时间复杂度为 O(N),其中 N 为节点总数。虽然 AVL 树是平衡的,但在计算高度时需要遍历整个树。
4.3 计算节点数量 (Size)
节点数量是树中所有节点的总数。通过递归遍历树,计算左右子树的节点数量,并加上当前节点。
int Size() {
return _Size(_root);
}
int _Size(Node* root) {
if (root == nullptr) return 0;
return _Size(root->_left) + _Size(root->_right) + 1;
}
该方法的时间复杂度为 O(N),需要遍历整个树来计算节点总数。
4.4 树是否平衡 (IsBalanceTree)
为了验证 AVL 树的平衡性,我们需要检查每个节点的平衡因子,并确保它们的绝对值不超过 1。如果树的任意节点的平衡因子大于 1 或小于 -1,那么树就不平衡。
bool _IsBalanceTree(Node* root) {
if (root == nullptr) return true;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
int diff = leftHeight - rightHeight;
if (abs(diff) >= 2) {
cout << root->_kv.first << "高度差异常" << endl;
return false;
}
if (root->_bf != diff) {
cout << root->_kv.first << "平衡因子异常" << endl;
return false;
}
return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}
该方法的时间复杂度为 O(N),需要递归遍历整个树并计算每个节点的平衡因子。
五、性能分析与测试
5.1 性能分析
AVL 树通过保持平衡,保证了插入、查找和删除操作的时间复杂度都为 O(logN),相比于普通的二叉查找树,AVL 树避免了退化成链表的情况,极大提高了性能。
5.2 测试代码
下面是两个测试代码,用于验证 AVL 树的插入和查找操作。
void TestAVLTree1() {
AVLTree<int, int> t;
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
for (auto e : a) {
t.Insert({ e, e });
cout << "Insert:" << e << "->";
cout << t.IsBalanceTree() << endl;
}
t.InOrder();
cout << t.IsBalanceTree() << endl;
}
void TestAVLTree2() {
const int N = 1000000;
vector<int> v;
v.reserve(N);
srand((unsigned int)time(0));
for (int i = 0; i < N; i) {
v.push_back(rand() + i);
}
size_t begin2 = clock();
AVLTree<int, int> t;
for (auto e : v) {
t.Insert(make_pair(e, e));
}
size_t end2 = clock();
cout << t.IsBalanceTree() << endl;
cout << "Insert:" << end2 - begin2 << endl;
cout << "Height:" << t.Height() << endl;
cout << "Size:" << t.Size() << endl;
size_t begin1 = clock();
for (int i = 0; i < N; i) {
t.Find((rand() + i));
}
size_t end1 = clock();
cout << "Find:" << end1 - begin1 << endl;
}
六、全部代码
AVLTree.h
#pragma once
template <class K, class V>
struct AVLTreeNode {
pair<K, V> _kv;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
int _bf;
AVLTreeNode(const pair<K, V>& kv) : _kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0) {}
};
template <class K, class V>
class AVLTree {
typedef AVLTreeNode<K, V> Node;
public:
bool Insert(const pair<K, V>& kv) {
if (_root == nullptr) {
_root = new Node(kv);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur) {
if (cur->_kv.first < kv.first) {
parent = cur;
cur = cur->_right;
} else if (cur->_kv.first > kv.first) {
parent = cur;
cur = cur->_left;
} else {
return false;
}
}
cur = new Node(kv);
if (parent->_kv.first < kv.first) {
parent->_right = cur;
} else {
parent->_left = cur;
}
cur->_parent = parent;
while (parent) {
if (cur == parent->_left) {
parent->_bf++;
} else {
parent->_bf--;
}
if (parent->_bf == 0) {
break;
} else if (parent->_bf == 1 || parent->_bf == -1) {
cur = parent;
parent = cur->_parent;
} else if (parent->_bf == 2 || parent->_bf == -2) {
if (parent->_bf == 2 && cur->_bf == 1) {
RotateR(parent);
} else if (parent->_bf == -2 && cur->_bf == -1) {
RotateL(parent);
} else if (parent->_bf == 2 && cur->_bf == -1) {
RotateLR(parent);
} else if (parent->_bf == -2 && cur->_bf == 1) {
RotateRL(parent);
} else assert(false);
break;
} else {
assert(false);
}
}
return true;
}
void InOrder() {
_InOrder(_root);
}
bool IsBalanceTree() {
return _IsBalanceTree(_root);
}
Node* Find(const K& key) {
Node* cur = _root;
while (cur) {
if (cur->_kv.first < key) cur = cur->_right;
else if (cur->_kv.first > key) cur = cur->_left;
else return cur;
}
return nullptr;
}
int Height() {
return _Height(_root);
}
int Size() {
return _Size(_root);
}
private:
int _Size(Node* root) {
if (root == nullptr) return 0;
return _Size(root->_left) + _Size(root->_right) + 1;
}
int _Height(Node* root) {
if (root == nullptr) return 0;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
bool _IsBalanceTree(Node* root) {
if (root == nullptr) return true;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
int diff = leftHeight - rightHeight;
if (abs(diff) >= 2) {
cout << root->_kv.first << "高度差异常" << endl;
return false;
}
if (root->_bf != diff) {
cout << root->_kv.first << "平衡因子异常" << endl;
return false;
}
return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}
void _InOrder(Node* root) {
if (root == nullptr) {
return;
}
_InOrder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << " ";
_InOrder(root->_right);
}
void RotateR(Node* parent) {
Node* subL = parent->_left;
Node* subLR = subL->_right;
Node* ppNode = parent->_parent;
parent->_left = subLR;
if (subLR) {
subLR->_parent = parent;
}
subL->_right = parent;
parent->_parent = subL;
if (parent == _root) {
subL->_parent = nullptr;
_root = subL;
} else {
subL->_parent = ppNode;
if (parent == ppNode->_right) ppNode->_right = subL;
else ppNode->_left = subL;
}
parent->_bf = 0;
subL->_bf = 0;
}
void RotateL(Node* parent) {
Node* subR = parent->_right;
Node* ppNode = parent->_parent;
Node* subRL = subR->_left;
parent->_right = subRL;
parent->_parent = subR;
if (subRL) {
subRL->_parent = parent;
}
subR->_left = parent;
if (parent == _root) {
_root = subR;
subR->_parent = nullptr;
} else {
subR->_parent = ppNode;
if (parent == ppNode->_right) ppNode->_right = subR;
else ppNode->_left = subR;
}
parent->_bf = 0;
subR->_bf = 0;
}
void RotateLR(Node* parent) {
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(subL);
RotateR(parent);
if (bf == 1) {
subLR->_bf = 0;
subL->_bf = 0;
parent->_bf = -1;
} else if (bf == -1) {
subLR->_bf = 0;
subL->_bf = 1;
parent->_bf = 0;
} else if (bf == 0) {
subL->_bf = subLR->_bf = parent->_bf = 0;
} else {
assert(false);
}
}
void RotateRL(Node* parent) {
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(subR);
RotateL(parent);
if (bf == 0) {
parent->_bf = subR->_bf = subRL->_bf = 0;
} else if (bf == 1) {
subRL->_bf = 0;
parent->_bf = 0;
subR->_bf = -1;
} else if (bf == -1) {
subRL->_bf = 0;
parent->_bf = 1;
subR->_bf = 0;
} else {
assert(false);
}
}
Node* _root = nullptr;
};
void TestAVLTree1() {
AVLTree<int, int> t;
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
for (auto e : a) {
t.Insert({ e, e });
cout << "Insert:" << e << "->";
cout << t.IsBalanceTree() << endl;
}
t.InOrder();
cout << t.IsBalanceTree() << endl;
}
void TestAVLTree2() {
const int N = 1000000;
vector<int> v;
v.reserve(N);
srand((unsigned int)time(0));
for (int i = 0; i < N; i) {
v.push_back(rand() + i);
}
size_t begin2 = clock();
AVLTree<int, int> t;
for (auto e : v) {
t.Insert(make_pair(e, e));
}
size_t end2 = clock();
cout << t.IsBalanceTree() << endl;
cout << "Insert:" << end2 - begin2 << endl;
cout << "Height:" << t.Height() << endl;
cout << "Size:" << t.Size() << endl;
size_t begin1 = clock();
for (int i = 0; i < N; i) {
t.Find((rand() + i));
}
size_t end1 = clock();
cout << "Find:" << end1 - begin1 << endl;
}
Test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <assert.h>
#include <vector>
using namespace std;
#include "AVLTree.h"
int main() {
TestAVLTree1();
TestAVLTree2();
return 0;
}
七、总结与展望
随着数据量的不断增长,如何保持高效的查找与插入操作成为了现代计算机科学的核心问题之一。AVL 树作为一种自平衡二叉查找树,凭借其稳定的时间复杂度和高效的性能,已广泛应用于各类需要动态数据结构支持的场景。未来,随着算法和硬件的进一步发展,AVL 树的实现和优化将迎来更多挑战与机遇。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online