实现AVL树

news2024/11/25 20:41:11

封面:实现AVL树.png

王有志,一个分享硬核Java技术的互金摸鱼侠
加入Java人的提桶跑路群:共同富裕的Java人

上一篇我们学习了平衡二分搜索树的理论知识,并学习了AVL树是如何保持二分搜索树的平衡的,今天我们一起来实现AVL树。
Tips

  • AVL树和数据结构:二分搜索树中的二分搜索树非常相似;
  • 分别使用Java和Python实现AVL树,Python版本仅供大家批评。

记录节点高度

首先对二分搜索树的节点进行改造,使它可以记录自身的高度:

public class TreeNode<E extends Comparable<E>> {
	private E element;
	private TreeNode<E> left;
	private TreeNode<E> right;
	private int height;
	public TreeNode(E element) {
		this.element = element;
		this.height = 1;
	}
}

Python版本:

class TreeNode(object):
	def __init__(self, element, left=None, right=None):
		self.element = element
		self.left = left
		self.right = right
		self.height = 1

接着在AVL树中提供辅助函数,用来获取节点的高度,要针对null(None)的情况进行处理

public class AVLTree<E extends Comparable<E>> extends BinarySearchTree<E> {
	private int getHeight(TreeNode<E> node) {
		return node == null ? 0 : node.getHeight();
	}
}

Python版本:

class AVLTree(object):
	def __init__(self):
		self.root = None
	def getHeight(self, node):
		if node is None:
			return 0
		else:
			return node.height

Tips:为了简便Python版本中的element为数字类型。

计算平衡因子

记录高度之后,就可以计算节点的平衡因子了,而AVL树正是根据平衡因子来决定是否通过旋转来维持平衡。
添加获取平衡因子的辅助函数,逻辑非常简单,计算左右子树高度差即可

private int getBalanceFactor(TreeNode<E> node) {
	if(node == null) {
		return 0;
	}
	return getHeight(node.getLeft()) - getHeight(node.getRight());
}

Python版本:

def _getBalanceFactor(self, node):
	if node is None:
		return 0
	return self._getHeight(node.left) - self._getHeight(node.right)

好了,到目前为止,准备工作已经差不多了,接下来我们要开始修改改变树结构的的添加方法。
Tips

  • 可以利用二分搜索树和平衡二分搜索树的特性,添加检查是否为平衡二分搜索树的辅助功能;
  • 删除方法的原理相同,文章中不再展示代码展示,可以查看后文的代码仓库。

修改添加方法

AVL树中节点的高度并不是一成不变的,它会随着结构的变化而变化,当添加或者删除节点后,树的结构都会发生变化。
当结构发生变化时,我们需要维护节点的高度,并计算平衡因子,以决定是否需要进行旋转维持平衡。递归的实现中维护节点的高度非常容易,只需要获取左右子树的最大高度后加1即可,而计算平衡因子可以通过之前的辅助函数完成:

@Override  
public void add(E element) {
	this.root = add(this.root, element);
}

private TreeNode<E> add(TreeNode<E> node, E element) {
	if (node == null) {
		this.size++;
		return new TreeNode<>(element);
	}
	if (element.compareTo(node.getElement()) > 0) {
		node.setRight(add(node.getRight(), element));
	} else {
		node.setLeft(add(node.getLeft(), element));
	}

	// 上面的逻辑和二分搜索树相同
	// 维护当前节点的高度
	node.setHeight(Math.max(getHeight(node.getRight()), getHeight(node.getLeft())) + 1);
	// 计算平衡因子
	int balanceFactor = getBalanceFactor(node);
}

Python版本:

def add(self, element):
	self.root = self._add(self.root, element)
def _add(self, node, element):
	if node is None:
		self.size = self.size + 1
		return TreeNode(element)
	if element > node.elment:
		node.right = self._add(node.right, element)
	else:
		node.left = self._add(node.left, element)

	# 维护当前节点的高度
	node.height = max(self._getHeight(node.right), self._getHeight(node.left)) + 1
	# 计算平衡因子
	balanceFactor = self._getBalanceFactor(node)

旋转维护平衡

当不平衡发生时,即balanceFactor > 1,可能存在多种情况:

  • 当左子节点的平衡因子大于0,可以通过右旋转恢复平衡;
  • 当右子节点的平衡因子大于0,可以通过左旋转恢复平衡。

