【数据结构 07】AVL树

news2024/11/25 15:52:20

目录

一、二叉搜索树

二、AVL树

2.1 左单旋

2.2 右单旋

2.3 左右双旋

2.4 右左双旋

三、AVL.h

四、test.cpp


一、二叉搜索树

二叉搜索树,又称二叉排序树(Binary Search Tree),相比于普通二叉树,BST的特性有:

  1. 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  2. 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  3. 它的左右子树也分别为二叉搜索树

二叉搜索树通常在数据插入的时候便会将数据正确排序(不允许插入重复值),这样在进行数据查询的时候,一般情况下时间复杂度是O(\log_2 n),但是如果插入的数据为一个有序或接进有序的序列,即退化成了单支树,这时时间复杂度退化到O(n)

二、AVL树

AVL树也叫平衡二叉搜索树,是二叉搜索树的进化版,设计是原理是弥补二叉搜索树的缺陷:当插入的数据接近于有序数列时,二叉搜索树的性能严重下降。

AVL的节点设计采用三叉链结构(每个节点包含left, right, parent三个节点指针),每个节点中都有平衡因子bf。

AVL树在BST的基础上引入了一个平衡因子bf用于表示左右子树的高度差,并控制其不超过1,若在数据插入时左右子树的高度差超过1了,AVL树会寻找新的节点作为根节点进行调整树的结构,这样AVL树进行数据查询的时间复杂度就稳定在了O(\log_2 n)

AVL的特点是左子树和右子树高度差 < 2,平衡因子bf就是右子树高度 - 左子树高度的差,当bf等于2或-2时,AVL将根据不同情况进行旋转调节,使其始终保持AVL树的特性。

对于一棵AVL树,若它的节点数为n,则它的深度为\log_2 n

2.1 左单旋

若数列 1,2,3 按顺序插入AVL树,则树的结构为图1:

图1 数列{1,2,3}插入AVL树

图1 中,parent节点的平衡因子bf为2,新节点向上调节时的cur节点的平衡因子为1,这种情况需要左单旋:cur节点的左子树(空)变成parent节点的右子树,parent节点变成cur节点的左子树。

图2 左单旋后的节点

2.2 右单旋

右单旋与左单旋类似,也是根据平衡因子情况进行旋转调整。

图1 向AVL树中插入新节点1

图1 新节点1插入,向上调整平衡因子,出现parent->bf == -2, cur->bf == -1,此时需要右单旋:cur节点的右子树变为parent节点的左子树,parent变成cur节点的右子树。

图2 右单旋结果

2.3 左右双旋

左右双旋在设计上可以调用左单旋和右单旋函数,但是需要不同情况讨论旋转后的平衡因子。

图1 新插入节点0

图1 新插入节点0后,parent->bf == -2, cur->bf == 1,此时需要左右双旋,即先以节点-1为父节点进行一次左单旋,再以1为父节点进行一次左单旋:节点0的左子树(空)变成节点-1的左子树,节点-1变成节点0的左子树。

图2 以节点-1位父节点左单旋结果

图2 完成左单旋之后再以1位父节点进行一次右单旋:节点0的右子树(空)变成节点1的左子树,节点1变成节点0的右子树。

图3 完成右单旋,同时完成左右双旋

2.4 右左双旋

图1 插入新节点65

图1 中红色数字就是每个节点平衡因子,值为65的节点是新插入的节点,当其插入之后,所有节点的平衡因子更新,出现了parent平衡因子为2,cur平衡因子为1,此时需要进行旋转调节。

图2 观察平衡因子

图2 观察平衡因子,决定旋转策略为右左双旋,即先进行一次右单旋,再进行一次左单旋。右单旋是以节点80为父节点进行,即节点60的右子树变成节点80的左子树,节点80变成节点60的右子树。

图3 右单旋结果

图3 右单旋结束,接下来再一次进行以节点40为父节点的左单旋,即节点60的左子树变成节点40的右子树,节点40变成节点60的左子树。

图4 右左双旋旋转最终结果

三、AVL.h

#define _CRT_SECURE_NO_WARNINGS 1

#pragma once
#include <iostream>

template<class K, class V>
struct AVLTreeNode
{
	std::pair<K, V> kv;
	AVLTreeNode* parent;
	AVLTreeNode* left;
	AVLTreeNode* right;
	int bf;

