大家好,我是bigsai,好久不见,甚是想念,今天给大家讲讲AVL树。
对于树这种数据结构,想必大家也已经不再陌生,我们简单回顾一下。
在树的种类中,通常分成二叉树和多叉树,我们熟悉的二叉树种类有二叉搜索(排序、查找)树、二叉平衡树、伸展树、红黑树等等。而熟悉的多叉树像B树、字典树都是经典多叉树。
普通的二叉树,我们研究其遍历方式,因为其没啥规则约束查找和插入都很随意所以很少有研究价值。
但是二叉树结构上很有特点:左孩子和右孩子,两个不同方向的孩子对应二进制的01,判断的对错,比较的大小 ,所以根据这个结构所有树左侧节点比父节点小,右侧节点比父节点大,这时候就诞生了二叉搜索(排序)树。二叉搜索(排序)树的一大特点就是查找效率提高了,因为查找一个元素位置或者查看元素是否存在通过每遇到一个节点直接进行比较就可以一步步逼近结果的位置。
但二叉搜索(排序树)有个很大的问题就是当插入节点很有序,很可能成为一棵斜树或者深度很高,那么这样的一个查找效率还是趋近于线性O(n)级别,所以这种情况二叉搜索(排序)树的效率是比较低的。
所以,人们有个期望:对一棵树来说插入节点,小的还在左面,大的还在右面方便查找,但是能不能不要出现那么斜的情况?
这不,平衡二叉搜索(AVL)树就是这么干的,AVL在插入的时候每次都会旋转自平衡,让整个树一直处于平衡状态,让整个树的查询更加稳定(logN)。我们首先来看一下什么是AVL树:
n0=0
;n1=1
;nk=n(k-1)+n(k-2)+1
;(求法可以类比斐波那契)难点:AVL是一颗二叉排序树,用什么样的规则或者规律让它能够在复杂度不太高的情况下实现动态平衡呢?
如果从简单情况模型看,其实四种不平衡情况很简单,分别是RR,LL,RL,LR四种不平衡情况。
然后将其平衡的结果也很容易(不考虑其附带节点只看结果),将中间大小数值移动最上方,其他相对位置不变即可:
当然,这个仅仅是针对三个节点情况太过于理想化了,很多时候让你找不平衡的点,或者我们在解决不平衡的时候,我们需要的就是找到第一个不平衡(从底往上)的点将其平衡即可,下面列举两个不平衡的例子:
上述四种不平衡条件情况,可能出现在底部,也可能出现在头,也可能出现在某个中间节点导致不平衡, 而我们只需要研究其首次不平衡点,解决之后整棵树即继续平衡,在具体的处理上我们使用递归的方式解决问题。
针对四种不平衡的情况,这里对每种情况进行详细的讲解。
这里的RR指的是节点模型的样子,其含义是需要左单旋转(记忆时候需要注意一下RR不是右旋转)!
出现这种情况的原因是节点的右侧的右侧较深这时候不平衡节点需要左旋,再细看过程。
root(oldroot)
节点下沉,中间节点(newroot)
上浮.而其中中间节点(newroot)
的右侧依然不变。根节点(oldroot)
(毕竟一棵树)。但是这样newroot
原来左侧节点H
空缺。而我们需要仍然让整个树完整并且满足二叉排序树的规则。NULL
,不过不影响操作!其更详细流程为:
而左旋的代码可以表示为:
private node getRRbanlance(node oldroot) {//右右深,需要左旋
// TODO Auto-generated method stub
node newroot=oldroot.right;
oldroot.right=newroot.left;
newroot.left=oldroot;
oldroot.height=Math.max(getHeight(oldroot.left),getHeight(oldroot.right))+1;
newroot.height=Math.max(getHeight(newroot.left),getHeight(newroot.right))+1;//原来的root的高度需要从新计算
return newroot;
}
而右旋和左旋相反,但是思路相同,根据上述进行替换即可!
代码:
private node getLLbanlance(node oldroot) {//LL小,需要右旋转
// TODO Auto-generated method stub
node newroot=oldroot.left;
oldroot.left=newroot.right;
newroot.right=oldroot;
oldroot.height=Math.max(getHeight(oldroot.left),getHeight(oldroot.right))+1;
newroot.height=Math.max(getHeight(newroot.left),getHeight(newroot.right))+1;//原来的root的高度需要从新金酸
return newroot;
}
这个RL你可能有点懵圈,为啥RR叫左旋,LL叫右旋,这个RL怎么就叫先右后左旋转了?
别急别急,这个之所以先后后左,是因为具体需要中间节点右旋一次,然后上面节点左旋一次才能平衡,具体可以下面慢慢看。
首先产生这种不平衡的条件原因是:ROOT节点右侧左侧节点的深度高些,使得与左侧的差大于1,这个与我们前面看到的左旋右旋不同因为旋转一次无法达到平衡!
对于右左结构,中间(R)的最大,两侧(ROOT,R.L)的最小,但是下面(R.L
)的比上面(ROOT
)大(R.L
在ROOT
右侧)所以如果平衡的话,那么R.L
应该在中间,而R
应该在右侧,原来的ROOT
在左侧。
这个过程节点的变化浮动比较大,需要妥善处理各个子节点的移动使其满足二叉排序树的性质!
这种双旋转具体实现其实也不难,不要被外表唬住,这里面双旋转我提供两种解答方法。
思路(标准答案)1:两次旋转RR,LL
这个处理起来非常容易,因为前面已经解决RR(左旋),LL(右旋)的问题,所以这里面在上面基础上可以直接解决,首先对R节点进行一次LL右旋,旋转一次之后R在最右侧,这就转化成RR不平衡旋转的问题了,所以这个时候以ROOT开始一次RR左旋即可完成平衡,具体流程可以参考下面这张图。
思路(个人方法)2:直接分析
根据初始和结果的状态,然后分析各个节点变化顺序=,手动操作这些节点即可。其实不管你怎么操作,只要能满足最后结构一致就行啦!
首先根据ROOT
,R
,R.L
三个节点变化,R.L
肯定要在最顶层,左右分别指向ROOT和R,那么这其中R.left
,ROOT.right
发生变化(原来分别是R.L和R)暂时为空。而刚好根据左右大小关系可以补上R.L
原来的孩子节点A
,B
。
代码为:(注释部分为方案1)
private node getRLbanlance(node oldroot) {//右左深
// node newroot=oldroot.right.left;
// oldroot.right.left=newroot.right;
// newroot.right=oldroot.right;
// oldroot.right=newroot.left;
// newroot.left=oldroot;
// oldroot.height=Math.max(getHeight(oldroot.left),getHeight(oldroot.right))+1;
// newroot.right.height=Math.max(getHeight(newroot.right.left),getHeight(newroot.right.right))+1;
// newroot.height=Math.max(getHeight(oldroot.left),getHeight(newroot.right))+1;//原来的root的高度需要从新金酸
oldroot.right =getLLbanlance(oldroot.right);
oldroot.height=Math.max(getHeight(oldroot.left), getHeight(oldroot.right))+1;
return getRRbanlance(oldroot);
}
这个情况和RL情况相似,采取相同操作即可。
根据上述RL修改即可
这部分代码为
private node getLRbanlance(node oldroot) {
oldroot.left =getRRbanlance(oldroot.left);
oldroot.height=Math.max(getHeight(oldroot.left), getHeight(oldroot.right))+1;
return getLLbanlance(oldroot);
}
首先对于节点多个height
属性。用于计算高度(平衡因子)
插入是递归插入,递归是一个来回的过程,去的过程进行插入,回的过程进行高度更新,和检查是否平衡。推荐不要写全局递归计算高度,效率太低下,事实上高度变化只和插入和平衡有关,仔细考虑即不会有疏漏!
代码写的比较早,如有命名不规范的情况,还请勿喷,如果有疏漏还请指出!
import java.util.ArrayDeque;
import java.util.Queue;
public class AVLTree {
class node
{
int value;
node left;
node right;
int height;
public node() {
}
public node(int value)
{
this.value=value;
this.height=0;
}
public node(int value,node left,node right)
{
this.value=value;
this.left=left;this.right=right;
this.height=0;
}
}
node root;// 根
public AVLTree() {
this.root = null;
}
public boolean isContains(int x)// 是否存在
{
node current = root;
if (root == null) {
return false;
}
while (current.value != x && current != null) {
if (x < current.value) {
current = current.left;
}
if (x > current.value) {
current = current.right;
}
if (current == null) {
return false;
} // 在里面判断如果超直接返回
}
// 如果在这个位置判断是否为空会导致current.value不存在报错
if (current.value == x) {
return true;
}
return false;
}
public int getHeight(node t)
{
if(t==null) {return -1;}//
return t.height;
//return 1+Math.max(getHeight(t.left), getHeight(t.right));这种效率太低
}
public void cengxu(node t) {//层序遍历
Queue<node> q1 = new ArrayDeque<node>();
if (t == null)
return;
if (t != null) {
q1.add(t);
}
while (!q1.isEmpty()) {
node t1 = q1.poll();
if (t1.left != null)
q1.add(t1.left);
if (t1.right != null)
q1.add(t1.right);
System.out.print(t1.value + " ");
}
System.out.println();
}
public void zhongxu(node t)//中序遍历 中序遍历:左子树---> 根结点 ---> 右子树
{//为了测试改成中后都行
if(t!=null)
{
zhongxu(t.left);
System.out.print(t.value+" ");//访问完左节点访问当前节点
zhongxu(t.right);
//System.out.print(t.value+" ");//访问完左节点访问当前节点
}
}
public void qianxu(node t)//前序递归 前序遍历:根结点 ---> 左子树 ---> 右子树
{
if(t!=null) {
System.out.print(t.value+" ");//当前节点
qianxu(t.left );
qianxu(t.right);}
}
public void insert(int value) {
root=insert(value, root);
}
public node insert(int x,node t)//插入 t是root的引用
{
node a1=new node(x);
//if(root==null) {root=a1;return root;}
if(t==null) {return a1;}
//插入操作。递归实现
else if(t!=null)
{
if(x<t.value)
{ t.left=insert(x,t.left);}
else
{ t.right= insert(x,t.right);}
}
/*
* 更新当前节点的高度,因为整个插入只有被插入的一方有影响,
* 所以递归会更新好最底层的,上层可直接调用
*/
t.height=Math.max(getHeight(t.left),getHeight(t.right))+1;//不要写成递归, 递归效率低
return banlance(t);//因为java对象传参机制,需要克隆,不可直接t=xx 否则变换
}
private node banlance(node t) {
// TODO Auto-generated method stub
//if(t==null)return null;
int lefthigh=getHeight(t.left);
int righthigh=getHeight(t.right);
if(Math.abs(lefthigh-righthigh)<=1)//不需要平衡滴
{ return t;}
else if(lefthigh<righthigh)//右侧大
{
if(getHeight(t.right.left)<getHeight(t.right.right))//RR需要左旋
{
return getRRbanlance(t);
}
else {
return getRLbanlance(t);
}
}
else {
if(getHeight(t.left.left)>getHeight(t.left.right))//ll 左左
{
return getLLbanlance(t);
}
else {
return getLRbanlance(t);
}
}
}
/*
* oldroot(平衡因子为2,不平衡) ==> newroot
* / \ / \
* B newroot(平衡因子为1) oldroot D
* / \ / \ \
* C D B C E
* \
* E
*/
private node getRRbanlance(node oldroot) {//右右深,需要左旋
// TODO Auto-generated method stub
node newroot=oldroot.right;
oldroot.right=newroot.left;
newroot.left=oldroot;
oldroot.height=Math.max(getHeight(oldroot.left),getHeight(oldroot.right))+1;
newroot.height=Math.max(getHeight(newroot.left),getHeight(newroot.right))+1;//原来的root的高度需要从新计算
return newroot;
}
/*
* 右旋同理
*/
private node getLLbanlance(node oldroot) {//LL小,需要右旋转
// TODO Auto-generated method stub
node newroot=oldroot.left;
oldroot.left=newroot.right;
newroot.right=oldroot;
oldroot.height=Math.max(getHeight(oldroot.left),getHeight(oldroot.right))+1;
newroot.height=Math.max(getHeight(newroot.left),getHeight(newroot.right))+1;//原来的root的高度需要从新金酸
return newroot;
}
private node getLRbanlance(node oldroot) {
oldroot.left =getRRbanlance(oldroot.left);
oldroot.height=Math.max(getHeight(oldroot.left), getHeight(oldroot.right))+1;
return getLLbanlance(oldroot);
}
/* (不平衡出现在右左节点)
* oldroot ==> newroot
* / \ / \
* A B oldroot B
* / \ / \ / \
* newroot D A E F D
* / \
* E F
*/
private node getRLbanlance(node oldroot) {//右左深
// node newroot=oldroot.right.left;
// oldroot.right.left=newroot.right;
// newroot.right=oldroot.right;
// oldroot.right=newroot.left;
// newroot.left=oldroot;
// oldroot.height=Math.max(getHeight(oldroot.left),getHeight(oldroot.right))+1;
// newroot.right.height=Math.max(getHeight(newroot.right.left),getHeight(newroot.right.right))+1;
// newroot.height=Math.max(getHeight(oldroot.left),getHeight(newroot.right))+1;//原来的root的高度需要从新金酸
oldroot.right =getLLbanlance(oldroot.right);
oldroot.height=Math.max(getHeight(oldroot.left), getHeight(oldroot.right))+1;
return getRRbanlance(oldroot);
}
}
测试情况:
AVL的理解需要时间,当然笔者的AVL自己写的可能有些疏漏,如果有问题还请各位一起探讨!
当然,除了插入,AVL还有删除等其他操作,(原理相似。删除后平衡)有兴趣可以一起研究。
原创不易,如果本文对你有帮助 !
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/jackutRD9YXTMl-wZQ8BeQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。