Tips

  • 如果忘记了树旋转的原理可以回顾下数据结构:平衡二分搜索树;
  • 这里将恢复LL失衡的LL旋转称为右旋转。

单旋(LL旋转)

为了方便理解,我们把数据结构:平衡二分搜索树的LL旋转图示拿过来。
图1:LL旋转的过程.png
来看图翻译代码:

private TreeNode<E> rightRotate(TreeNode<E> node) {

	// 暂存节点B
	TreeNode<E> leftChild = node.getLeft();

	// 暂存节点E,A的左孩子的右孩子
	TreeNode<E> leftChildRightChild = leftChild.getRight();

	// 右旋转
	// 节点A作为节点B的右孩子
	leftChild.setRight(node);

	// 节点E作为节点A的左孩子
	node.setLeft(leftChildRightChild);

	// 更新节点的Height,只有节点A和节点B的高度发生了变化
	node.setHeight(Math.max(getHeight(node.getRight()), getHeight(node.getLeft())+ 1));
	leftChild.setHeight(Math.max(getHeight(leftChild.getRight()), getHeight(leftChild.getLeft())+ 1));


	// 返回节点B,新的根节点
	return leftChild;
}

Python版本:

def _rightRotate(self, node):
	leftChild = node.left
	leftChildRightChild = leftChild.getRight
	leftChild.right = node
	node.left = leftChildRightChild
	node.height = max(self._getHeight(node.right), self._getHeight(node.left)) + 1
	leftChild.height = max(self._getHeight(leftChild.right), self._getHeight(leftChild.left)) + 1
	return leftChild

看图翻译代码是不是简单很多?这也就是我为什么会在和王有志一起学习数据结构和算法中建议大家画数据结构。

双旋(LR旋转)

回顾下上一篇文章,在应对LR失衡的场景中,我们先对R失衡的部分进行RR旋转,使整体称为LL失衡,接着进行LL旋转恢复平衡。那么代码是不是就非常容易了?

node.setLeft(leftRotate(node.getLeft()));
return rightRotate(node);

Python版本:

node.left = self._leftRotate(node.left)
return self._rightRotate(node)

是不是想复杂了?以为我们要写leftRightRotate方法?其实我们结合一下就可以完成看似复杂的LR旋转或者RL旋转。

完善添加方法

回顾头来看add方法,目前我们还剩最后一个疑问,如何判断产生了哪种失衡场景?
我们先来看“左重右轻”的场景,即LL失衡和LR失衡。此时一定有getBalanceFactor(node) > 1,即左边高于右边。那么它们的差别就在于node.getLeft()了。在LL失衡的场景中,一定有getBalanceFactor(node.getLeft()) >= 0,而在LR失衡的场景中,则是getBalanceFactor(node.getLeft()) < 0
至于“右重左轻”的场景,只需要反过来即可。那么我们在添加方法计算balanceFactor后添加如下旋转场景:

// LL失衡
if(balanceFactor > 1 && getBalanceFactor(node.getLeft()) >= 0) {
	return rightRotate(node);  
}

// RR失衡
if(balanceFactor < -1 && getBalanceFactor(node.getRight()) <= 0) {
	return leftRotate(node);  
}

// LR失衡
if(balanceFactor > 1 && getBalanceFactor(node.getLeft()) < 0) {
	node.setLeft(leftRotate(node.getLeft()));
	return rightRotate(node);  
}

// RL失衡
if(balanceFactor < -1 && getBalanceFactor(node.getRight()) > 0) {
	node.setRight(rightRotate(node.getRight()));
	return leftRotate(node);
}

Python版本:

# RR失衡
if balanceFactor > 1 and self._getBalanceFactor(node.left) >= 0:
	return self._rightRotate(node)

# LR失衡
if balanceFactor < -1 and self._getBalanceFactor(node.right) <= 0:
	return self._leftRotate(node)

# LR失衡
if balanceFactor > 1 and self._getBalanceFactor(node.left) < 0:
	node.left = self._leftRotate(node.left)
	return self._rightRotate(node)

# RL失衡
if balanceFactor < -1 and self._getBalanceFactor(node.right) > 0:
	node.right = self._rightRotate(node.right)
	return self._leftRotate(node)

结语