	AVLTreeNode(const std::pair<K, V> x)
		: kv(x), parent(nullptr), left(nullptr), right(nullptr), bf(0)
	{}
};

template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	bool Insert(const std::pair<K, V>& x)
	{
		if (_root == nullptr)
		{
			_root = new Node(x);
			return true;
		}

		// 通过二叉搜索树找到需要插入节点的位置
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			parent = cur;
			if (cur->kv.first < x.first)
				cur = cur->right;
			else if (cur->kv.first > x.first)
				cur = cur->left;
			else
				return false;
		}

		cur = new Node(x);
		cur->parent = parent;
		if (parent->kv.first > x.first)
			parent->left = cur;
		else
			parent->right = cur;

		// 更新平衡因子
		while (parent)
		{
			if (parent->left == cur)
				--parent->bf;
			else
				++parent->bf;

			if (parent->bf == 0)
			{
				return true;
			}
			else if (parent->bf == 1 || parent->bf == -1)
			{
				// 继续向上更新
				cur = parent;
				parent = parent->parent;
			}
			else if (parent->bf == 2 || parent->bf == -2)
			{
				if (parent->bf == 2 && cur->bf == 1)
					_RotateLeft(parent);
				else if (parent->bf == -2 && cur->bf == -1)
					_RotateRight(parent);
				else if (parent->bf == -2 && cur->bf == 1)
					_RotateLeftRight(parent);
				else if (parent->bf == 2 && cur->bf == -1)
					_RotateRightLeft(parent);

				break;
			}
		}
	}

	void InOrder()
	{
		_InOrder(_root);
		std::cout << std::endl;
	}

	bool IsBalance()
	{
		return _IsBalance(_root);
	}

private:
	void _RotateLeft(Node* parent)
	{
		Node* subR = parent->right;
		Node* subRL = subR->left;

		parent->right = subRL;
		if (subRL != nullptr)
			subRL->parent = parent;

		subR->left = parent;
		Node* ppNode = parent->parent;
		parent->parent = subR;
		if (ppNode == nullptr)
		{
			_root = subR;
			_root->parent = nullptr;
		}
		else
		{
			if (ppNode->left == parent)
				ppNode->left = subR;
			else
				ppNode->right = subR;
			subR->parent = ppNode;
		}

		parent->bf = subR->bf = 0;
	}

	void _RotateRight(Node* parent)
	{
		Node* subL = parent->left;
		Node* subLR = subL->right;

		parent->left = subLR;
		if (subLR != nullptr)
			subLR->parent = parent;

		subL->right = parent;
		Node* ppNode = parent->parent;
		parent->parent = subL;
		if (ppNode == nullptr)
		{
			_root = subL;
			_root->parent = nullptr;
		}
		else
		{
			if (ppNode->left == parent)
				ppNode->left = subL;
			else
				ppNode->right = subL;
			subL->parent = ppNode;
		}

		parent->bf = subL->bf = 0;
	}

	void _RotateLeftRight(Node* parent)
	{
		Node* subL = parent->left;
		Node* subLR = subL->right;
		int bf = subLR->bf;

		_RotateLeft(subL);
		_RotateRight(parent);

		if (bf == 0)
		{
			subLR->bf = 0;
			subL->bf = 0;
			parent->bf = 0;
		}
		else if (bf == -1)
		{
			subLR->bf = 0;
			subL->bf = 0;
			parent->bf = 1;
		}
		else if (bf == 1)
		{
			subLR->bf = 0;
			subL->bf = -1;
			parent->bf = 0;
		}
	}

	void _RotateRightLeft(Node* parent)
	{
		Node* subR = parent->right;
		Node* subRL = subR->left;
		int bf = subRL->bf;

		_RotateRight(subR);
		_RotateLeft(parent);

		if (bf == 0)
		{
			subRL->bf = 0;
			subR->bf = 0;
			parent->bf = 0;
		}
		else if (bf == -1)
		{
			subRL->bf = 0;
			subR->bf = 1;
			parent->bf = 0;
		}
		else if (bf == 1)
		{
			subRL->bf = 0;
			subR->bf = 0;
			parent->bf = -1;
		}
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->left);
		std::cout << "<" << root->kv.first << "," << root->kv.second << ">" << " ";
		_InOrder(root->right);
	}

	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int leftHeight = _Height(root->left);
		int rightHeight = _Height(root->right);

		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
			return true;

		int leftHeight = _Height(root->left);
		int rightHeight = _Height(root->right);

		if (rightHeight - leftHeight != root->bf)
		{
			std::cout << "平衡因子异常" << std::endl;
			return false;
		}

		return abs(rightHeight - leftHeight) < 2
			&& _IsBalance(root->left)
			&& _IsBalance(root->right);
	}

