网易首页 > 网易号 > 正文 申请入驻

看动画学算法之:平衡二叉搜索树AVL Tree

0
分享至

简介

平衡二叉搜索树是一种特殊的二叉搜索树。为什么会有平衡二叉搜索树呢?

考虑一下二叉搜索树的特殊情况,如果一个二叉搜索树所有的节点都是右节点,那么这个二叉搜索树将会退化成为链表。从而导致搜索的时间复杂度变为O(n),其中n是二叉搜索树的节点个数。

而平衡二叉搜索树正是为了解决这个问题而产生的,它通过限制树的高度,从而将时间复杂度降低为O(logn)。

AVL的特性

在讨论AVL的特性之前,我们先介绍一个概念叫做平衡因子,平衡因子表示的是左子树和右子树的高度差。

如果平衡因子=0,表示这是一个完全平衡二叉树。

如果平衡因子=1,那么这棵树就是平衡二叉树AVL。

也就是是说AVL的平衡因子不能够大于1。

先看一个AVL的例子:

总结一下,AVL首先是一个二叉搜索树,然后又是一个二叉平衡树。

AVL的构建

有了AVL的特性之后,我们看下AVL是怎么构建的。

public class AVLTree {

//根节点
Node root;

class Node {
int data; //节点的数据
int height; //节点的高度
Node left;
Node right;

public Node(int data) {
this.data = data;
left = right = null;
}
}

同样的,AVL也是由各个节点构成的,每个节点拥有data,left和right几个属性。

因为是二叉平衡树,节点是否平衡还跟节点的高度有关,所以我们还需要定义一个height作为节点的高度。

在来两个辅助的方法,一个是获取给定的节点高度:

//获取给定节点的高度
int height(Node node) {
if (node == null)
return 0;
return node.height;
}

和获取平衡因子:

//获取平衡因子
int getBalance(Node node) {
if (node == null)
return 0;
return height(node.left) - height(node.right);
}
AVL的搜索

AVL的搜索和二叉搜索树的搜索方式是一致的。

先看一个直观的例子,怎么在AVL中搜索到7这个节点:

搜索的基本步骤是:

  1. 从根节点15出发,比较根节点和搜索值的大小

  2. 如果搜索值小于节点值,那么递归搜索左侧树

  3. 如果搜索值大于节点值,那么递归搜索右侧树

  4. 如果节点匹配,则直接返回即可。

相应的java代码如下:

//搜索方法,默认从根节点搜索
public Node search(int data){
return search(root,data);
}

//递归搜索节点
private Node search(Node node, int data)
{
// 如果节点匹配,则返回节点
if (node==null || node.data==data)
return node;

// 节点数据大于要搜索的数据,则继续搜索左边节点
if (node.data > data)
return search(node.left, data);

// 如果节点数据小于要搜素的数据,则继续搜索右边节点
return search(node.right, data);
}

AVL的插入

AVL的插入和BST的插入是一样的,不过插入之后有可能会导致树不再平衡,所以我们需要做一个再平衡的步骤。

看一个直观的动画:

插入的逻辑是这样的:

  1. 从根节点出发,比较节点数据和要插入的数据

  2. 如果要插入的数据小于节点数据,则递归左子树插入

  3. 如果要插入的数据大于节点数据,则递归右子树插入

  4. 如果根节点为空,则插入当前数据作为根节点

插入数据之后,我们需要做再平衡。

再平衡的逻辑是这样的:

  1. 从插入的节点向上找出第一个未平衡的节点,这个节点我们记为z

  2. 对z为根节点的子树进行旋转,得到一个平衡树。

根据以z为根节点的树的不同,我们有四种旋转方式:

  • left-left:

如果是left left的树,那么进行一次右旋就够了。

右旋的步骤是怎么样的呢?

  1. 找到z节点的左节点y

  2. 将y作为旋转后的根节点

  3. z作为y的右节点

  4. y的右节点作为z的左节点

  5. 更新z的高度

相应的代码如下:

Node rightRotate(Node node) {
Node x = node.left;
Node y = x.right;

// 右旋
x.right = node;
node.left = y;

// 更新node和x的高度
node.height = max(height(node.left), height(node.right)) + 1;
x.height = max(height(x.left), height(x.right)) + 1;

// 返回新的x节点
return x;
}

