引入

上一篇写了二叉排序树,构建一个二叉排序树,如果构建序列是完全有序的,则会出现这样的情况:

显然这种情况会使得二叉搜索树退化成链表。当出现这样的情况,二叉排序树的查找也就退化成了线性查找,所以我们需要合理调整二叉排序树的形态,使得树上的每个结点都尽量有两个子结点,这样整个二叉树的高度就会大约在\(log(n)\) 左右,其中 \(n\) 为结点个数。

基本性质

AVL树也称为平衡二叉树,是一种自平衡的二叉排序树,本质上仍然是一颗二叉排序树,只是增加了“平衡”的要求,平衡是指,对AVL树中任何节点的两个子树的高度之差(称为平衡因子)的绝对值不超过 \(1\) 。能保证上面这一点,AVL树的高度就能始终保持在 \(O(logn)\) 级别。

数据结构定义

由于需要对每个结点都得到平衡因子,因此在AVL树的结构中加入一个变量height,用来记录当前结点为根结点的子树的高度:

typedef struct Node
{
char data;
int height;
struct Node* Left;
struct Node* Right;
}*AVLTree;

获取 root 结点高度:

int getHeight(Node *root){
if(!root) return 0;//空节点高度为0
return root->height;
}

基本操作

查找

​ AVL树是一颗二叉查找树,因此查找操作与二叉查找树相同。因为AVL树的高度为 \(O(logn)\) 级别,所以查找操作的时间复杂度为 \(O(logn)\)。

可以得到和二叉查找树完全相同的代码:

//找不到返回NULL,找到返回该节点。
//非递归
Node* Find(AVLTree t, int x) {
if (!t)return NULL;
if (t->data == x) return t;
if (x < t->data) return BSTreeFind(t->Left, x);
if (x > t->data) return BSTreeFind(t->Right, x);
}
//非递归
Node* Find(AVLTree T,int x) {
BSTree p = T;
while (p) {
if (x == p->data)
return p;
p = x > p->data ? p->Right : p->Left;
}
return NULL;
}

插入

左旋

先抛开AVL树的插入问题,看下面左边的二叉排序树。大家本来和平共处,突然有一天 B 觉得自己的权值比 A 大,要造反,但是B要做根结点,必须也要保证调整后的树仍然是一颗二叉排序树。

☆上所有权值都比A小, ∆ 上所有权值都比B大,无需在调整中进行位置变动;因为调整后B的左孩子变成了A,那么▲必须移动到其他地方去,因为A、B、▲的权值关系满足 A<▲<B ,所以让▲成为A的右子树即可。

这个调整过程称为左旋,分解调整过程如下:

代码如下:

void L(AVLTree *root){
Node* temp = (*root)->Right; //root指向结点A,temp指向结点B
(*root)->Right = temp->Left; //图示步骤2
temp->Left = *root; //图示步骤3
root->height = max(getHeight(root->Left), getHeight(root->Rihgt)) + 1;//更新结点A高度
temp = max(getHeight(temp->Left), getHeight(temp->Rihgt)) + 1;//更新结点B高度
*root = temp;//图示步骤4
}

右旋

右旋是左旋的逆过程,如下:

分解调整过程如下:

代码如下:

void R(AVLTree *root) {
Node* temp = (*root)->Left;//root指向结点B,temp指向结点A
(*root)->Left = temp->Right;
temp->Right = *root;
root->height = max(getHeight(root->Left), getHeight(root->Rihgt)) + 1;
temp = max(getHeight(temp->Left), getHeight(temp->Rihgt)) + 1;
*root = temp;
}

​ 接下来讨论AVL树的插入操作,假设现在已经有一颗平衡二叉树,那么在向其中插入一个结点时,一定会有结点的平衡因子发生改变,此时就可能会有结点的平衡因子大于1 ,这样以该结点为根结点的子树就是失去平衡的,会使平衡二叉树发生失衡的情况可以分为下面四种:

LL、RR型

左左(LL)、右右(RR),LL,RR只表示树型(导致树失去平衡的插入位置),不是左旋、右旋的意思。

对于LL型,需要以A结点为根进行右旋;

对于RR型,需要以A为根结点进行左旋。

所以代码如下:

void RR_Rotate(AVLTree *root){
L(root);
} void LL_Rotate(AVLTree *root) {
R(root);
}

LR,RL型

左右(LR)、右左(RL)。

对于LR型,需要先以B结点为根结点进行一次左旋,再以A结点为根结点进行一次右旋。

