C++实现平衡二叉树
1.概念
平衡二叉树(AVL Tree)首先要满足二叉树的定义,如下
- 二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
- 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 左、右子树也分别为二叉排序树;
- 没有键值相等的节点。
- 平衡度是左子树高度减去右子树高度,平衡度只能是-1,+1,0


- 危机结点是指平衡度为1,有可能破坏平衡树的结点
- 左改组是指新结点插入到危机结点的左树下
- 右改组是指新新结点插入到危机结点的右子树下
- LL是新结点插入在危机节点的左子树的左子树上
- LR是新结点插入在危机节点的左子树的右子树上
- RR是新结点插入在危机节点的右子树的右子树上
- RL是新结点插入在危机节点的右子树的左子树上
2.代码实现
当前树的结构
11
/ \
7 15
/ \ / \
3 9 14 18
/ \ / / \
1 5 12 16 20
/
26
2.1 定义平衡树结点:
平衡因子可以是右子树高度减去左子树高度,不同的教材定义不一样,我这里按照左树-右树来做
template<class K, class V>
struct AVLTreeNode
{
K _key; //树权值
V _value;
int _bf; //平衡因子 -1,0,1 (每个节点的平衡因子等于左子树的高度减去右子树的高度)
//有的教材定义平衡度是左子树高度减去右子树,都是可以的
AVLTreeNode<K, V>* _parent; //指向父节点的指针
AVLTreeNode<K, V>* _left; //指向左孩子的指针
AVLTreeNode<K, V>* _right; //指向右孩子的指针 AVLTreeNode(const K& key = K(), const V& value = V())
:_key(key)
, _value(value)
, _bf()
, _parent(NULL)
, _left(NULL)
, _right(NULL)
{}
};
2.2 左改组图解
左右改组是为了方便我们插入删除的时候保持二叉树平衡而引入的概念
左改组LL型和LR(a),LR(b),LR(c)型图解

