AVL树的讲解

news2024/12/26 3:37:05

算法拾遗三十八AVL树

      • AVL树
        • AVL树平衡性
        • AVL树加入节点
        • AVL删除节点
        • AVL树代码

AVL树

AVL树具有最严苛的平衡性,(增、删、改、查)时间复杂度为O(logN),AVL树任何一个节点,左树的高度和右树的高度差不超过1(<2)
和SB树,红黑树时间复杂度一样,只是不同的人搞出了不同的平衡性。
AVL树就是一颗搜索二叉树和搜索二叉树的区别主要是做完属于搜索二叉树的调整之后有专属于自己平衡性的补丁。
搜索二叉树加节点,则小于根节点的挂左边,大于根节点的挂右边
搜索二叉树删除节点分情况:
1、当找到了要删除的节点X之后,X既没有左子树也没有右子树,则直接删除
2、找到了要删除的节点X之后,X有左但无右,那么直接让这个删除节点的X的左子树完全替代X
3、如果要删除的节点X,X无左有右,那么直接让右子树替代X
4、如果要删除的X既有左又有右,可以找左树的最右节点(最大节点)或者右树的最左节点(最小节点)代替X

AVL树平衡性

破坏平衡性操作:
LL:
在这里插入图片描述
需要调整为如下:
在这里插入图片描述
LR:
在这里插入图片描述
同理还有RR和RL型违规破坏平衡性。

如下图(LR型违规:只要是LR型违规。则让那个孙节点跑上去保持平衡):
在这里插入图片描述
可知树的不平衡原因为:B的右子树导致的整棵树不平衡,则需要让C来到A节点的位置,那么需要在BC这棵树上对B来一个左旋,得到下图结果:
在这里插入图片描述
然后再对A来一个右旋C就上去了:
在这里插入图片描述
RL型违规:
在这里插入图片描述
则让它的孙子节点顶上来就完事了,先在B上面执行一个右旋,让C顶上来,再在整棵树上执行一个A的左旋那么最后的C就上来了。

有一个细节:
有没有可能既是LL型违规又是LR型违规:
一棵树的左子树对应的左子树高度和这棵树的右子树对应的右子树高度一样所造成的不平衡是LL和LR型违规
在这里插入图片描述
如上图假设平衡二叉树左树高度为7右树高度为6,在某个时间右树删除一个数导致右树高度为5了,B的左树和右树高度都是6,我的失误既来自LL型又来自LR型
在这里插入图片描述
此时一定要按照LL型来调整,直接右旋(总能保证有效)。

如果用LR的方式来调整,则可能不对:有如下图
在这里插入图片描述
A的左树高度是7A的右树高度为5,如果这个例子按照LL型调整:会发现这棵树的高度是异常平衡的
在这里插入图片描述
如果按照LR方式来调整:如果将S的高度调整成4的话
这样调整出来的K,S则不平了
在这里插入图片描述
再来一个不平衡的:
在这里插入图片描述
按照LR方式进行调整,z替代y的位置
在这里插入图片描述
y接受了z的左空子树,y的右边是没有东西的,最后调整成这样:
在这里插入图片描述
综上:
总结:LL型违规只用进行一次右旋,LR型违规则需要进行一次小范围的左旋,再执行整棵树的右旋,RL型违规则需要先进行小范围上的右旋,再进行整棵树的左旋,RR型只需要进行一次左旋,时间复杂度均为O(1)

AVL树加入节点

加入一个节点之后需要依次查询加入的节点中了哪种类型的违规,如果未找到违规则找其对应的父节点,如果父节点未违规则继续找父节点对应的父节点,一直找到根节点未违规为止。
所以AVL树调整不是只对一个节点查是沿途所有节点都需要查(防止旋转一次后其上的节点还需要旋转),整体时间复杂度为O(logN)的调整代价,

如下图加入一个节点X首先看当前X节点是平的,再看X对应的父节点也是平的,最终找到方框标记的节点发现不再平衡了,左树高度为1,右树高度为3,而且是RR型违规
在这里插入图片描述
则需要进行左旋
在这里插入图片描述

AVL删除节点