  • right-right:

如果是right-right形式的树,需要经过一次左旋:

左旋的步骤正好和右旋的步骤相反:

  1. 找到z节点的右节点y

  2. 将y作为旋转后的根节点

  3. z作为y的左节点

  4. y的左节点作为z的右节点

  5. 更新z的高度

相应的代码如下:

//左旋
Node leftRotate(Node node) {
Node x = node.right;
Node y = x.left;

//左旋操作
x.left = node;
node.right = y;

// 更新node和x的高度
node.height = max(height(node.left), height(node.right)) + 1;
x.height = max(height(x.left), height(x.right)) + 1;

// 返回新的x节点
return x;
}

  • left-right:

如果是left right的情况,需要先进行一次左旋将树转变成left left格式,然后再进行一次右旋,得到最终结果。

  • right-left:

如果是right left格式,需要先进行一次右旋,转换成为right right格式,然后再进行一次左旋即可。

现在问题来了,怎么判断一个树到底是哪种格式呢?我们可以通过获取平衡因子和新插入的数据比较来判断:

  1. 如果balance>1,那么我们在Left Left或者left Right的情况,这时候我们需要比较新插入的data和node.left.data的大小

    如果data < node.left.data,表示是left left的情况,只需要一次右旋即可

    如果data > node.left.data,表示是left right的情况,则需要将node.left进行一次左旋,然后将node进行一次右旋

  2. 如果balance<-1,那么我们在Right Right或者Right Left的情况,这时候我们需要比较新插入的data和node.right.data的大小
    如果data > node.right.data,表示是Right Right的情况,只需要一次左旋即可

    如果data < node.left.data,表示是Right left的情况,则需要将node.right进行一次右旋,然后将node进行一次左旋

插入节点的最终代码如下:

//插入新节点,从root开始
public void insert(int data){
root=insert(root, data);
}

//遍历插入新节点
Node insert(Node node, int data) {

//先按照普通的BST方法插入节点
if (node == null)
return (new Node(data));

if (data < node.data)
node.left = insert(node.left, data);
else if (data > node.data)
node.right = insert(node.right, data);
else
return node;

//更新节点的高度
node.height = max(height(node.left), height(node.right)) + 1;

//判断节点是否平衡
int balance = getBalance(node);

//节点不平衡有四种情况
//1.如果balance>1,那么我们在Left Left或者left Right的情况,这时候我们需要比较新插入的data和node.left.data的大小
//如果data < node.left.data,表示是left left的情况,只需要一次右旋即可
//如果data > node.left.data,表示是left right的情况,则需要将node.left进行一次左旋,然后将node进行一次右旋
//2.如果balance<-1,那么我们在Right Right或者Right Left的情况,这时候我们需要比较新插入的data和node.right.data的大小
//如果data > node.right.data,表示是Right Right的情况,只需要一次左旋即可
//如果data < node.left.data,表示是Right left的情况,则需要将node.right进行一次右旋,然后将node进行一次左旋

//left left
if (balance > 1 && data < node.left.data)
return rightRotate(node);

// Right Right
if (balance < -1 && data > node.right.data)
return leftRotate(node);

// Left Right
if (balance > 1 && data > node.left.data) {
node.left = leftRotate(node.left);
return rightRotate(node);
}

// Right Left
if (balance < -1 && data < node.right.data) {
node.right = rightRotate(node.right);
return leftRotate(node);
}

//返回插入后的节点
return node;
}

AVL的删除

AVL的删除和插入类似。

首先按照普通的BST删除,然后也需要做再平衡。

看一个直观的动画:

删除之后,节点再平衡也有4种情况:

  1. 如果balance>1,那么我们在Left Left或者left Right的情况,这时候我们需要比较左节点的平衡因子

    如果左节点的平衡因子>=0,表示是left left的情况,只需要一次右旋即可

    如果左节点的平衡因<0,表示是left right的情况,则需要将node.left进行一次左旋,然后将node进行一次右旋

  2. 如果balance<-1,那么我们在Right Right或者Right Left的情况,这时候我们需要比较右节点的平衡因子