private:
	Node* _root = nullptr;
};

四、test.cpp

#define _CRT_SECURE_NO_WARNINGS 1

#include "AVL.h"

void Test1()
{
	int a[] = { 2, 4, 5, 8, 10, 1, 3, 5, 7, 9, 100, 200, -100, 0 };
	AVLTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(std::make_pair(e, e * 2));
	}
	t.InOrder();

	std::cout << t.IsBalance() << std::endl << std::endl;
}

void Test2()
{
	int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	AVLTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(std::make_pair(e, e * 2));
	}
	t.InOrder();

	std::cout << t.IsBalance() << std::endl << std::endl;
}

void Test3()
{
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	AVLTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(std::make_pair(e, e * 2));
	}
	t.InOrder();

	std::cout << t.IsBalance() << std::endl << std::endl;
}

int main()
{
	Test1();
	Test2();
	Test3();

	return 0;
}

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

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

相关文章

springboot完成一个线上图片存放地址+实现前后端上传图片+回显

1.路径 注意路径 2.代码&#xff1a;&#xff08;那个imagePath没什么用&#xff0c;懒的删了&#xff09;&#xff0c;注意你的本地文件夹要有图片&#xff0c;才可以在线上地址中打开查看 package com.xxx.common.config;import org.springframework.beans.factory.annotat…

一站式在线协作开源办公软件ONLYOFFICE,协作更安全更便捷

1、ONLYOFFICE是什么&#xff1f; ONLYOFFICE是一款功能强大的在线协作办公软件&#xff0c;可以创建编辑Word文档、Excel电子表格&#xff0c;PowerPoint&#xff08;PPT&#xff09;演示文稿、Forms表单等多种文件。ONLYOFFICE支持多个平台&#xff0c;无论使用的是 Windows、…

vue3.0中从proxy中取值

使用vue3.0时&#xff0c;因为底层是使用proxy进行代理的所以当我们打印一些值的时候是proxy代理之后的&#xff0c;是Proxy 对象&#xff0c;Proxy对象里边的[[Target]]才是真实的对象。也是我们需要的 第一种获取target值的方式&#xff1a; import { toRaw } from vue; le…

【Jenkins】配置及使用|参数化|邮件|源码|报表|乱码

目录 一、Jenkins 二、Jenkins环境搭建 1、下载所需的软件包 2、部署步骤 3、其他 三、Jenkins全局设置 &#xff08;一&#xff09;Manage Jenkins——Tools系统管理->全局工具配置分别配置JDK、Maven、Allure、Git&#xff0c;可以配置路径或者直接选择版本安装 1…

[C++]继承(续)

一、基类和派生类对象赋值转换 在public继承时&#xff0c;父类和子类是一个“is - a”的关系。 子类对象赋值给父类对象/父类指针/父类引用&#xff0c;我们认为是天然的&#xff0c;中间不产生临时对象&#xff0c;也叫作父子类赋值兼容规则&#xff08;切割/切片&#xff…

【C/C++ 03】堆排序

堆排序是选择排序算法的进阶&#xff0c;也就是通过二叉树节点存储数组&#xff0c;并通过root节点存储最值与二叉树最后一个节点进行交换完成排序&#xff0c;降低了时间复杂度。在大数据时代&#xff0c;堆排序常用于处理Top-K问题。 排序对象&#xff1a;数组时间复杂度&am…

Maya------创建多边形工具

配合导入图像使用 Tab键可以删除一个点&#xff01; 模型不能超过4边面&#xff01;多切割工具进行连接&#xff01; 15.maya常用命令5.创建多边形工具 反转 双显 挤出_哔哩哔哩_bilibili

政安晨的机器学习笔记——演绎一个TensorFlow官方的Keras示例(对服装图像进行分类,很全面)

导语 Keras是一个高级API接口&#xff0c;用于构建和训练神经网络模型。它是TensorFlow的一部分&#xff0c;提供了一种简洁、直观的方式来创建深度学习模型。 Keras的主要特点如下&#xff1a; 简洁易用&#xff1a;Keras提供了一组简单的函数和类&#xff0c;使模型的创建和…