分为以下情况:
1、X节点既没有左也没有右子树,这种情况只需要从删除节点开始算上面每一个父都要全查一遍。
2、X节点有右无左,直接拿右孩子替换原来的X,然后从右孩子来到的位置往上查询一遍
3、X节点既有左又有右孩子,看如下例子
在这里插入图片描述
如果此处要删掉7,则需要找到7对应右孩子的最小值8去替换7的位置,调整成如下图的样子,此时只需要从9开始查它的父节点依次调整即可,
在这里插入图片描述

AVL树代码

右旋步骤:
在这里插入图片描述

1、当前树的左边去接管左孩子的右
在这里插入图片描述
2、左孩子的右会接管cur
在这里插入图片描述
参照代码:

	//右旋
		private AVLNode<K, V> rightRotate(AVLNode<K, V> cur) {
			//记录左孩子
			AVLNode<K, V> left = cur.l;
			//左孩子的右树挂载当前树的左边
			cur.l = left.r;
			//左孩子的右接管cur
			left.r = cur;
			//高度也得接管(现在左孩子和右孩子的高度最大的那个再加1)
			cur.h = Math.max((cur.l != null ? cur.l.h : 0), (cur.r != null ? cur.r.h : 0)) + 1;
			left.h = Math.max((left.l != null ? left.l.h : 0), (left.r != null ? left.r.h : 0)) + 1;
			//右旋以left做头节点返回
			return left;
		}

再看AVL树add节点:
当前来到cur节点,要加的key是啥要加的value是啥,搜索二叉树潜台词为加入的key都不一样

public class Code_AVLTreeMap {

	public static class AVLNode<K extends Comparable<K>, V> {
		public K k;
		public V v;
		//左孩子及右孩子
		public AVLNode<K, V> l;
		public AVLNode<K, V> r;
		//高度
		public int h;

		public AVLNode(K key, V value) {
			k = key;
			v = value;
			h = 1;
		}
	}

	public static class AVLTreeMap<K extends Comparable<K>, V> {
		//根节点
		private AVLNode<K, V> root;
		//一共加入多少个节点
		private int size;

		public AVLTreeMap() {
			root = null;
			size = 0;
		}

		//右旋
		private AVLNode<K, V> rightRotate(AVLNode<K, V> cur) {
			//记录左孩子
			AVLNode<K, V> left = cur.l;
			//左孩子的右树挂载当前树的左边
			cur.l = left.r;
			//左孩子的右接管cur
			left.r = cur;
			//高度也得接管(现在左孩子和右孩子的高度最大的那个再加1)
			cur.h = Math.max((cur.l != null ? cur.l.h : 0), (cur.r != null ? cur.r.h : 0)) + 1;
			left.h = Math.max((left.l != null ? left.l.h : 0), (left.r != null ? left.r.h : 0)) + 1;
			//右旋以left做头节点返回
			return left;
		}

		//左旋
		private AVLNode<K, V> leftRotate(AVLNode<K, V> cur) {
			AVLNode<K, V> right = cur.r;
			cur.r = right.l;
			right.l = cur;
			cur.h = Math.max((cur.l != null ? cur.l.h : 0), (cur.r != null ? cur.r.h : 0)) + 1;
			right.h = Math.max((right.l != null ? right.l.h : 0), (right.r != null ? right.r.h : 0)) + 1;
			return right;
		}

		//平衡性调整
		private AVLNode<K, V> maintain(AVLNode<K, V> cur) {
			if (cur == null) {
				return null;
			}
			int leftHeight = cur.l != null ? cur.l.h : 0;
			int rightHeight = cur.r != null ? cur.r.h : 0;
			//此时左右树高度差大于1不平衡了
			if (Math.abs(leftHeight - rightHeight) > 1) {
				//左树高还是右树高
				if (leftHeight > rightHeight) {
					//左树高
					int leftLeftHeight = cur.l != null && cur.l.l != null ? cur.l.l.h : 0;
					int leftRightHeight = cur.l != null && cur.l.r != null ? cur.l.r.h : 0;
					//左树的左树高度大于等于右树的右树高度则LL型
					if (leftLeftHeight >= leftRightHeight) {
						//LL型违规
						cur = rightRotate(cur);
					} else {
						//LR型违规
						cur.l = leftRotate(cur.l);
						cur = rightRotate(cur);
					}
				} else {
					int rightLeftHeight = cur.r != null && cur.r.l != null ? cur.r.l.h : 0;
					int rightRightHeight = cur.r != null && cur.r.r != null ? cur.r.r.h : 0;
					if (rightRightHeight >= rightLeftHeight) {
						//RR
						cur = leftRotate(cur);
					} else {
						//RL
						cur.r = rightRotate(cur.r);
						cur = leftRotate(cur);
					}
				}
			}
			return cur;
		}