    如果右节点的平衡因子<=0,表示是Right Right的情况,只需要一次左旋即可

    如果右节点的平衡因子>0,表示是Right left的情况,则需要将node.right进行一次右旋,然后将node进行一次左旋

相应的代码如下:

Node delete(Node node, int data)
{
//Step 1. 普通BST节点删除
// 如果节点为空,直接返回
if (node == null)
return node;

// 如果值小于当前节点,那么继续左节点删除
if (data < node.data)
node.left = delete(node.left, data);

//如果值大于当前节点,那么继续右节点删除
else if (data > node.data)
node.right = delete(node.right, data);

//如果值相同,那么就是要删除的节点
else
{
// 如果是单边节点的情况
if ((node.left == null) || (node.right == null))
{
Node temp = null;
if (temp == node.left)
temp = node.right;
else
temp = node.left;

//没有子节点的情况
if (temp == null)
{
node = null;
}
else // 单边节点的情况
node = temp;
}
else
{ //非单边节点的情况
//拿到右侧节点的最小值
Node temp = minValueNode(node.right);
//将最小值作为当前的节点值
node.data = temp.data;
// 将该值从右侧节点删除
node.right = delete(node.right, temp.data);
}
}

// 如果节点为空,直接返回
if (node == null)
return node;

// step 2: 更新当前节点的高度
node.height = max(height(node.left), height(node.right)) + 1;

// step 3: 获取当前节点的平衡因子
int balance = getBalance(node);

// 如果节点不再平衡,那么有4种情况
//1.如果balance>1,那么我们在Left Left或者left Right的情况,这时候我们需要比较左节点的平衡因子
//如果左节点的平衡因子>=0,表示是left left的情况,只需要一次右旋即可
//如果左节点的平衡因<0,表示是left right的情况,则需要将node.left进行一次左旋,然后将node进行一次右旋
//2.如果balance<-1,那么我们在Right Right或者Right Left的情况,这时候我们需要比较右节点的平衡因子
//如果右节点的平衡因子<=0,表示是Right Right的情况,只需要一次左旋即可
//如果右节点的平衡因子>0,表示是Right left的情况,则需要将node.right进行一次右旋,然后将node进行一次左旋
// Left Left Case
if (balance > 1 && getBalance(node.left) >= 0)
return rightRotate(node);

// Left Right Case
if (balance > 1 && getBalance(node.left) < 0)
{
node.left = leftRotate(node.left);
return rightRotate(node);
}

// Right Right Case
if (balance < -1 && getBalance(node.right) <= 0)
return leftRotate(node);

// Right Left Case
if (balance < -1 && getBalance(node.right) > 0)
{
node.right = rightRotate(node.right);
return leftRotate(node);
}
return node;
}

本文的代码地址:

learn-algorithm

本文收录于 http://www.flydean.com/11-algorithm-avl-tree/

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
警告普京有兵变的俄军卢宁被捕!想做普里戈金第二?

警告普京有兵变的俄军卢宁被捕!想做普里戈金第二?

项鹏飞
2026-06-28 21:41:22
A股:收盘后,传来一个消息,明天牛市,周二行情或有大变盘?

A股:收盘后,传来一个消息,明天牛市,周二行情或有大变盘?

夜深爱杂谈
2026-06-29 21:13:03
陈志只是小钻风,他才是山大王?太子集团“幕后大佬”被抓

陈志只是小钻风,他才是山大王?太子集团“幕后大佬”被抓

北向财经
2026-06-28 20:34:59
中国已经成为全球第一个集体拒接电话的国家

中国已经成为全球第一个集体拒接电话的国家

黯泉
2026-06-26 10:44:35
三观不正的人有多么可怕,看网友讲述心底一阵恶寒。

三观不正的人有多么可怕,看网友讲述心底一阵恶寒。

侃神评故事
2026-06-23 16:15:05
世界杯淘汰赛最弱半区!卫冕冠军直通八强 日本地狱级夺冠路径曝

世界杯淘汰赛最弱半区!卫冕冠军直通八强 日本地狱级夺冠路径曝

郝小小看体育
2026-06-29 04:12:20
再这么搞下去,三桶油的内退潮或将无可避免!

再这么搞下去,三桶油的内退潮或将无可避免!

小蜜情感说
2026-06-28 14:23:28
2米26徐昕,做出了1个重要的决定!

2米26徐昕,做出了1个重要的决定!

体育哲人
2026-06-29 19:31:23
一个残酷真相:再过三年,再大牌的明星,也可能彻底无戏可拍

一个残酷真相:再过三年,再大牌的明星,也可能彻底无戏可拍

一盅情怀
2026-06-23 13:34:28
印度女性为什么不嫁到中国看网友讲述这是真不能娶 实在忍受不了

印度女性为什么不嫁到中国看网友讲述这是真不能娶 实在忍受不了

侃神评故事
2026-06-29 12:02:09
民警唐万羴牺牲,年仅22岁:拦截违法逃窜车辆时被撞倒,参加公安工作还未满一年

民警唐万羴牺牲,年仅22岁:拦截违法逃窜车辆时被撞倒,参加公安工作还未满一年

大象新闻
2026-06-29 15:58:28
“带父母旅游,落地第一天妈妈晒脱水进医院”热浪席卷欧洲,旅行变生存挑战

“带父母旅游,落地第一天妈妈晒脱水进医院”热浪席卷欧洲,旅行变生存挑战

上观新闻
2026-06-28 09:36:33
秦海璐变卖房产,清空全部资产,凑出近亿身家,绝境兜底救下刘涛

秦海璐变卖房产,清空全部资产,凑出近亿身家,绝境兜底救下刘涛

秋别离
2026-06-13 15:50:00
全新宝马X5内饰谍照曝光:全景视域桥+倾斜中控屏+副驾屏

全新宝马X5内饰谍照曝光:全景视域桥+倾斜中控屏+副驾屏

IT之家
2026-06-29 18:30:08
1天4个瓜!国外生子、被抓进去、自曝怀双胎、韩红最让人意外

1天4个瓜!国外生子、被抓进去、自曝怀双胎、韩红最让人意外

三石记
2026-06-25 11:54:09
“85 花"大洗牌:2 人出局,杨幂跌出前三,赵丽颖换桌,榜首易主

“85 花"大洗牌:2 人出局,杨幂跌出前三,赵丽颖换桌,榜首易主

小嵩
2026-06-28 03:59:17
整容失败不可怕,一股姨味才尴尬!52岁苏有朋给所有男星提了个醒

整容失败不可怕,一股姨味才尴尬!52岁苏有朋给所有男星提了个醒

胡一舸南游y
2026-06-28 22:43:38
军事 | 于北辰“210%拦截率”成为大陆数学考题……

军事 | 于北辰“210%拦截率”成为大陆数学考题……

新民周刊
2026-06-29 09:17:06
丰田皇冠家族全系焕新:9月3日全球首发

丰田皇冠家族全系焕新:9月3日全球首发

盖世汽车
2026-06-29 11:27:05
美的创始人何享健,坐拥2250亿财富无人继承,三个孩子均为老总

美的创始人何享健,坐拥2250亿财富无人继承,三个孩子均为老总

墨印斋
2026-06-29 09:43:17
2026-06-29 23:20:49
flydean程序那些事
flydean程序那些事
最通俗的解读,最深刻的干货!
356文章数 438关注度
往期回顾 全部

科技要闻

杀疯了!深圳一天出两家200亿具身智能公司

头条要闻

小米SU7加速向左偏减速向右偏 车主维权近1年4S店松口

头条要闻

小米SU7加速向左偏减速向右偏 车主维权近1年4S店松口

体育要闻

他和伊朗队,再次赢得全世界的尊重

娱乐要闻

跟风电影《给阿公的牛肉丸》开机

财经要闻

万达广场批量易主 多位投资人正式入局

汽车要闻

全新宝马iX3长轴版将于成都车展预售 四季度交付

态度原创

亲子
艺术
房产
手机
家居

亲子要闻

兄弟俩的卷尺糖

艺术要闻

他爱上自己的缪斯,把她画成女神,却眼睁睁看着她死去

房产要闻

你敢想?海口房地产投资,暴跌5成!

手机要闻

屏幕反人类,但AI绝了!酷派小方块上手:没法当主力机用

家居要闻

传奇筑 日常诗

无障碍浏览 进入关怀版