算法-搜索(3)AVL树
AVL树高度平衡的二叉搜索树,任一点的平衡印章只能是+1、-1、0,从而尽量降低树的高度。
如果它有n个结点,高度可保持在O(log2n),平均搜索长度也可保持在O(log2n)。
(1)AVL树的插入
在插入一个新结点时,需要从插入位置沿通向根的路径回溯,检查各结点左右子树高度差。
发现结点的平衡因子为0,刚刚是在其较矮的子树插入新结点,从该结点到根的路径上各结点为根的子树高度不变、平衡因子不变,无需继续检查。
发现结点平衡因子|bf|=1,说明插入前是0,插入后该结点没有失去平衡。但该子树高度增加了,还需要继续往根方向检查。
发现某一结点|bf|=2不平衡,停止回溯,做平衡化旋转。
从发生不平衡的结点起,沿刚才回溯的路径取直接下两层的结点。
1.这三个结点位于同一条直线上,采用单旋转进行平衡化
bf=2,右子树高,查看右结点bf=1,执行左单旋转
执行后ptr和subL的bf均变为0:
bf=-2,左子树高,查看左结点bf=-1,执行右单旋转
执行后ptr和subL的bf均变为0
2.这三个结点位于一条折线上,采用双旋转进行平衡化
bf=2,右子树高,查看右结点bf=-1,无论ptr->bf是1还是-1,都执行先右后左双旋转
如果ptr的bf原为1,右单旋转后subR的bf改为0,先右后左双旋转,旋转后subL的bf变为-1
如果ptr的bf原为-1,右单旋转后subR的bf改为1,先右后左双旋转,旋转后subL的bf变为0
ptr的bf最终变为0
bf=-2,左子树高,查看左结点bf=1,无论ptr->bf是1还是-1,都执行先左后右双旋转
如果ptr的bf原为-1,左单旋转后subL的bf改为0,先左后右双旋转,旋转后subR的bf变为1
如果ptr的bf原为1,左单旋转后subL的bf改为-1,先左后右双旋转,旋转后subR的bf变为0
ptr的bf最终变为0
(2)AVL树的删除
1.被删结点有两个子女
寻找被删结点p在中序下的直接前驱或者直接后继q,把q的值给结点p,然后把结点q删除(q最多一个子女)
2.被删结点最多一个子女q
直接把p的父结点pr中原来指向p的指针指向q。
q是pr的左子女的话,pr的bf加1;q是pr的右子女的话,pr的bf减1。
(1)pr的bf原为0,左/右子树被缩短后改为1/-1,以pr为根的子树高度未变,pr到根结点的路径上所有结点都不需要调整。
(2)pr的bf原不为0,较高的子树被缩短,bf变为0。
以pr为根的子树虽然平衡,但高度减少了。需要继续考察其父结点。
(3)pr的bf原不为0,较矮的子树又被缩短,bf变为±2。令q指向pr的较高的子树
①q->bf==0的情况:
pr的bf=2,右子树较高,右子树q的bf为0,作左单旋转
旋转后q->bf=pr->bf=0,还要把q->pf改为-1,pr->bf改为1
pr的bf=-2,左子树较高,左子树q的bf为0,作右单旋转
旋转后q->bf=pr->bf=0,还要把q->pf改为1,pr->bf改为-1
②pr->bf和q->bf正负号相同的情况:
pr的bf=2,右子树较高,右子树q的bf=1,执行左单旋转,旋转后q->bf=pr->bf=0
pr的bf=-2,左子树较高,左子树q的bf=-1,执行右单旋转,旋转后q->bf=pr->bf=0
③pr和p的bf正负号相反,执行双旋转,先绕q转一次,再绕pr转一次:
pr的bf=2,右子树较高,右子树q的bf为-1,执行先右后左双旋转
pr的bf=-2,左子树较高,左子树q的bf为1,执行先左后右双旋转
处理后子树高度减少1,继续考察其父结点。
#include <iostream>
#include "stack.h"
using namespace std; template <class E,class K>
struct AVLNode:public BSTNode<E,K>{
int bf; //右子树高度-左子树高度
AVLNode():left(NULL),right(NULL),bf(){}
AVLNode(E d,AVLNode<E,K> *l=NULL,AVLNode<E,K> *r=NULL):data(d),left(l),right(r),bf(){}
} template <class E,class K>
class AVLTree:public BST<E,K>{
public:
AVLTree():root(NULL){}
AVLTree(K Ref):RefValue(Ref),root(NULL){}
bool Insert(E& el){return Insert(root,el);}
bool Remove(K x,E& el){return Remove(root,el);}
friend istream& operator>>(istream& in,AVLTree<E,K>& tree);
friend ostream& operator<<(ostream& out,AVLTree<E,K>& tree);
int Height()const;
protected:
AVLNode<E,K> *Search(K x,AVLNode<E,K>* &par)const;
bool Insert(AVLNode<E,K>* &ptr,E& el);
bool Remove(AVLNode<E,K>* &ptr,K x,E& el);
void RotateL(AVLNode<E,K>* &ptr);
void RotateR(AVLNode<E,K>* &ptr);
void RotateLR(AVLNode<E,K>* &ptr);
void RotateRL(AVLNode<E,K>* &ptr);
int Height(AVLNode<E,K> *ptr)const;
void AVLTree<E,K>::Traverse(AVLNode<E,K> *ptr,ostream& out)const;
} template <class E,class K>
void AVLTree<E,K>::RotateL(AVLNode<E,K>* &ptr){
//右子树比左子树高,对以ptr为根的AVL树做左单旋转,旋转后新根在ptr
AVLNode<E,K> *subL=ptr;
ptr=subL->right; //两个结点平衡因子均为正,需要做左单旋转
subL->right=ptr->left;
ptr->left=subL;
ptr->bf=subL->bf=;
} template <class E,class K>
void AVLTree<E,K>::RotateR(AVLNode<E,K>* &ptr){
//左子树比右子树高,对以ptr为根的AVL树做右单旋转,旋转后新根在ptr
AVLNode<E,K> *subR=ptr;
ptr=subR->left; //两个结点平衡因子均为负,需要做右单旋转
subR->left=ptr->right;
ptr->right=subR;
ptr->bf=subR->bf=;
} template <class E,class K>
void AVLTree<E,K>::RotateLR(AVLNode<E,K>* &ptr){
AVLNode<E,K> *subR=ptr,*subL=ptr->left;
ptr=subL->right;
subL->right=ptr->left;
ptr->left=subL;
if(ptr->bf<=) subL->bf=; //如果ptr->bf原为-1,左单旋转后subL->bf为0
else subL->bf=-; //如果ptr->bf原为1,左单旋转后subL->bf为-1
subR->left=ptr->right;
ptr->right=subR;
if(ptr->bf==-) subR->bf=; //如果ptr->bf原为-1,先左后双旋转后subR->bf为1
else subR->bf=; //如果ptr->bf原为1,先左后双旋转后subR->bf为0
ptr->bf=;
} template <class E,class K>
void AVLTree<E,K>::RotateRL(AVLNode<E,K>* &ptr){
AVLNode<E,K> *subL=ptr,*subR=ptr->right;
ptr=subR->left;
subR->left=ptr->right;
ptr->right=subR;
if(ptr->bf>=) subR->bf=;
else subR->bf=;
subL->right=ptr->left;
ptr->left=subL;
if(ptr->bf==) subL->bf=-;
else subL->bf=;
ptr->bf=;
} template <class E,class K>
bool AVLTree<E,K>::Insert(AVLNode<E,K>* &ptr,E& el){ //改变bf的步骤在旋转中做了,Insert方法中无需再改变
AVLNode<E,K> *pr=NULL,*p=ptr,*q;
int d;
stack<AVLNode<E,K>*> st;
while(p!=NULL){
if(el==p->data) return false;
pr=p;
st.push(pr);
if(el<p->data) p=p->left;
else p=p->right;
}
p=new AVLNode<E,K>(el);
if(p==NULL){
cerr<<"存储空间不足!"<<endl;
exit();
}
if(pr==NULL){ //空树,新结点成为根结点
ptr=p;
return true;
}
if(el<pr->data) pr->left=p;
else pr->right=p;
while(!st.IsEmpty()){
st.Pop(pr);
if(p==pr->left) pr->bf--; //调整父结点平衡因子
else pr->bf++;
//第一种情况,平衡因子为0,说明刚才在pr较矮的子树上插入新结点,结点pr处平衡且高度未变。结点pr到根的路径上所有结点为根的子树高度都未变。可以直接结束重新平衡化的处理。
if(pr->bf==) break;
//第二种情况,平衡因子绝对值为1,说明插入前平衡因子为0,插入后以pr为根的子树没有失去平衡,但仍然需要检查结点pr到根的路径上所有结点为根的子树
if(pr->bf== || pr->bf==-) p=pr;
//第三种情况,平衡因子绝对值为2,说明新结点在较高的子树上插入,造成了不平衡,需要做平衡化旋转
else{
d=(pr->bf<)?-:;
if(p->bf==d){ //两结点平衡因子同号,单旋转
if(d==-) RotateR(pr); //pr的bf为-2,p的bf为1,右单旋转
else RotateL(pr); //pr的bf为2,p的bf为-1,左单旋转
}
else{
if(d==-) RotateLR(pr); //pr的bf为2,p的bf为-1,先右后左旋转
else RotateRL(pr); //pr的bf为-2,p的bf为-1,先左后右旋转
}
break; //旋转之后以pr为根的子树高度降低,无需再向上层回溯
}
}
if(st.IsEmpty()) ptr=pr; //栈空,pr作为新根
else{
st.getTop(q); //栈不空,取出pr的父结点
if(q->data>pr->data) q->left=pr;
else q->right=pr;
}
return true;
} template <class E,class K>
istream& operator>>(istream& in,AVLTree<E,K>& Tree){
E item;
cout<<"Construct AVL tree:\n";
cout<<"Input Data(end with"<<Tree.RefValue<<"):";
in>>item;
while(item.key!=Tree.RefValue){
Tree.Insert(item);
cout<<"Input Data(end with"<<Tree.RefValue<<"):";
in>>item;
}
return in;
} template <class E,class K>
ostream& operator<<(ostream& out,const AVLTree<E,K>& Tree){
out<<"Inorder traversal of AVL tree.\n";
Tree.Traverse(Tree.root,out);
out<<endl;
return out;
} template <class E,class K>
void AVLTree<E,K>::Traverse(AVLNode<E,K> *ptr,ostream& out)const{
if(ptr!=NULL){
Traverse(ptr->left,out);
out<<ptr->data<<'';
Traverse(ptr->right,out);
}
} template <class E,class K>
bool AVLTree<E,K>::Remove(AVLNode<E,K>* &ptr,K x,E& el){
AVLNode<E,K> *pr=NULL,*p=ptr,*q,*ppr;
int d,dd=;
while(p!=NULL){
if(k==p->data.key) break;
pr=p;
st.Push(pr);
if(k<p->data.key) p=p->left;
else p=p->right;
}
if(p==NULL) return false;
if(p->left!=NULL && p->right!=NULL){ //被删结点有两个子女
pr=p;
st.Push(pr);
q=p->left; //在p的左子树找p的直接前驱
while(q->right!=NULL){
pr=q;
st.Push(pr);
q=q->right;
}
p->data=q->data;
p=q; //被删结点转换为q,q最多一个子女
}
if(p->left!=NULL) q=p->left; //被删结点只有一个子女或者没有子女
else q=p->right;
if(pr==NULL) ptr=q; //被删结点为根结点,让其子女成为新根
else{ //被删结点不是根结点
if(pr->left==p) pr->left=q; //p的父结点pr原本指向p的指针改指向q
else pr->right=q;
while(!st.IsEmpty()){ //重新平衡化
st.Pop(pr); //从栈中推出父结点
if(pr->right==q) pr->bf--; //调整父结点的平衡因子
else pr->bf++;
if(!st.IsEmpty()){
st.getTop(ppr); //从栈中取出祖父结点
dd=(ppr->left==pr)?-:; //旋转后与上层链接方向
}
else dd=; //栈空,旋转后不与上层链接
if(pr->bf== || pr->bf==-)//pr的平衡因子原为0,在它的左/右子树被缩短后,平衡因子变为1或者-1,以pr为根的子树高度没有改变,从pr到根结点的路径上所有结点都不需要调整
break;
if(pr->bf!=){
if(pr->bf<){d=-;q=pr->left;}
else{d=;q=pr->right;}
if(q->bf==){
if(d==-){ //pr的平衡因子为-2,左子树q的平衡因子为0,执行右单旋转
RotateR(pr);
pr->bf=;
pr->left->bf=-;
}
else{ //pr的平衡因子为2,右子树q的平衡因子为0,执行左单旋转
RotateL(pr);
pr->bf=-;
pr->right->bf=;
}
break;
}
if(q->bf==d){ //q和pr的平衡因子正负号相同,执行一个单旋转来恢复平衡
if(d==-) RotateR(pr); //pr平衡因子为-2,p平衡因子为-1,右单旋转
else RotateL(pr); //pr平衡因子为2,p平衡因子为1,左单旋转
}
else{ //q和pr的平衡因子正负号相反,执行一个双旋转来恢复平衡
if(d==-) RotateLR(pr); //pr的bf=-2,左子树较高,左子树p的bf为1,执行先左后右双旋转
else RotateRL(pr); //pr的bf=2,右子树较高,右子树p的bf为-1,执行先右后左双旋转
}
if(dd==-) ppr->left=pr;
else if(dd==) ppr->right=pr; //旋转后新根与上层链接
}
q=pr; //pr的平衡因子变为0(较高的子树被缩短了),此时以pr为根的树平衡但高度减少1,需要继续考察pr父结点的平衡状态
}
if(st.IsEmpty()) ptr=pr; //调整到树的根结点
}
delete p;
return true;
}
算法-搜索(3)AVL树的更多相关文章
- 数据结构与算法(九):AVL树详细讲解
数据结构与算法(一):基础简介 数据结构与算法(二):基于数组的实现ArrayList源码彻底分析 数据结构与算法(三):基于链表的实现LinkedList源码彻底分析 数据结构与算法(四):基于哈希 ...
- [算法] 数据结构之AVL树
1 .基本概念 AVL树的复杂程度真是比二叉搜索树高了整整一个数量级——它的原理并不难弄懂,但要把它用代码实现出来还真的有点费脑筋.下面我们来看看: 1.1 AVL树是什么? AVL树本质上还是一棵 ...
- 数据结构与算法——平衡二叉树(AVL树)
目录 二叉排序树存在的问题 基本介绍 单旋转(左旋转) 树高度计算 旋转 右旋转 双旋转 完整代码 二叉排序树存在的问题 一个数列 {1,2,3,4,5,6},创建一颗二叉排序树(BST) 创建完成的 ...
- 红黑树与AVL树
概述:本文从排序二叉树作为引子,讲解了红黑树,最后把红黑树和AVL树做了一个比较全面的对比. 1 排序二叉树 排序二叉树是一种特殊结构的二叉树,可以非常方便地对树中所有节点进行排序和检索. 排序二叉树 ...
- 平衡搜索树(一) AVL树
AVL树 AVL树又称为高度平衡的二叉搜索树,是1962年有俄罗斯的数学家G.M.Adel'son-Vel'skii和E.M.Landis提出来的.它能保持二叉树的高度 平衡,尽量降低二叉树的高度,减 ...
- 深入浅出数据结构C语言版(12)——平衡二叉查找树之AVL树
在上一篇博文中我们提到了,如果对普通二叉查找树进行随机的插入.删除,很可能导致树的严重不平衡 所以这一次,我们就来介绍一种最老的.可以实现左右子树"平衡效果"的树(或者说算法),即 ...
- AVL树的平衡算法(JAVA实现)
1.概念: AVL树本质上还是一个二叉搜索树,不过比二叉搜索树多了一个平衡条件:每个节点的左右子树的高度差不大于1. 二叉树的应用是为了弥补链表的查询效率问题,但是极端情况下,二叉搜索树会无限接近 ...
- AVL树的插入删除查找算法实现和分析-1
至于什么是AVL树和AVL树的一些概念问题在这里就不多说了,下面是我写的代码,里面的注释非常详细地说明了实现的思想和方法. 因为在操作时真正需要的是子树高度的差,所以这里采用-1,0,1来表示左子树和 ...
- 算法与数据结构(十一) 平衡二叉树(AVL树)
今天的博客是在上一篇博客的基础上进行的延伸.上一篇博客我们主要聊了二叉排序树,详情请戳<二叉排序树的查找.插入与删除>.本篇博客我们就在二叉排序树的基础上来聊聊平衡二叉树,也叫AVL树,A ...
随机推荐
- isinstance方法判断可迭代和迭代器
from collections import Iterable print(isinstance([],Iterable)) print(isinstance( {}, Iterable)) pri ...
- filter 函数基本写法
filter 返回一个符合要求的元素所构成的新列表 filter(函数,可迭代对象) map 和 filter 混合使用将 lst_num 中为偶数的取出来进行加2 和 乘2 操作 2020- ...
- pandas_时间序列和常用操作
# 时间序列和常用操作 import pandas as pd # 每隔五天--5D pd.date_range(start = '',end = '',freq = '5D') ''' Dateti ...
- PHP array_intersect_key() 函数
实例 比较两个数组的键名,并返回交集: <?php$a1=array("a"=>"red","b"=>"gree ...
- PHP date_sunrise() 函数
------------恢复内容开始------------ 实例 返回葡萄牙里斯本今天的日出时间: <?php// Lisbon, Portugal:// Latitude: 38.4 Nor ...
- Blob分析之ball_seq.hdev
* ball_seq.hdev: Inspection of Ball Bonding * 关闭更新dev_update_off ()*图像集合ImageNames := 'die/' + ['die ...
- luogu P3264 [JLOI2015]管道连接
LINK:管道连接 一张无向图 有P个关键点 其中有K个集合 各个集合要在图中形成联通块 边有边权 求最小代价. 其实还是生成树问题 某个点要和某个点要在生成树中 类似这个意思. 可以发现 是斯坦纳树 ...
- 打造静态分析器(二)基于Asp.Net Core 3.0的AspectCore组件检测
上一篇,我们打造了一个简单的分析器,但是我们实际使用分析器就是为了对项目做分析检测,增加一些非语法的自检的 比如Asp.Net Core 3.0的替换依赖注入检测 设计分析 我们创建一个默认的Asp. ...
- mybatis中的延迟查询思想
1.一对一延迟加载 延迟加载: 就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据.延迟加载也称懒加载. 好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比 ...
- python1.1列表知识点:
#定义列表[]a=[1,2,3,4,5,6,7,"hello","world"]#列表索引从0开始,指定位置提取元素print(a[3])print(a) #列 ...