		private AVLNode<K, V> findLastIndex(K key) {
			AVLNode<K, V> pre = root;
			AVLNode<K, V> cur = root;
			while (cur != null) {
				pre = cur;
				if (key.compareTo(cur.k) == 0) {
					break;
				} else if (key.compareTo(cur.k) < 0) {
					cur = cur.l;
				} else {
					cur = cur.r;
				}
			}
			return pre;
		}

		private AVLNode<K, V> findLastNoSmallIndex(K key) {
			AVLNode<K, V> ans = null;
			AVLNode<K, V> cur = root;
			while (cur != null) {
				if (key.compareTo(cur.k) == 0) {
					ans = cur;
					break;
				} else if (key.compareTo(cur.k) < 0) {
					ans = cur;
					cur = cur.l;
				} else {
					cur = cur.r;
				}
			}
			return ans;
		}

		private AVLNode<K, V> findLastNoBigIndex(K key) {
			AVLNode<K, V> ans = null;
			AVLNode<K, V> cur = root;
			while (cur != null) {
				if (key.compareTo(cur.k) == 0) {
					ans = cur;
					break;
				} else if (key.compareTo(cur.k) < 0) {
					cur = cur.l;
				} else {
					ans = cur;
					cur = cur.r;
				}
			}
			return ans;
		}

		//AVL树加节点
		private AVLNode<K, V> add(AVLNode<K, V> cur, K key, V value) {
			if (cur == null) {
				//如果当前树为null,则新建节点
				return new AVLNode<K, V>(key, value);
			} else {
				//如果key小于当前树的k
				if (key.compareTo(cur.k) < 0) {
					//我去左树上面找,头部调整为当前节点的左树
					//之所以用cur.l = xxx 是因为这条记录挂在左树上是可能换头的
					//需要将返回值由我的头指针的左子树重新指一下接住
					cur.l = add(cur.l, key, value);
				} else {
					//右树上挂
					cur.r = add(cur.r, key, value);
				}
				//我自己的高度调整对
				cur.h = Math.max(cur.l != null ? cur.l.h : 0, cur.r != null ? cur.r.h : 0) + 1;
				//做平衡调整
				return maintain(cur);
			}
		}

		// 在cur这棵树上,删掉key所代表的节点
		// 返回cur这棵树的新头部
		private AVLNode<K, V> delete(AVLNode<K, V> cur, K key) {
			if (key.compareTo(cur.k) > 0) {
				cur.r = delete(cur.r, key);
			} else if (key.compareTo(cur.k) < 0) {
				cur.l = delete(cur.l, key);
			} else {
				if (cur.l == null && cur.r == null) {
					cur = null;
				} else if (cur.l == null && cur.r != null) {
					cur = cur.r;
				} else if (cur.l != null && cur.r == null) {
					cur = cur.l;
				} else {
					AVLNode<K, V> des = cur.r;
					while (des.l != null) {
						des = des.l;
					}
					//调用右树删除整个k,同时调整了平衡
					cur.r = delete(cur.r, des.k);
					des.l = cur.l;
					des.r = cur.r;
					cur = des;
				}
			}
			if (cur != null) {
				cur.h = Math.max(cur.l != null ? cur.l.h : 0, cur.r != null ? cur.r.h : 0) + 1;
			}
			return maintain(cur);
		}

		public int size() {
			return size;
		}

		public boolean containsKey(K key) {
			if (key == null) {
				return false;
			}
			AVLNode<K, V> lastNode = findLastIndex(key);
			return lastNode != null && key.compareTo(lastNode.k) == 0 ? true : false;
		}

		public void put(K key, V value) {
			if (key == null) {
				return;
			}
			AVLNode<K, V> lastNode = findLastIndex(key);
			if (lastNode != null && key.compareTo(lastNode.k) == 0) {
				lastNode.v = value;
			} else {
				size++;
				root = add(root, key, value);
			}
		}

		public void remove(K key) {
			if (key == null) {
				return;
			}
			if (containsKey(key)) {
				size--;
				root = delete(root, key);
			}
		}