对于RL型,需要先以B结点为根结点进行一次右旋,再以A结点为根结点进行一次左旋。

void LR_Rotate(AVLTree *root) {
L(&(*root)->Left);
R(root);
}
void RL_Rotate(AVLTree *root) {
R(&(*root)->Right);
L(root);
}

插入结点

插入算法就是出现不平衡状态时,判断需要使用哪种旋转方式来使得二叉树保持平衡

AVLTree InsertAVLTree(AVLTree root, int x) {
if (root == NULL) {
root = new Node;
root->Left = NULL;
root->Right = NULL;
root->data = x;
return root;
}
if (x > root->data) {
//递归返回插入位置的父节点或者祖父……。
root->Right = InsertAVLTree(root->Right, x);
//如果插入之后失去了平衡
if (height(root->Left) - height(root->Right) == -2) {
//如果插入的值大于,当前节点的左孩子节点,说明该节点是插在root的右子树上的
if (x > root->Left->data) RR_Rotate(&root);
else RL_Rotate(&root);
}
} else if (x < root->data) {
root->Left = InsertAVLTree(root->Left, x);
if (height(root->Left) - height(root->Right) == 2) {
if (x < root->Left->data) LL_Rotate(&root);
else LR_Rotate(&root);
}
}
else {
cout << "the number is already included." << endl;
return NULL;
}
return root;
}

删除结点

和二叉排序树的节点的删除差不多,就是多出来一个判断从哪个子树删除节点的问题。

void AVLTreeDel(AVLTree *root, int data)
{
if (!*root) {
cout << "delete failed" << endl;
return;
}
Node *p = *root;
if (data == p->data) {
//左右子树都非空
if (p->Left && p->Right) {
//在高度更大的那个子树上进行删除操作
//进左子树,右转到底,进右子树,左转到底,转弯碰壁,杀孩子。
if (height(p->Left) > height(p->Right)) {
Node *pre=NULL,*q = p->Left;
if (!q->Right)
q->Right = p->Right;
else {
while (q->Right) {
pre = q;
q = q->Right;
}
pre->Right = q->Left;
q->Left = p->Left;
q->Right = p->Right;
}
*root = q;
}
else {
Node *pre = NULL, *q = p->Right;
if (!q->Left)
q->Left = p->Left;
else {
while (q->Left) {
pre = q;
q = q->Left;
}
pre->Left = q->Right;
q->Left = p->Left;
q->Right = p->Right;
}
*root=q;
}
}
else
(*root) = (*root)->Left ? (*root)->Left : (*root)->Right;
delete p;
}
else if (data < p->data){//要删除的节点在左子树中
//在左子树中进行递归删除
AVLTreeDel(&(*root)->Left, data);
//判断是否仍然满足平衡条件
if (height(p->Right) - height(p->Left) == 2){
//如果当前节点右孩子的左子树更高
if (height(p->Right->Left) > height(p->Right->Right))
RL_Rotate(root);
else
RR_Rotate(root);
}
}
else{
AVLTreeDel(&(*root)->Right, data);
if (height(p->Left) - height(p->Right) == 2) {
if (height((*root)->Left->Left) > height((*root)->Left->Right))
LL_Rotate(root);
else
LR_Rotate(root);
}
}
}

完整测试代码:

#pragma once
#include "top.h" typedef BTreeNode Node, *AVLTree; void RR_Rotate(AVLTree *root){
Node* Right = (*root)->Right;
(*root)->Right = Right->Left;
Right->Left = *root;
*root = Right;
} void LL_Rotate(AVLTree *root) {
Node* Left = (*root)->Left;
(*root)->Left = Left->Right;
Left->Right = *root;
*root = Left;
}
void LR_Rotate(AVLTree *root) {
RR_Rotate(&(*root)->Left);
return LL_Rotate(root);
}
void RL_Rotate(AVLTree *root) {
LL_Rotate(&(*root)->Right);
RR_Rotate(root);
}
AVLTree AVLTreeInsert(AVLTree root, int x) {
if (root == NULL) {
root = new Node;
root->Left = NULL;
root->Right = NULL;
root->data = x;
return root;
}
if (x > root->data) {
root->Right = AVLTreeInsert(root->Right, x);
//递归返回插入位置的父节点或者祖父……,如果失去了平衡
if (height(root->Left) - height(root->Right) == -2) {
//如果插入的值大于,当前节点的右孩子节点,说明该节点是插在root的右子树上的
//if (x > root->Left->data) RR_Rotate(&root);不能保证该节点一定有左子树
if (x > root->Right->data)RR_Rotate(&root);
else RL_Rotate(&root);
}
}
else if (x < root->data) {
root->Left = AVLTreeInsert(root->Left, x);
if (height(root->Left) - height(root->Right) == 2) {
if (x < root->Left->data) LL_Rotate(&root);
else LR_Rotate(&root);
}
}
else {
cout << "the number is already included." << endl;
return NULL;
}
return root;
} AVLTree AVLTreeCreat(int *a, int length) {
AVLTree T = NULL;
for (int i = 0; i < length; i++) {
T = AVLTreeInsert(T, a[i]);
}
return T;
}
Node* AVLFind(AVLTree T, int x) {
Node *p = T;
while (p) {
if (x == p->data) break;
p = x > p->data ? p->Right : p->Left;
}
return p;
} AVLTree AVLMax(AVLTree p)
{
if (!p) return NULL;
if (p->Right == NULL)
return p;
return AVLMax(p->Right);
} AVLTree AVLMin(AVLTree p)
{
if (!p)
return NULL;
if (p->Left == NULL)
return p;
return AVLMin(p->Left);
} void AVLTreeDel(AVLTree *root, int data)
{
if (!*root) {
cout << "delete failed" << endl;
return;
}
Node *p = *root;
if (data == p->data) {
//左右子树都非空
if (p->Left && p->Right) {
//在高度更大的那个子树上进行删除操作
//进左子树,右转到底,进右子树,左转到底,转弯碰壁,杀孩子。
if (height(p->Left) > height(p->Right)) {
Node *pre=NULL,*q = p->Left;
if (!q->Right)
q->Right = p->Right;
else {
while (q->Right) {
pre = q;
q = q->Right;
}
pre->Right = q->Left;
q->Left = p->Left;
q->Right = p->Right;
}
*root = q;
}
else {
Node *pre = NULL, *q = p->Right;
if (!q->Left)
q->Left = p->Left;
else {
while (q->Left) {
pre = q;
q = q->Left;
}
pre->Left = q->Right;
q->Left = p->Left;
q->Right = p->Right;
}
*root=q;
}
}
else
(*root) = (*root)->Left ? (*root)->Left : (*root)->Right;
delete p;
}
else if (data < p->data){//要删除的节点在左子树中
//在左子树中进行递归删除
AVLTreeDel(&(*root)->Left, data);
//判断是否仍然满足平衡条件
if (height(p->Right) - height(p->Left) == 2){
//如果当前节点右孩子的左子树更高
if (height(p->Right->Left) > height(p->Right->Right))
RL_Rotate(root);
else
RR_Rotate(root);
}
}
else{
AVLTreeDel(&(*root)->Right, data);
if (height(p->Left) - height(p->Right) == 2) {
if (height((*root)->Left->Left) > height((*root)->Left->Right))
LL_Rotate(root);
else
LR_Rotate(root);
}
}
} int height(BTree L) {
if (L == NULL)
return 0;
int left = height(L->Left);
int right = height(L->Right);
return left >= right ? left + 1 : right + 1;
} void checkCreat() {
int length = 10;
int *a = getNoRepateRandomArray(length, 10); for (int i = 0; i < length; i++) {
cout << a[i] << ",";
} cout << endl;
AVLTree T = AVLTreeCreat(a, length);
int t = rand() % length;
AVLTreeDel(&T, a[t]);
for (int i = t; i < length - 1; i++) {
a[i] = a[i + 1];
} Preorder(T);
cout << endl;
Inorder(T);
cout << endl;
Postorder(T);
cout << endl;
free(a);
}