vue 适配大屏 页面 整体缩放

正常应该放在app.vue 里面。我这里因为用到element-ui 弹框无法缩放&#xff0c;所以加在body上面 (function (doc, win) {var docEl doc.documentElement,resizeEvt orientationchange in window ? orientationchange : resize,recalc function () {var clientWidth docE…

力扣hot100 二叉树的层序遍历 队列 广度优先搜索

Problem: 102. 二叉树的层序遍历 文章目录 思路复杂度Code 思路 &#x1f468;‍&#x1f3eb; 路飞 复杂度 时间复杂度: 添加时间复杂度, 示例&#xff1a; O ( n ) O(n) O(n) 空间复杂度: 添加空间复杂度, 示例&#xff1a; O ( n ) O(n) O(n) Code /*** Definition …

CKS1.28【1】kube-bench 修复不安全项

Context 针对 kubeadm 创建的 cluster 运行 CIS 基准测试工具时&#xff0c;发现了多个必须立即解决的问题。 Task 通过配置修复所有问题并重新启动受影响的组件以确保新的设置生效。 修复针对 API 服务器发现的所有以下违规行为&#xff1a; 1.2.7 Ensure that the --authoriz…

Vue3中实现歌词滚动显示效果

目录 &#x1f389;前言 &#x1f389;整体布局 &#x1f389;处理歌词数据 &#x1f389;处理事件 &#x1f389;完整代码 &#x1f389;总结 &#x1f389;前言 在这篇博客中&#xff0c;我将分享如何在 Vue 3 中实现一个简单的歌词滚动效果。我将从歌词数据的处理开始&…

Spring 学习1

1、什么是Spring Spring 是一款主流的 Java EE 轻量级开源框架 &#xff0c;Spring 由“Spring 之父”Rod Johnson 提出并创立&#xff0c;其目的是用于简化 Java 企业级应用的开发难度和开发周期。Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言…

HTML+CSS+JS的3D进度条

<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>HTMLCSSJS的3D进度条</title><style>…

C#,斯特林数(Stirling Number)的算法与源代码

1 斯特林数 在组合数学&#xff0c;斯特林数可指两类数&#xff0c;第一类斯特林数和第二类斯特林数&#xff0c;都是由18世纪数学家James Stirling提出的。它们自18世纪以来一直吸引许多数学家的兴趣&#xff0c;如欧拉、柯西、西尔沃斯特和凯莱等。后来哥本哈根&#xff08;…

JUC并发编程01——进程,线程(详解),并发和并行

目录 1.进程和线程的概念及对比1.进程概述 2.线程3.对比 2.并行与并发1.并发2.并行 3.线程详解3.1.创建和运行线程3.1.1.Thread3.1.2.Runnable结合Thread 创建线程3.1.3.Callable 3.2线程方法APIrun startsleep yieldjoininterrupt打断线程打断 park终止模式 daemon不推荐使用的…

MATLAB中hilb函数用法

MATLAB中的hilb函数用于生成希尔伯特矩阵。 语法为: H hilb(n) 其中: n: 生成的希尔伯特矩阵的阶数 H: 生成的n阶希尔伯特矩阵 希尔伯特矩阵又称为希尔伯特运算矩阵,它是一种测试矩阵,元素H(i,j) 1/(ij-1),i和j表示矩阵的行号和列号。 例如: H hilb(7)会生成一个7阶…

SPI指数计算(Standardized Precipitation Index,标准化降水指数) 附完整MATLAB代码

SPI指数(Standardized Precipitation Index,标准化降水指数)是反映干湿状况的一个指标,主要计算步骤如下: 收集研究区域过去30年或以上时间尺度(一般选取30年)的月降水量资料。 对月降水量资料进行统计分析,拟合出最适合的概率分布函数。常用的有Pearson III 分布、Gamma分布…

【Docker】使用VS创建、运行、打包、部署.net core 6.0 webapi

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是《Docker容器》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对…

【DDD】学习笔记-限界上下文对架构的影响

通信边界对架构的影响 限界上下文的通信边界会对系统的架构产生直接的影响&#xff0c;在此之前&#xff0c;我们需要理清几个和边界有关的概念。如前所述&#xff0c;我提出了限界上下文的通信边界的概念&#xff0c;并将其分为进程内通信与进程间通信两种方式。在 Toby Clem…