AVL树C++实现
1. AVL 树本质上还是一棵二叉搜索树,它的特点是:
- 本身首先是一棵二叉搜索树。
- 带有平衡条件: 每个结点的左右子树的高度之差的绝对值(平衡因子) 最多为 1。
2. 数据结构定义
AVL树节点类:
template <typename T>
class AVLTreeNode {
public:
T key;
AVLTreeNode<T>* parent;
AVLTreeNode<T>* left;
AVLTreeNode<T>* right;
AVLTreeNode():key(T()), parent(NULL), left(NULL), right(NULL) {}
};
AVL树类:
template <typename T>
class AVLTree {
public:
AVLTree():root(NIL) {};
void inorder_tree_walk(AVLTreeNode<T>* proot); //中序遍历整棵树, 参数为AVL树的根节点
int insert_key(const T& k);
int delete_key(const T& k);
AVLTreeNode<T>* tree_search(const T& k) const; //根据关键字k搜索AVL树,返回对应的节点指针
AVLTreeNode<T>* get_root() const;
~AVLTree(); private:
AVLTreeNode<T>* root;
static AVLTreeNode<T>* NIL;
int getDepth(const AVLTreeNode<T>* pnode) const; //获取以pnode为根节点的树的深度
int insert_Key(const T& k, AVLTreeNode<T>* pnode);
int delete_key(const T& k, AVLTreeNode<T>* pnode);
AVLTreeNode<T>* tree_minimum(AVLTreeNode<T>* pnode); //获取最小值节点(最左边节点)
void make_empty(AVLTreeNode<T>* proot);
void avl_translate(AVLTreeNode<T>* node_u, AVLTreeNode<T>* node_v);
void singleRotateWithL(AVLTreeNode<T>* pnode); //左单旋
void singleRotateWithR(AVLTreeNode<T>* pnode); //右单旋
void avl_delete_fixup(AVLTreeNode<T>* pnode, int flag); //节点删除后调整AVL树, 使其满足AVL树的性质
inline void rotateWithLR(AVLTreeNode<T>* pnode); //先左后右双旋
inline void rotateWithRL(AVLTreeNode<T>* pnode); //先右后左双旋
};
3. 假设有一个结点的平衡因子为 2(在 AVL 树中, 最大就是 2, 因为结点是一个一个地插入到树中的, 一旦出现不平衡的状态就会立即进行调整, 因此平衡因子最大不可能超过 2),那么就需要进行调整。由于任意一个结点最多只有两个儿子,所以当高度不平衡时,只可能是以下四种情况造成的:
- 对该结点的左儿子的左子树进行了一次插入。
- 对该结点的左儿子的右子树进行了一次插入。
- 对该结点的右儿子的左子树进行了一次插入。
- 对该结点的右儿子的右子树进行了一次插入。
情况 1 和 4 是关于该点的镜像对称,同样,情况 2 和 3 也是一对镜像对称。因此,理论上只有两种情况,当然了,从编程的角度来看还是四种情况。第一种情况是插入发生在“外边” 的情况(即左-左的情况或右-右的情况),该情况可以通过对树的一次单旋转来完成调整。 第二种情况是插入发生在“内部”的情况(即左-右的情况或右-左的情况),该情况要通过稍微复杂些的双旋转来处理。
左单旋:
template <typename T>
void AVLTree<T>::singleRotateWithL(AVLTreeNode<T>* pnode) {
AVLTreeNode<T> *tmpnode_x = pnode->right;
pnode->right = tmpnode_x->left;
if(tmpnode_x->left != NIL)
tmpnode_x->left->parent = pnode;
tmpnode_x->parent = pnode->parent;
if(pnode->parent == NIL)
root = tmpnode_x;
else if(pnode == pnode->parent->left)
pnode->parent->left = tmpnode_x;
else
pnode->parent->right = tmpnode_x;
tmpnode_x->left = pnode;
pnode->parent = tmpnode_x;
}
右单旋:
template <typename T>
void AVLTree<T>::singleRotateWithR(AVLTreeNode<T>* pnode) {
AVLTreeNode<T> *tmpnode_x = pnode->left;
pnode->left = tmpnode_x->right;
if(tmpnode_x->right != NIL)
tmpnode_x->right->parent = pnode;
tmpnode_x->parent = pnode->parent;
if(pnode->parent == NIL)
root = tmpnode_x;
else if(pnode == pnode->parent->left)
pnode->parent->left = tmpnode_x;
else
pnode->parent->right = tmpnode_x;
tmpnode_x->right = pnode;
pnode->parent = tmpnode_x;
}
双旋可以直接复用单旋的代码:
template <typename T>
void AVLTree<T>::rotateWithLR(AVLTreeNode<T>* pnode) {
singleRotateWithL(pnode->left);
return singleRotateWithR(pnode);
} template <typename T>
void AVLTree<T>::rotateWithRL(AVLTreeNode<T>* pnode) {
singleRotateWithR(pnode->right);
return singleRotateWithL(pnode);
}
4. 插入的核心思路是通过递归, 找到合适的位置, 插入新结点, 然后看新结点是否平衡(平衡因子是否为 2),如果不平衡的话,就分成两种大情况以及两种小情况:
1) 在结点的左儿子(data < p->data)
- 在左儿子的左子树((data < p->data) AND (data < p->left->data)), “外边”,要做单旋转。
- 在左儿子的右子树((data < p->data) AND (data > p->left->data0)),“内部”,要做双旋转。
2) 在结点的右儿子(data > p->data)
- 在右儿子的左子树((data > p->data) AND (data < p->right->data)),“内部”,要做双旋转。
- 在右儿子的右子树((data > p->data) AND (data > p->right->data)),“外边”,要做单旋转。
template <typename T>
int AVLTree<T>::insert_Key(const T& k, AVLTreeNode<T>* pnode) {
if(root == NIL) {
AVLTreeNode<T>* newnode = new AVLTreeNode<T>;
newnode->key = k;
newnode->left = NIL;
newnode->right = NIL;
newnode->parent = NIL;
root = newnode;
}
else {
if(k < pnode->key) {
if(pnode->left == NIL) {
AVLTreeNode<T>* newnode = new AVLTreeNode<T>;
newnode->key = k;
newnode->left = NIL;
newnode->right = NIL;
newnode->parent = pnode;
pnode->left = newnode;
}
else
insert_Key(k, pnode->left);
if( == (getDepth(pnode->left) - getDepth(pnode->right))) {
if(k < pnode->left->key)
singleRotateWithR(pnode);
else
rotateWithLR(pnode);
}
}
else {
if(pnode->right == NIL) {
AVLTreeNode<T>* newnode = new AVLTreeNode<T>;
newnode->key = k;
newnode->left = NIL;
newnode->right = NIL;
newnode->parent = pnode;
pnode->right = newnode;
}
else
insert_Key(k, pnode->right);
if( == (getDepth(pnode->right) - getDepth(pnode->left))) {
if(k > pnode->right->key)
singleRotateWithL(pnode);
else
rotateWithRL(pnode);
}
}
}
return ;
}
5. AVL删除
template <typename T>
int AVLTree<T>::delete_key(const T& k) {
int flag;
AVLTreeNode<T>* delnode = tree_search(k);
AVLTreeNode<T>* tmpnode_x = delnode->parent;
if(delnode == tmpnode_x->left)
flag = LC;
else
flag = RC;
if(delnode->left == NIL)
avl_translate(delnode, delnode->right);
else if(delnode->right == NIL)
avl_translate(delnode, delnode->left);
else {
AVLTreeNode<T>* tmpnode_y = tree_minimum(delnode->right);
tmpnode_x = tmpnode_y->parent;
if(tmpnode_y == tmpnode_x->left)
flag = LC;
else
flag = RC;
if(tmpnode_y->parent != delnode) {
avl_translate(tmpnode_y, tmpnode_y->right);
tmpnode_y->right = delnode->right;
tmpnode_y->right->parent = tmpnode_y;
}
avl_translate(delnode, tmpnode_y);
tmpnode_y->left = delnode->left;
tmpnode_y->left->parent = tmpnode_y;
}
avl_delete_fixup(tmpnode_x, flag);
return ;
}
template <typename T>
void AVLTree<T>::avl_delete_fixup(AVLTreeNode<T>* pnode, int flag) {
int tmpflag = flag;
AVLTreeNode<T>* tmpnode_x = pnode;
while(tmpnode_x != NIL) {
if(tmpflag == LC) {
if( == (getDepth(tmpnode_x->right) - getDepth(tmpnode_x->left))) {
if(LH == (getDepth(tmpnode_x->right->left) - getDepth(tmpnode_x->right->right)))
rotateWithRL(tmpnode_x);
else
singleRotateWithL(tmpnode_x);
break;
}
}
else if(tmpflag == RC) {
if( == (getDepth(tmpnode_x->left) - getDepth(tmpnode_x->right))) {
if(RH == (getDepth(tmpnode_x->left->left) - getDepth(tmpnode_x->left->right)))
rotateWithLR(tmpnode_x);
else
singleRotateWithR(tmpnode_x);
break;
}
}
if(tmpnode_x == tmpnode_x->parent->left)
tmpflag = LC;
else if(tmpnode_x == tmpnode_x->parent->right)
tmpflag = RC;
tmpnode_x = tmpnode_x->parent;
}
}
简单测试:


==========================我是华丽的分割线=================================
源码猛戳{这里}!
=====================================================================
参考资料:
http://www.luocong.com/dsaanotes/index-Z-H-11.htm#node_sec_10.1
AVL树C++实现的更多相关文章
- 算法与数据结构(十一) 平衡二叉树(AVL树)
今天的博客是在上一篇博客的基础上进行的延伸.上一篇博客我们主要聊了二叉排序树,详情请戳<二叉排序树的查找.插入与删除>.本篇博客我们就在二叉排序树的基础上来聊聊平衡二叉树,也叫AVL树,A ...
- AVL树原理及实现(C语言实现以及Java语言实现)
欢迎探讨,如有错误敬请指正 如需转载,请注明出处http://www.cnblogs.com/nullzx/ 1. AVL定义 AVL树是一种改进版的搜索二叉树.对于一般的搜索二叉树而言,如果数据恰好 ...
- AVL树
AVL树 在二叉查找树(BST)中,频繁的插入操作可能会让树的性能发生退化,因此,需要加入一些平衡操作,使树的高度达到理想的O(logn),这就是AVL树出现的背景.注意,AVL树的起名来源于两个发明 ...
- AVL树的平衡算法(JAVA实现)
1.概念: AVL树本质上还是一个二叉搜索树,不过比二叉搜索树多了一个平衡条件:每个节点的左右子树的高度差不大于1. 二叉树的应用是为了弥补链表的查询效率问题,但是极端情况下,二叉搜索树会无限接近 ...
- 【数据结构】平衡二叉树—AVL树
(百度百科)在计算机科学中,AVL树是最先发明的自平衡二叉查找树.在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树.查找.插入和删除在平均和最坏情况下都是O(log n).增 ...
- 数据结构图文解析之:AVL树详解及C++模板实现
0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...
- 数据结构之平衡二叉树(AVL树)
平衡二叉树(AVL树)定义如下:平衡二叉树或者是一棵空树,或者是具有以下性质的二叉排序树: (1)它的左子树和右子树的高度之差绝对值不超过1: (2)它的左子树和右子树都是平衡二叉树. AVL树避免了 ...
- PAT树_层序遍历叶节点、中序建树后序输出、AVL树的根、二叉树路径存在性判定、奇妙的完全二叉搜索树、最小堆路径、文件路由
03-树1. List Leaves (25) Given a tree, you are supposed to list all the leaves in the order of top do ...
- 论AVL树与红黑树
首先讲解一下AVL树: 例如,我们要输入这样一串数字,10,9,8,7,15,20这样一串数字来建立AVL树 1,首先输入10,得到一个根结点10 2,然后输入9, 得到10这个根结点一个左孩子结点9 ...
- (4) 二叉平衡树, AVL树
1.为什么要有平衡二叉树? 上一节我们讲了一般的二叉查找树, 其期望深度为O(log2n), 其各操作的时间复杂度O(log2n)同时也是由此决定的.但是在某些情况下(如在插入的序列是有序的时候), ...
随机推荐
- Firefox内存占用过高解决方法
刚开始使用firefox火狐浏览器的时候,你会发现firefox占用内存大,CPU占用率高,打开网页停顿等问题,其实这些是因为firefox没有进行优化,默认设置是标准的设置的原因,解决方法如下: 一 ...
- Spring 3.2 事件驱动模型
事件 @SuppressWarnings("serial") public class CheckEvent extends ApplicationEvent { public C ...
- as3 三行三列 布满9个为一个界面
var n:int=int(iconIndex/3); e.x =(int(n/3)*3+iconIndex%3)*557; e.y = int(iconIndex / 3) % 3 * 260; i ...
- 【OpenPose-Windows】OpenPose+VS2015+Windows+CUDA8+cuDNN5.1 官方配置教程(转载)
[我的电脑配置] 操作系统:Windows 10 CUDA版本:cuda_8.0.61_win10 cuDNN版本:cudnn-8.0-windows10-x64-v5.1 GPU model:Nvi ...
- 类定义,创建/销毁OC对象
类定义 1 OC类分为2个文件,一个是.h文件,一个是.m文件: 2 .h文件存放类.函数声明: 3 .m文件类的具体实现: 4 类声明使用关键字@interface.@end来声明: 5 类实现使用 ...
- 2018 Jar_Feb_Newwords
检测钩子程序 开发一个检测钩子程序的工具 - 豆丁网http://www.docin.com/p-1363993661.html pdf掺杂病毒的方法 Java的:xml文件中跳过的二进制数据在解析 ...
- 使用CXF发布的WebService报错:org.apache.cxf.interceptor.Fault: The given SOAPAction does not match an operation
场景:JAVA语言使用CXF搭建WebService发布报错 错误信息:org.apache.cxf.interceptor.Fault: The given SOAPAction does not ...
- hibernate中1对1的注解配置
hibernate中1对1的注解配置分为:外键关联映射和主键关联映射 1.外键配置 //一方@Entity@Table(name="test_classinfo")public c ...
- str和repr的区别(转)
Python打印值的时候会保持该值在python代码中的状态,不是用户所希望看到的状态.而使用print打印值则不一样,print打印出来的值是用户所希望看到的状态. 例如: >>> ...
- 【Java】JVM(一)、Java内存区域
一.程序计数器(Program Counter Register) 当前执行字节码的行号指示器,可以通过修改该计数器的值来实现字节码指令(分支,循环,跳转等), 每个线程都都有一个程序计数器, 属于线 ...