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发 ...
随机推荐
- 【leetcode 简单】 第八十六题 有效的完全平方数
给定一个正整数 num,编写一个函数,如果 num 是一个完全平方数,则返回 True,否则返回 False. 注意:不要使用任何内置的库函数,如 sqrt. 示例 1: 输入: 16 输出: Tr ...
- 洛谷 P4093: bzoj 4553: [HEOI2016/TJOI2016]序列
题目传送门:洛谷P4093. 题意简述: 给定一个长度为 \(n\) 的序列 \(a\). 同时这个序列还可能发生变化,每一种变化 \((x_i,y_i)\) 对应着 \(a_{x_i}\) 可能变成 ...
- C#匿名函数与Lambda表达式
Lambda 表达式是一种可用于创建委托或表达式目录树类型的匿名函数. 通过使用 lambda 表达式,可以写入可作为参数传递或作为函数调用值返回的本地函数.在C#中的Linq中的大部分就是由扩展方法 ...
- LinuxUSB驱动程序调试--009:编写应用程序---验证协议【转】
转自:http://biancheng.dnbcw.info/linux/257411.html [1] 如何编译X86下的 uBuntu APP---非常简单: gcc -o ...
- 25 个常用的 Linux iptables 规则【转】
转自 25 个常用的 Linux iptables 规则 - 文章 - 伯乐在线http://blog.jobbole.com/108468/ # 1. 删除所有现有规则 iptables -F # ...
- 移动端Touch事件
案例1: <!doctype html> <html lang="en"> <head> <meta charset="UTF- ...
- 漂亮的SVG时钟
漂亮的SVG时钟 效果图: 代码如下,复制即可使用: <!DOCTYPE html> <html lang="en"> <head> <m ...
- Linux基础 - crontab
列出当前用户设置的定时任务 crontab -l 编辑定时任务 crontab -e 用法 m h dom mon dow * * * * * command 字段详解: *:any m: minut ...
- TypeScript的配置文件 tsconfig.json
//tsconfig.json指定了用来编译这个项目的根文件和编译选项 { "compilerOptions": { //compilerOptions:编译选项,可以被忽略,这时 ...
- SQL中的left outer join,inner join,right outer join用法详解
这两天,在研究SQL语法中的inner join多表查询语法的用法,通过学习,发现一个SQL命令,竟然涉及到很多线性代数方面的知识,现将这些知识系统地记录如下: 使用关系代数合并数据1 关系代数合并数 ...