数据结构篇——平衡二叉树(AVL树)的更多相关文章

  1. 二叉查找树(BST)、平衡二叉树(AVL树)(只有插入说明)

    二叉查找树(BST).平衡二叉树(AVL树)(只有插入说明) 二叉查找树(BST) 特殊的二叉树,又称为排序二叉树.二叉搜索树.二叉排序树. 二叉查找树实际上是数据域有序的二叉树,即对树上的每个结点, ...

  2. Java 树结构实际应用 四(平衡二叉树/AVL树)

    平衡二叉树(AVL 树) 1 看一个案例(说明二叉排序树可能的问题) 给你一个数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST), 并分析问题所在.  左边 BST 存在的问题分析: ...

  3. 平衡二叉树,AVL树之图解篇

    学习过了二叉查找树,想必大家有遇到一个问题.例如,将一个数组{1,2,3,4}依次插入树的时候,形成了图1的情况.有建立树与没建立树对于数据的增删查改已经没有了任何帮助,反而增添了维护的成本.而只有建 ...

  4. 二叉查找树(BST)、平衡二叉树(AVL树)

    二叉查找树(BST) 特殊的二叉树,又称为排序二叉树.二叉搜索树.二叉排序树. 二叉查找树实际上是数据域有序的二叉树,即对树上的每个结点,都满足其左子树上所有结点的数据域均小于或等于根结点的数据域,右 ...

  5. 图解:平衡二叉树,AVL树

    学习过了二叉查找树,想必大家有遇到一个问题.例如,将一个数组{1,2,3,4}依次插入树的时候,形成了图1的情况.有建立树与没建立树对于数据的增删查改已经没有了任何帮助,反而增添了维护的成本.而只有建 ...

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

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

  7. 平衡二叉树,AVL树之代码篇

    看完了第一篇博客,相信大家对于平衡二叉树的插入调整以及删除调整已经有了一定的了解,下面,我们开始介绍代码部分. 首先,再次提一下使用的结构定义 typedef char KeyType; //关键字 ...

  8. 大话数据结构—平衡二叉树(AVL树)

    平衡二叉树(Self-Balancing Binary Search Tree/Height-Balanced Binary Search Tree),是一种二叉排序树,当中每个节点的左子树和右子树的 ...

  9. 平衡二叉树-AVL树(LL、RR、LR、RL旋转)

    平衡二叉树的定义: 任意的左右子树高度差的绝对值不超过1,将这样的二叉树称为平衡二叉树,二叉平衡树前提是一个二叉排序树. 平衡二叉树的插入: 二叉平衡树在插入或删除一个结点时,先检查该操作是否导致了树 ...

随机推荐

  1. 洛谷P4593 [TJOI2018]教科书般的亵渎

    小豆喜欢玩游戏,现在他在玩一个游戏遇到这样的场面,每个怪的血量为\(a_i\)​,且每个怪物血量均不相同,小豆手里有无限张"亵渎".亵渎的效果是对所有的怪造成\(1\)点伤害,如果 ...

  2. 【转】python中numpy模块下的np.clip()的用法

    转自:https://blog.csdn.net/HHTNAN/article/details/79799612 Numpy 中clip函数的使用 一维数组 其中a是一个数组,后面两个参数分别表示最小 ...

  3. OsharpNS轻量级.net core快速开发框架简明入门教程-多上下文配置(多个数据库的使用)

    OsharpNS轻量级.net core快速开发框架简明入门教程 教程目录 从零开始启动Osharp 1.1. 使用OsharpNS项目模板创建项目 1.2. 配置数据库连接串并启动项目 1.3. O ...

  4. Loj #3042. 「ZJOI2019」麻将

    Loj #3042. 「ZJOI2019」麻将 题目描述 九条可怜是一个热爱打麻将的女孩子.因此她出了一道和麻将相关的题目,希望这题不会让你对麻将的热爱消失殆尽. 今天,可怜想要打麻将,但是她的朋友们 ...

  5. DirectX:Vector

    Tag DirectX下的博客主要用于记录DirectX的学习过程,主要参考<DirectX 12 3D 游戏实战开发>. Vector in DirectX Shader的编写离不开数学 ...

  6. HTML+css基础 Text文本属性

    Text文本属性: 1.颜色  color color:red 2.文本缩进 text-indent 属性值 数字+px: text-indent:10px: 3.文本修饰 text-decorati ...

  7. Vue.js 源码分析(十) 基础篇 ref属性详解

    ref 被用来给元素或子组件注册引用信息.引用信息将会注册在父组件的 $refs 对象上.如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素:如果用在子组件上,引用就指向组件实例,例如: ...

  8. 携程 Apollo分布式部署

    一.环境准备 操作系统:CentOS release 7.5 (启动脚本理论上支持所有Linux发行版,建议CentOS 7) JDK :jdk1..0_162 (建议安装Java 1.8+) MyS ...

  9. EOF的意义及用法

    c/c++ 中EOF的意义及用法 EOF,为End Of File的缩写,通常在文本的最后存在此字符表示资料结束. 在微软的DOS和Windows中,读取数据时终端不会产生EOF.此时,应用程序知道数 ...

  10. SQLServer之列数据转换为行数据

    UNPIVOT特点 UNPIVOT运算符通过将列旋转到行来执行PIVOT的反向操作,UNPIVOT 并不完全是 PIVOT 的逆操作. PIVOT 执行聚合,并将多个可能的行合并为输出中的一行.UNP ...