2.3 左改组LL型
首先声明一个构造的左子树,subL其实就是危机结点,subLR是危机节点的右子树,ppNode是祖先节点
构建parent子树,将parent和subL连接起来
如果祖先结点为空,将当前结点subL置为根节点,请参见上图(a‘)的情况,B是危机结点,调整过后变成了根节点
否则祖父结点赋给subL的父结点,判断父节点是否是祖先结点的左子树,是的话,用构造的左子树替代之
不是就用subL替代祖先节点的右子树
//左改组LL型
template<class K, class V>
void AVLTree<K, V>::_RotateLL(AVLTreeNode<K, V>*& parent)
{
AVLTreeNode<K, V>* subL = parent->_left; //构造的左子树
AVLTreeNode<K, V>* subLR = subL->_right;//subL的右子树
AVLTreeNode<K, V>* ppNode = parent->_parent;//标记祖先节点
//1.构建parent子树 将parent和subLR链接起来
parent->_left = subLR;
if (subLR) subLR->_parent = parent;
//2.构建subL子树 将subL与parent链接起来
subL->_right = parent;
parent->_parent = subL;
//3.将祖先节点与subL链接起来
if (ppNode == NULL)
{ //如果祖先为NULL,说明当前subL节点为根节点
subL->_parent = NULL;
_root = subL;
}
else
{
subL->_parent = ppNode;
if (ppNode->_left == parent)
ppNode->_left = subL;
else if (ppNode->_right == parent)
ppNode->_right = subL;
}
//4.重置平衡因子
parent->_bf = ;
subL->_bf = ;
//5.更新subL为当前父节点
parent = subL;
}
2.4 左改组LR(a)、LR(b)和LR(c)型
pNode是当前父节点,subR是构造的右子树,subLR是subR的左子树
对当前父节点LL左改组,再右改组
根据平衡因子判断是LR什么类型,请参见上图图解(b),(c),(d)的情况
//左改组LR型
template<class K, class V>
void AVLTree<K, V>::_RotateLR(AVLTreeNode<K, V>*& parent)
{
AVLTreeNode<K, V>* pNode = parent;
AVLTreeNode<K, V>* subR = parent->_right;
AVLTreeNode<K, V>* subLR = subR->_left;
int bf = subLR->_bf; _RotateLL(parent->_right);
_RotateRR(parent);
//LR(b)型
if (bf == -)
{
pNode->_bf = ;
subR->_bf = ;
}
//LR(a)型
else if (bf == )
{
pNode->_bf = -;
subR->_bf = ;
}
//LR(c)型
else
{
pNode->_bf = ;
subR->_bf = ;
}
}
右改组和左改组镜像对称,反过来就行了
2.5 插入函数
AVL树是空,将当前结点直接置为根节点
AVL树在满足平衡度的要求下,和二叉排序树一致,key小于当前结点,转到当前结点左子树,key大于当前结点,转到当前结点右子树
将parent的左子树赋予当前结点,更新平衡因子,_bf++
将parent的右子树赋予当前结点,更新平衡因子,_bf--
如果合法,即平衡因子=0,终止当前循环
如果当前结点是危机结点,即平衡度绝对值等于1,当前结点往上回溯,变成父节点,继续检查它的平衡度
接下来是平衡异常的情况,父结点平衡度为2,当前结点(危机结点)平衡度为1,进入左改组LL,LL介绍参考2.2 左改组LL
当前结点平衡度为-1,进入左改组LR,LR介绍参考2.3 左改组LR
右改组的情况类似
template<class K, class V>
bool AVLTree<K, V>::Insert(const K& key, const V& value)
{
//1.空树
if (_root == NULL)
{
_root = new AVLTreeNode<K, V>(key, value);
return true;
} //2.AVL树不为NULL
AVLTreeNode<K, V>* parent = NULL;
AVLTreeNode<K, V>* cur = _root;
//找到数据插入位置
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
//插入数据
cur = new AVLTreeNode<K, V>(key, value);
cur->_parent = parent;
if (parent->_key > key)
parent->_left = cur;
else
parent->_right = cur; while (parent)
{
//更新平衡因子
if (cur == parent->_left)
parent->_bf++;
else if (cur == parent->_right)
parent->_bf--; //检验平衡因子是否合法
if (parent->_bf == )
break;
else if (parent->_bf == - || parent->_bf == )
{ // 回溯上升 更新祖父节点的平衡因子并检验合法性
cur = parent;
parent = cur->_parent;
}
// 2 -2 平衡因子不合法 需要进行旋转 降低高度
else
{
if (parent->_bf == -)
{
if (cur->_bf == -)
_RotateRR(parent);
else
_RotateLR(parent);
}
else if (parent->_bf == )
{
if (cur->_bf == )
_RotateLL(parent);
else
_RotateRL(parent);
}
break;
}
}
}
2.6 遍历方法
//中序遍历
template<class K, class V>
void AVLTree<K, V>::_InOrder(AVLTreeNode<K, V>* root)
{
if (root == NULL)
return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
//前序遍历
template<class K, class V>
void AVLTree<K, V>::_PreOrder(AVLTreeNode<K, V>* root)
{
if (root == NULL)
return;
cout << root->_key << " ";
_PreOrder(root->_left);
_PreOrder(root->_right);
}
//后序遍历
template<class K, class V>
void AVLTree<K, V>::_PostOrder(AVLTreeNode<K, V>* root)
{
if (root == NULL)
return;
_PostOrder(root->_left);
_PostOrder(root->_right);
cout << root->_key << " ";
}
3.运行和源码
运行效果如下

C++实现平衡二叉树的更多相关文章
- 算法与数据结构(十一) 平衡二叉树(AVL树)
今天的博客是在上一篇博客的基础上进行的延伸.上一篇博客我们主要聊了二叉排序树,详情请戳<二叉排序树的查找.插入与删除>.本篇博客我们就在二叉排序树的基础上来聊聊平衡二叉树,也叫AVL树,A ...
- [LeetCode] Balanced Binary Tree 平衡二叉树
Given a binary tree, determine if it is height-balanced. For this problem, a height-balanced binary ...
- Java数据结构——平衡二叉树的平衡因子(转自牛客网)
若向平衡二叉树中插入一个新结点后破坏了平衡二叉树的平衡性.首先要找出插入新结点后失去平衡的最小子树根结点的指针.然后再调整这个子树中有关结点之间的链接关系,使之成为新的平衡子树.当失去平衡的最小子树被 ...
- 【数据结构】平衡二叉树—AVL树
(百度百科)在计算机科学中,AVL树是最先发明的自平衡二叉查找树.在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树.查找.插入和删除在平均和最坏情况下都是O(log n).增 ...
- 平衡二叉树AVL删除
平衡二叉树的插入过程:http://www.cnblogs.com/hujunzheng/p/4665451.html 对于二叉平衡树的删除采用的是二叉排序树删除的思路: 假设被删结点是*p,其双亲是 ...
- 平衡二叉树AVL插入
平衡二叉树(Balancedbinary tree)是由阿德尔森-维尔斯和兰迪斯(Adelson-Velskiiand Landis)于1962年首先提出的,所以又称为AVL树. 定义:平衡二叉树或为 ...
- 数据结构快速回顾——平衡二叉树 AVL (转)
平衡二叉树(Balanced Binary Tree)是二叉查找树的一个进化体,也是第一个引入平衡概念的二叉树.1962年,G.M. Adelson-Velsky 和 E.M. Landis发明了这棵 ...
- LeetCode——Balanced Binary Tree(判断是否平衡二叉树)
问题: Given a binary tree, determine if it is height-balanced. For this problem, a height-balanced bin ...
- 数据结构之平衡二叉树(AVL树)
平衡二叉树(AVL树)定义如下:平衡二叉树或者是一棵空树,或者是具有以下性质的二叉排序树: (1)它的左子树和右子树的高度之差绝对值不超过1: (2)它的左子树和右子树都是平衡二叉树. AVL树避免了 ...
- 平衡二叉树AVL
1.定义 平衡二叉树(Balanced Binary Tree)是二叉查找树的一个改进,也是第一个引入平衡概念的二叉树.1962年,G.M. Adelson-Velsky 和 E.M. Landis发 ...
随机推荐
- java 获得字符串中最大重复子串长度
参考:http://blog.csdn.net/csdn_yaobo/article/details/50338025 要找一串字符串中,重复的字串长度,.例如ABCX1&ABC,中ABC重复 ...
- opencv的基本数据结构(二)(转)
转自:原文链接,以下代码.图片.内容有点改动,只为转载不降低博客内容的可阅性,版权归原作者所有. OpenCV中强大的Mat类型大家已经比较熟悉了.这里梳理一些在工程中其他经常用到的几种基本数据类型. ...
- HDU 1181 变形课 (深搜)
题目连接 Problem Description 呃......变形课上Harry碰到了一点小麻烦,因为他并不像Hermione那样能够记住所有的咒语而随意的将一个棒球变成刺猬什么的,但是他发现了变形 ...
- JS模块规范
ES6标准发布后,module成为标准,标准的使用是以export指令导出接口,以import引入模块,但是在我们一贯的node模块中,我们采用的是CommonJS规范,使用require引入模块,使 ...
- 【微服务架构】SpringCloud之Ribbon
一:Ribbon是什么? Ribbon是Netfix发布的开源项目,主要负责客户端的软件负载均衡算法,将Netfix的中间层连接在一起,Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等. ...
- MVVM模式View和ViewModel的通信
还需要些什么呢 在前面几篇博客中我们尝试去实现了MVVM中的数据绑定.命令绑定和事件绑定.貌似实现的差不多了.我最早尝试用MVVM去开发的时候也是这么想的,没有用第三方框架,甚至只是实现了数据绑定和命 ...
- python基础-实现进度条功能,for和yield实现
实现进度条功能 方法一:简单FOR实现打印进度条功能 for i in range(10): print("#",end="",flush=True) time ...
- nginx php mysql日志配置
1.编辑mysql的配置文件my.cnf,这个文件通常在/etc目录下,但我用rpm装mysql的时候这个配置文件是在/usr目录下,但我测试过,无论是放在/etc目录下,还是放在 /usr目 ...
- git —— 异常1,index.lock
git提交过程中出现的问题 解决方法:找到 index.lock文件将其删除 一般 index.lock 在.git下面, 有时 .git 是隐藏的,但是无论怎样, 可以通过 everything 找 ...
- python基础学习之路No.5 数学函数以及操作
python的基本数学函数 函数 返回值 ( 描述 ) abs(x) 返回数字的绝对值,如abs(-10) 返回 10 ceil(x) 返回数字的上入整数,如math.ceil(4.1) 返回 5 c ...