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++实现的更多相关文章

  1. 算法与数据结构(十一) 平衡二叉树(AVL树)

    今天的博客是在上一篇博客的基础上进行的延伸.上一篇博客我们主要聊了二叉排序树,详情请戳<二叉排序树的查找.插入与删除>.本篇博客我们就在二叉排序树的基础上来聊聊平衡二叉树,也叫AVL树,A ...

  2. AVL树原理及实现(C语言实现以及Java语言实现)

    欢迎探讨,如有错误敬请指正 如需转载,请注明出处http://www.cnblogs.com/nullzx/ 1. AVL定义 AVL树是一种改进版的搜索二叉树.对于一般的搜索二叉树而言,如果数据恰好 ...

  3. AVL树

    AVL树 在二叉查找树(BST)中,频繁的插入操作可能会让树的性能发生退化,因此,需要加入一些平衡操作,使树的高度达到理想的O(logn),这就是AVL树出现的背景.注意,AVL树的起名来源于两个发明 ...

  4. AVL树的平衡算法(JAVA实现)

      1.概念: AVL树本质上还是一个二叉搜索树,不过比二叉搜索树多了一个平衡条件:每个节点的左右子树的高度差不大于1. 二叉树的应用是为了弥补链表的查询效率问题,但是极端情况下,二叉搜索树会无限接近 ...

  5. 【数据结构】平衡二叉树—AVL树

    (百度百科)在计算机科学中,AVL树是最先发明的自平衡二叉查找树.在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树.查找.插入和删除在平均和最坏情况下都是O(log n).增 ...

  6. 数据结构图文解析之:AVL树详解及C++模板实现

    0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...

  7. 数据结构之平衡二叉树(AVL树)

    平衡二叉树(AVL树)定义如下:平衡二叉树或者是一棵空树,或者是具有以下性质的二叉排序树: (1)它的左子树和右子树的高度之差绝对值不超过1: (2)它的左子树和右子树都是平衡二叉树. AVL树避免了 ...

  8. PAT树_层序遍历叶节点、中序建树后序输出、AVL树的根、二叉树路径存在性判定、奇妙的完全二叉搜索树、最小堆路径、文件路由

    03-树1. List Leaves (25) Given a tree, you are supposed to list all the leaves in the order of top do ...

  9. 论AVL树与红黑树

    首先讲解一下AVL树: 例如,我们要输入这样一串数字,10,9,8,7,15,20这样一串数字来建立AVL树 1,首先输入10,得到一个根结点10 2,然后输入9, 得到10这个根结点一个左孩子结点9 ...

  10. (4) 二叉平衡树, AVL树

    1.为什么要有平衡二叉树? 上一节我们讲了一般的二叉查找树, 其期望深度为O(log2n), 其各操作的时间复杂度O(log2n)同时也是由此决定的.但是在某些情况下(如在插入的序列是有序的时候), ...

随机推荐

  1. Git .gitignore使用 -- 过滤class文件或指定目录

    1. 进入当前的项目根目录 执行 git init touch .gitignore 2. 过滤class文件或指定目录 *.class /target/ 3. 提交 git add . 将所有文件提 ...

  2. 文字折行不折行 css

    white-space : 1. normal  默认值 ,文字自动换行.               2. pre 使用<pre>标签形式,表示元素.                 * ...

  3. 前端-CSS-5-继承性&层叠性&权重比较

    1.继承性 <style type="text/css"> .father{ font-size: 30px; background-color: green; } . ...

  4. 第一模块第一章 review

    ---恢复内容开始--- 练习题: 1.简述编译型与解释型语言的区别,且分别列出你知道的那些属于编译型,哪些属于解释型 机器语言:由于计算机内部只能接受二进制代码,因此,用二进制代码0和1描述的指令称 ...

  5. UI5-文档-2.4-Node.js-Based开发环境

    用于修改OpenUI5.环境是基于Node.js,用作服务器,具有一个基于Grunt的构建过程.本节提供关于初始设置.开发工作流和测试执行的信息. 常规开发过程: 不需要构建过程,您可以简单地修改任何 ...

  6. Python之建立APP流程以及SVN 的使用

    一, 1)我们先拿到SVN的地址比如说:https://123.com/trunck/nihao 2)执行命令行 svn checkout 此时check out是将项目存到了本地根目录下面,如果想存 ...

  7. Objective C, erum 枚举类型

    typedef NS_ENUM(NSInteger, MYENUM) { TYPE1, TYPE2, TYPE3 };

  8. Java Magic. Part 3: Finally

    Java Magic. Part 3: Finally @(Base)[JDK, magic, 黑魔法] 转载请写明:原文地址 英文原文 系列文章: -Java Magic. Part 1: java ...

  9. PowerDesigner表生成 EXCEL

    今天收到一个需求,要把数据库设计给一个excel版本的,百度出来一个脚本文件,很好用发现,留个纪念 在pd中,shift+ctrl+X,打开脚本运行,脚本如下,附件也留了一份: '********** ...

  10. 通过Chrome的inspect对手机webview进行调试

    使用chrome的inspect可以对手机上的webview进行调试,因为真机没有什么比较好的调试工具,而chrome提供了这一个工具可以比较方便的查看真机上的元素,以及进行调试. 其实我对webvi ...