今天的内容还是比较简单的,类似于小学的看图写作,只不过我们这次是将图示的过程翻译成的代码。
大家可以重点理解下各种旋转的过程(实际上还是上一篇的内容),至于叫法,大家就不用太纠结。
下一篇呢,会和大家分享另一棵平衡二分搜索树–大名鼎鼎的红黑树。

练习

  • 实现AVL树的leftRotate方法
  • 实现AVL树的删除方法
  • 比较AVL树二分搜索树在性能上的提升

如果本文对你有帮助的话,还请多多点赞支持。如果文章中出现任何错误,还请批评指正。最后欢迎大家关注分享硬核Java技术的金融摸鱼侠王有志,我们下次再见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1405419.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

操作系统-进程控制(如何实现进程控制 如何实现原子性 相关进程控制原语)

文章目录 什么是进程控制总览如何实现进程控制&#xff1f;如何实现原语的“原子性”&#xff1f;进程控制相关的原语创建原语撤销原语子进程与父进程阻塞与唤醒原语切换原语 小结 什么是进程控制 控制进程的状态变换 总览 如何实现进程控制&#xff1f; 原语实现 假设不是原…

使用DockerFile构建镜像与镜像上传

目录 前言&#xff1a;为什么要使用Dockerfile &#xff1f; DockerFile构建镜像 1、构建基础对象 2、Dockerfile文件结构 3、构建Dockerfile文件镜像 二、镜像上传&#xff08;阿里云&#xff09; 前言&#xff1a;为什么要使用Dockerfile &#xff1f; 首先Dockerfile …

Linux系统Shell脚本 ----- 编程规范和变量详细解读

一、Shell脚本概述 1、什么是Shell Linux系统中运行的一种特殊程序在用户和内核之间充当“翻译官”用户登录Linux系统时&#xff0c;自动加载一个Shell程序Bash是Linux系统中默认使用的Shell程序 2、Shell的作用 Linux系统中的shell是一个特殊的应用程序&#xff0c;它介于操…

Ansys Lumerical|如何将Klayout Cell动态导入Lumerical Multiphysics

附件下载 联系工作人员获取附件 说明 在本例中&#xff0c;演示了如何将KLayout Library Cell动态导入 Lumerical 以执行设计扫描和表征。该功能支持动态导入到Lumerical FDTD、MODE以及Multiphysics的所有工具&#xff0c;包括CHARGE、HEAT、FEEM、MQW、DGTD。本例适用于&am…

【nginx实战】nginx正向代理、反向代理、由反向代理实现的负载均衡、故障转移详解

文章目录 一. 正向代理与反向代理的概念二. Nginx服务器的正向代理服务1. Nginx服务器正向代理服务的配置的3个指令1.1. resolver指令1.2. resolver_timeout指令1.3. proxy_pass指令 2. Nginx服务器正向代理服务的使用 三. Nginx服务器的反向代理服务1. 反向代理的基本指令1.1.…

pytest配置文件pytest.ini

说明&#xff1a; pytest.ini是pytest的全局配置文件&#xff0c;一般放在项目的根目录下是一个固定的文件-pytest.ini可以改变pytest的运行方式&#xff0c;设置配置信息&#xff0c;读取后按照配置的内容去运行 pytest.ini 设置参数 addopts 设置自定义执行参数&#xff0…

IDEA启动项目遇到的异常汇总,包括插件异常,版本依赖异常,启动异常等以及对应的解决办法

该文章旨在记录开发中遇到的一些异常&#xff0c;以供遇到似错误进行参考修改 一、项目在多个环境下切换&#xff0c;有一次启动后编译失败&#xff0c;报异常 背景&#xff1a;项目在不同环境下有对应的分支&#xff0c;切换分支后运行项目&#xff0c;报错如下 错误:Kotlin:…

赠书活动~

关注公众号获得&#xff0c;发送抽奖

PolarDB无感切换特性助力游戏领域高可用实践

❤️作者主页&#xff1a;小虚竹 ❤️作者简介&#xff1a;大家好,我是小虚竹。2022年度博客之星评选TOP 10&#x1f3c6;&#xff0c;Java领域优质创作者&#x1f3c6;&#xff0c;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;掘金年度人气作…

《WebKit 技术内幕》学习之六(2): CSS解释器和样式布局

2 CSS解释器和规则匹配 在了解了CSS的基本概念之后&#xff0c;下面来理解WebKit如何来解释CSS代码并选择相应的规则。通过介绍WebKit的主要设施帮助理解WebKit的内部工作原理和机制。 2.1 样式的WebKit表示类 在DOM树中&#xff0c;CSS样式可以包含在“style”元素中或者使…