		public V get(K key) {
			if (key == null) {
				return null;
			}
			AVLNode<K, V> lastNode = findLastIndex(key);
			if (lastNode != null && key.compareTo(lastNode.k) == 0) {
				return lastNode.v;
			}
			return null;
		}

		public K firstKey() {
			if (root == null) {
				return null;
			}
			AVLNode<K, V> cur = root;
			while (cur.l != null) {
				cur = cur.l;
			}
			return cur.k;
		}

		public K lastKey() {
			if (root == null) {
				return null;
			}
			AVLNode<K, V> cur = root;
			while (cur.r != null) {
				cur = cur.r;
			}
			return cur.k;
		}

		public K floorKey(K key) {
			if (key == null) {
				return null;
			}
			AVLNode<K, V> lastNoBigNode = findLastNoBigIndex(key);
			return lastNoBigNode == null ? null : lastNoBigNode.k;
		}

		public K ceilingKey(K key) {
			if (key == null) {
				return null;
			}
			AVLNode<K, V> lastNoSmallNode = findLastNoSmallIndex(key);
			return lastNoSmallNode == null ? null : lastNoSmallNode.k;
		}

	}

}

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

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

相关文章

为什么美元美债没有出现死亡螺旋?

号外&#xff1a;刘教链最新文章&#xff0c;欢迎点击阅读&#xff1a; 公众号「刘教链内参」8.18发表&#xff1a;《内参&#xff1a;SEC批准ETF将推高大饼至15-18w$ &#xff1f;》。 公众号「刘教链Pro」8.18发表&#xff1a;《大饼插爆两万五》。 * * * * * * 如果我们把美…

Linux系统基础服务启动的方法

服务&#xff0c;其实就是运行在操作系统后台的一个或者多个应用程序&#xff0c;为计算机系统或用户提供某项特定的服务。Linux系统运行的绝大多数服务都是需要安装才有的&#xff0c;例如FTP服务、httpd服务、MySQL、redis、Zookeeper、rabbitmq、vsftpd等等&#xff0c;那么…

面试题 ①

1、请讲一下常见的SQL优化方法&#xff08;至少10条&#xff09; 1.尽量避免使用子查询 虽然在 mysql5.6 版本之后对 select 的子查询用 join关联方式 做了优化&#xff0c;但是update/delete子查询依然先查外表再查内表&#xff0c;当外表过大时查询速度会很慢&#xff1b;因此…

浙大数据结构第八周之08-图7 公路村村通

题目详情&#xff1a; 现有村落间道路的统计数据表中&#xff0c;列出了有可能建设成标准公路的若干条道路的成本&#xff0c;求使每个村落都有公路连通所需要的最低成本。 输入格式: 输入数据包括城镇数目正整数N&#xff08;≤1000&#xff09;和候选道路数目M&#xff08…

Linux 应急响应命令总结【持续更新】

系统基本信息 CPU 信息 CPU 信息&#xff1a; lscpu操作系统信息 操作系统信息&#xff1a; uname -a操作系统信息&#xff1a; cat /proc/version模块信息 模块信息&#xff1a; lsmod账户信息 系统所有账户 系统所有账户&#xff1a; cat /etc/passwd超级权限账户…

【leetcode 力扣刷题】快乐数/可被k整除的最小整数(可能存在无限循环的技巧题)

可能存在无限循环的技巧题 202. 快乐数数学分析 1015. 可被k整除的最小整数数学分析 202. 快乐数 题目链接&#xff1a;202. 快乐数 题目内容&#xff1a; 理解题意&#xff0c;快乐数就是重复每位数的平方之和得到的新数的过程&#xff0c;最终这个数能变成1。变成1以后&…

STM32 串口复习

按数据通信方式分类&#xff1a; 串行通信&#xff1a;数据逐位按顺序依次传输。传输速率较低&#xff0c;抗干扰能力较强&#xff0c;通信距离较长&#xff0c;I/O资源占用较少&#xff0c;成本较低。并行通信&#xff1a;数据各位通过多条线同时传输。 按数据传输方向分类&…

04_Redis与mysql数据双写一致性案例

04——redis与mysql数据双写一致性 一、canal 是什么 canal[ka’nel,中文翻译为水道/管道/沟渠/运河&#xff0c;主要用途是用于MySQL数据库增量日志数据的订阅、消费和解析&#xff0c;是阿里巴巴开发并开源的,采用Java语言开发&#xff1b; 历史背景是早期阿里巴巴因为杭州和…

你知道CSGO转区内购吗?了解下内购系统!

哈喽&#xff0c;大家好&#xff0c;我是童话姐姐&#xff0c;这两天群里很多人都在问关于内购的事情&#xff0c;今天就专门给大家讲一下关于内购的一些情况吧。 1、首先什么是转区内购? 顾名思义&#xff0c;内购就是在游戏内部的一个购买行为&#xff0c;csgo内购自然就是…

MATLAB打开excel读取写入操作例程

本文使用素材含代码测试用例等 MATLAB读写excel文件历程含&#xff0c;内含有测试代码资源-CSDN文库 打开文件 使用uigetfile函数过滤非xlsx文件&#xff0c;找到需要读取的文件&#xff0c;首先判断文件是否存在&#xff0c;如果文件不存在&#xff0c;程序直接返回&#x…

all in one之安装ubuntu-server系统(第二章)

pve安装ubuntu-server系统 安装ubuntu-server系统 参考链接 在pve环境下安装ubuntu服务器版本&#xff0c;安装过程如下&#xff1a; ubuntu官方链接&#xff08;不推荐&#xff0c;下载慢&#xff09; 中科大开源库 阿里开源镜像库 兰州大学开源镜像站 … 自行选择进行下载&…

韦东山-电子量产工具项目:页面系统

代码结构 所有代码都已通过测试跑通&#xff0c;其中代码结构如下&#xff1a; 一、include文件夹 1.1 common.h #ifndef _COMMON_H #define _COMMON_Htypedef struct Region {int iLeftUpX; //区域左上方的坐标int iLeftUpY; //区域左下方的坐标int iWidth; //区域宽度…

Nodejs沙箱逃逸--总结

一、沙箱逃逸概念 JavaScript和Nodejs之间有什么区别&#xff1a;JavaScript用在浏览器前端&#xff0c;后来将Chrome中的v8引擎单独拿出来为JavaScript单独开发了一个运行环境&#xff0c;因此JavaScript也可以作为一门后端语言&#xff0c;写在后端&#xff08;服务端&#…

Unity进阶–通过PhotonServer实现人物选择和多人同步–PhotonServer(四)

文章目录 Unity进阶–通过PhotonServer实现人物选择和多人同步–PhotonServer(四)服务端客户端 Unity进阶–通过PhotonServer实现人物选择和多人同步–PhotonServer(四) 服务端 服务端结构如下&#xff1a; UserModel using System; using System.Collections.Generic; usin…

houdini volume trail volume几种模式

1vdb from polygon 先 attribwrangle v 后vdbfrompolygon v 2 volume 3

在 Transformer 之前生成文本 Text generation before transformers

1. 在 Transformer 之前生成文本 重要的是要注意&#xff0c;生成算法并不是新的。先前的语言模型使用了一个叫做循环神经网络或RNN的架构。尽管RNN在其时代很强大&#xff0c;但由于需要大量的计算和内存来很好 地执行生成任务&#xff0c;所以它们的能力受到了限制。让我们看…

好用的消售管理系统十大排行榜

销售管理是企业经营至关重要的一环。市场上有很多销售管理系统&#xff0c;对小企业来说&#xff0c;购买和实施CRM系统昂贵且复杂。这里有一份好用的10大免费销售管理系统榜单&#xff0c;让您既可以节省成本&#xff0c;又享受到高效的销售管理。 Zoho CRM&#xff1a; Zoh…

Tesla Model S 3对比分析拆解图

文章来源&#xff1a;网络 需要特斯拉电驱样件的请&#xff1a;shbinzer &#xff08;拆车邦&#xff09; 5 款电机&#xff0c;其中扁线永磁同步电机最大功率从 202kW 提升至 220kW&#xff0c;最大扭矩从 404Nm提升至 440Nm。 Model S/X→Model 3/Y&#xff1a;双电机版本…

软考A计划-系统集成项目管理工程师-收尾管理

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

2023河南萌新联赛第(五)场:郑州轻工业大学

A.买爱心气球 原题链接 : 登录—专业IT笔试面试备考平台_牛客网 博弈论 : #include <iostream> using namespace std; int t,n,m; string s1 "Alice",s2 "Bob"; int main() {cin>>t;while(t--){cin>>n>>m;if (n % 3 0) {cou…