依托物联网、互联网,建立云端大数据管理平台,形成“端+云+大数据”的智慧工地

概述&#xff1a; 智慧工地&#xff0c;是将物联网应用到建筑工地中&#xff0c;从施工现场源头抓起&#xff0c;最大程度的收集人员、安全、环境、材料等关键业务数据&#xff0c;依托物联网、互联网&#xff0c;建立云端大数据管理平台&#xff0c;的业务体系和新的管理模式…

Linux下用树莓派DS18B20温度传感器读取温度并上传至服务端

目录 一、DS18B20温度传感器 二、逻辑分析 三、实战操作 1、服务端 2、客户端 3、运行结果 一、DS18B20温度传感器 DS18B20是比较常用到的温度传感器&#xff0c;采用单总线控制。是美国DALLAS半导体公司继DS1820之后最新推出的一种改进型智能温度传感器。关于该温度传感…

Leetcode刷题笔记题解(C++):LCR 153. 二叉树中和为目标值的路径

思路&#xff1a;利用回溯的思想&#xff0c;回溯的退出条件为当前节点为空&#xff0c;是符合路径的判断条件为路径和为目标值且叶子节点包含了&#xff0c;代码如下&#xff1a; /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *…

Elasticsearch 常用信息

简述 本文针对 Elasticsearch&#xff08;简称ES&#xff09;集群6.x版本出现故障时&#xff0c;可通过提供的命令进行排查。 1、集群健康状态 集群健康状态状态说明red不是所有的主要分片都可用。表示该集群中存在不可用的主分片。可以理解为某个或者某几个索引存在主分片丢失…

Vue2:全局事件总线

一、场景描述 之前我们学习了&#xff0c;通过props实现父子组件之间的通信。通过自定义组件&#xff0c;实现了子给父传递数据。 那么&#xff0c;兄弟关系的组件&#xff0c;如何通信了&#xff1f;任意组件间如何通信了&#xff1f; 这个时候&#xff0c;就要学习全局事件总…

测试工程师必看!测试用例设计全解析,让你彻底掌握

测试工程师在入行时&#xff0c;都会接触到一个名词——测试用例&#xff0c;都知道测试用例是干什么用的&#xff0c;提到设计测试用例的方法&#xff0c;大部分测试工程师都会侃侃而谈&#xff1a;等价类法、边界值法、判定表法、正交分解法……这些方法说起来都如数家珍&…

8-Python 工匠:使用装饰器的技巧

Python 工匠&#xff1a;使用装饰器的技巧 前言 这是 “Python 工匠”系列的第 8 篇文章。[查看系列所有文章] 装饰器 (Decorator) 是 Python 里的一种特殊工具&#xff0c;它为我们提供了一种在函数外部修改函数的灵活能力。它有点像一顶画着独一无二 符号的神奇帽子&#x…

仰暮计划|“说是操场,那就是个土坡,我们在那儿上边种种树啊,拔拔草,有的时候还会有同学来喂喂羊啥的,这都是我们的娱乐”

我是1948年农历二月份在河南省许昌市五女店镇的一个乡村里边出生的。从我记事的时候&#xff0c;中华人民共和国就已经成立了。当时是好多年&#xff0c;经历了三大改造呀、生产队呀、大队呀&#xff0c;乱七八糟的很多&#xff0c;估计你们现在这些孩子们啊&#xff0c;都没有…

浪花 - 更新队伍信息

一、接口设计 1. 请求路径&#xff1a;/team/update 2. 请求参数&#xff1a;TeamUpdateRequest 有些数据不允许修改&#xff0c;封装一个请求类&#xff0c;只存放允许修改的参数列表 package com.example.usercenter.model.request;import lombok.Data;import java.io.Se…

9款最新文生图模型汇总!含华为、谷歌、Stability AI等大厂创新模型(附论文和代码)

2023年真是文生图大放异彩的一年&#xff0c;给数字艺术界和创意圈注入了新鲜血液。从起初的基础图像创作跃进到现在的超逼真效果&#xff0c;这些先进的模型彻底变革了我们制作和享受数字作品的途径。 最近&#xff0c;一些大公司比如华为、谷歌、还有Stability AI等人工智能巨…