平衡二叉树(AVL) 的认识与实现

news2024/10/6 5:58:09

文章目录

  • 1 基本
    • 1.1 概念
    • 1.2 特点
    • 1.3 构建
    • 1.4 调整
      • 1.4.1 RR
        • 1.4.1.1 示例
        • 1.4.1.2 多棵树不平衡
      • 1.4.2 LL
        • 1.4.2.1 示例
      • 1.4.3 LR
        • 1.4.3.1 示例
      • 1.4.4 RL
        • 1.4.4.1 示例
    • 1.5 实现
      • 1.5.1 示例
      • 1.5.2 完善

1 基本

1.1 概念

平衡二叉树是一棵合理的二叉排序树

解释

对于这么一个序列

在这里插入图片描述

如果按照普通的排序思路,5>4>3>2>1,会得到这么一棵树

如果想要查询结点5,则需要比对很多次;

而如果以3为根,则可以得到一棵较为合理的树

这样就搜索结点5只需要3次即可

为了实现较为合理的排序,AVL就因此诞生

1.2 特点

平衡二叉树有个至关重要的特点:

左右子树高度差不超过1

由此减少树的深度,从而减少查找次数

1.3 构建

  • 本质上与构建二叉排序树一致
  • 在构建二叉排序树的过程中,如果发现树不符合左右子树高度差不超过1的特点,需要进行调整

1.4 调整

通过**失衡树的根节点root导致树失衡的结点node**来调整新的树

而根据2个方向来选择调整方法

  • 导致树失衡的结点noderoot的哪一侧
  • noderoot孩子结点的哪一侧

调整方法

  • LL:上面两个方向均在左侧
  • RR:上面两个方向均在右侧
  • RL:上面两个方向一右一左
  • LR:上面两个方向一左一右

1.4.1 RR

RR

  • 取中间节点,使其父节点成为其左子树
  • 如果它有左子树,则先使其父节点成为其左子树,然后使其原有的左子树连接到现有左子树的右子树上
//	代码的实现则是反过来,先连接原有左子树到现有左子树的右子树,再连接到中间节点的右子树
root 1	node/child 3
root->right_child = child->left_child;
child->left_child = root;
1.4.1.1 示例

对于开头提到的序列

在这里插入图片描述

进行正常排序

可见,root1node3

  • noderoot的右侧:R
  • noderoot孩子节点的右侧:R

两个都是R

进行调整

在这里插入图片描述

如果2原先有左子树,调整:

在这里插入图片描述

1.4.1.2 多棵树不平衡

注意:如果遇到多棵树不平衡,则调整最小树

将上面的序列继续排序

在这里插入图片描述

排序

在这里插入图片描述

如图,有两棵树失衡,一棵树最大的树12345,一颗是345,我们只用调整最小的345即可

这棵也是RR,直接调整

在这里插入图片描述

1.4.2 LL

RR完全相反

LL

  • 取中间节点,使其父节点成为其右子树
  • 如果它有右子树,则先使其父节点成为其右子树,然后使其原有的右子树连接到现有右子树的左子树上
//	代码的实现则是反过来,先连接原有左子树到现有左子树的右子树,再连接到中间节点的右子树
root 5	node/child 3
root->left_child = child->right_child;
child->right_child = root;
1.4.2.1 示例

再来一组数据

在这里插入图片描述

正常排序

进行LL排序

在这里插入图片描述

继续

继续排序最小树

在这里插入图片描述

1.4.3 LR

LR:

  • 取最后一个节点,使其作为父节点
  • 将其原有的父节点作为它的左子树,将它原有的父节点的父节点作为它的右子树
  • 如果它有左子树,则先使其父节点成为其左子树,然后使其原有的左子树连接到现有左子树的右子树上
  • 如果它有右子树,则先将它原有的父节点的父节点作为它的右子树,然后将其原有的右子树连接到它原有的父节点的父节点的左子树上

总结

  • RR
  • LL
//	代码的实现则是反过来
root 7	node/child 6
root->left_child = child->right_child;	child-right_>child = root;		//有右子树
root->left_child->right_child = child->left_child;	root->left_child->right_child = child->left_child 	//有左子树
child->right_child = root;
root = child;
1.4.3.1 示例

在这里插入图片描述

进行正常排序

在这里插入图片描述

两棵树失衡

在这里插入图片描述

调整最下面的树

可见,root7node6

  • noderoot的左侧:L
  • noderoot孩子节点的右侧:R

LR

进行调整

在这里插入图片描述

1.4.4 RL

RL:

  • 取最后一个节点,使其作为父节点
  • 将其原有的父节点作为它的右子树,将它原有的父节点的父节点作为它的左子树
  • 如果它有左子树,则先将它原有的父节点的父节点作为它的左子树,然后将其原有的左子树连接到它原有的父节点的父节点的右子树上
  • 如果它有右子树,则先使其父节点成为其右子树,然后使其原有的右子树连接到现有右子树的左子树上,

总结

  • LL
  • RR
//	代码的实现则是反过来
root 1	node/child 6
root->right_child->left_child = child->right_child;	child-right_>child = root->right_child;		//有右子树
root->right_child = child->left_child;	child->left_child = root;		//有左子树
child->left_child = root;
root = child;
1.4.4.1 示例

在这里插入图片描述

进行正常排序

在这里插入图片描述

已经失衡,进行调整

可见,root1node6

  • noderoot的左侧:R
  • noderoot孩子节点的右侧:L

RL

进行调整

在这里插入图片描述

然后继续插入7/10

在这里插入图片描述

1.5 实现

  • 建立平衡二叉树的过程就是建立一棵二叉搜索树的过程
  • 而在建立过程中我们需要对树进行调整,调整需要用到树的高度,因此我们节点的结构体中需要添加一个height字段来标记当前树的高度

1.5.1 示例

只实现二叉排序树,还未使用字段height

#include<iostream>
using namespace std;
//节点
typedef struct TreeNode {
	int data;
	int height;
	struct TreeNode* left_child;
	struct TreeNode* right_child;
}TreeNode;
//进行二叉排序树的构建
void AVLInsert(TreeNode** T, int data)
{
	if (*T == NULL)	//没有节点
	{
		*T = (TreeNode*)malloc(sizeof(TreeNode));	//创建一个
		(*T)->data = data;
		(*T)->height = 0;	//高度设为0
		(*T)->left_child = NULL;
		(*T)->right_child = NULL;
	}
	else if (data < (*T)->data)
	{
		AVLInsert(&(*T)->left_child, data);	//比根节点的data大,插入在左子树
	}
	else if (data > (*T)->data)
	{
		AVLInsert(&(*T)->right_child, data);
	}
}
//前序遍历打印树
void PreOrder(TreeNode* T)
{
	if (T)
	{
		cout << T->data;
		PreOrder(T->left_child);	//递归调用左子树
		PreOrder(T->right_child);	//递归调用右子树
	}
}
int main()
{
	TreeNode* T = NULL;
	int nums[5] = { 1,2,3,4,5 };
	for (int i = 0; i < 5; i++)
	{	//构建树
		AVLInsert(&T, nums[i]);
	}
	PreOrder(T);
	cout << endl;
	return 0;
}

在这里插入图片描述

1.5.2 完善

  • 获取高度(其实我们结构体中有定义height字段可以直接调用,但为了易读我这里创建一个函数得到height
//获取高度高度
int GetHeight(TreeNode* node)
{
	return node ? node->height : 0;	//节点不为空,则返回节点的height值,否则返回0
}
  • 获取最大高度

    //获取最大高度
    int GetMax(int a, int b)
    {
    	return a > b ? a : b;
    }
    
  • 实现RRLL

//RR旋转
void rrRotation(TreeNode* root, TreeNode** node)
{
	TreeNode* tmp = root->right_child;
	root->right_child = tmp->left_child;	//原有左子树连接到现有左子树的右子树上
	tmp->left_child = root;					//使其父节点成为其现有的左子树

	root->height = GetMax(GetHeight(root->left_child), GetHeight(root->right_child)) + 1;
	tmp->height = GetMax(GetHeight(tmp->left_child), GetHeight(tmp->right_child)) + 1;
	*node = tmp;
}

//LL旋转
void llRotation(TreeNode* root, TreeNode** node)
{
	TreeNode* tmp = root->left_child;
	root->left_child = tmp->right_child;	//原有右子树连接到现有右子树的左子树上
	tmp->right_child = root;				//使其父节点成为其现有的右子树

	root->height = GetMax(GetHeight(root->left_child), GetHeight(root->right_child)) + 1;
	tmp->height = GetMax(GetHeight(tmp->left_child), GetHeight(tmp->right_child)) + 1;
	*node = tmp;
}
  • 实现LRRL,就是调用LLRR

    //LR
    void lrTotation(TreeNode** T)
    {
    	rrRotation((*T)->left_child, &(*T)->left_child);
    	llRotation(*T, T);
    }
    
    //RL
    void rlTotation(TreeNode** T)
    {
    	llRotation((*T)->right_child, &(*T)->right_child);
    	rrRotation(*T, T);
    }
    
  • 最后完善构建二叉树

    //进行二叉排序树的构建
    void AVLInsert(TreeNode** T, int data)
    {
    	if (*T == NULL)	//没有节点
    	{
    		*T = (TreeNode*)malloc(sizeof(TreeNode));	//创建一个
    		(*T)->data = data;
    		(*T)->height = 0;	//高度设为0
    		(*T)->left_child = NULL;
    		(*T)->right_child = NULL;
    	}
    	else if (data < (*T)->data)		//比根节点的data小,插入在左子树	:L
    	{
    		AVLInsert(&(*T)->left_child, data);
    		//获取当前节点左右子树的高度
    		int left_height = GetHeight((*T)->left_child);
    		int right_height = GetHeight((*T)->right_child);
    
    		//判断高度差
    		if (left_height - right_height > 1)
    		{
    			if (data < (*T)->left_child->data)//小于父节点左子树的值,说明在左边	:L
    			{
    				//LL
    				llRotation(*T, T);
    			}
    			else// : R
    			{
    				//LR
    				lrTotation(T);
    			}
    		}
    	}
    	else if (data > (*T)->data)//比根节点的data大,插入在右子树			:R
    	{
    		AVLInsert(&(*T)->right_child, data);						
    		//获取当前节点左右子树的高度
    		int left_height = GetHeight((*T)->left_child);
    		int right_height = GetHeight((*T)->right_child);
    
    		//判断高度差
    		if (right_height - left_height > 1)
    		{
    			if (data > (*T)->right_child->data)	//大于父节点右子树的值,说明在右边	:R
    			{
    					//RR
    				rrRotation(*T, T);
    			}
    			else//:L
    			{
    					//RL
    				rlTotation(T);
    			}
    		}
    	}
    	(*T)->height = GetMax(GetHeight((*T)->left_child), GetHeight((*T)->right_child)) + 1;
    }
    
  • main

    int main()
    {
    	TreeNode* T = NULL;
    	int nums1[5] = { 1,2,3,4,5 };
    	int nums2[5] = { 5,4,3,2,1 };
    	int nums3[5] = { 8,7,9,5,6 };
    	int nums4[5] = { 1,8,6,7,10 };
    
    	for (int i = 0; i < 5; i++)
    	{	//构建树
    		AVLInsert(&T, nums4[i]);
    	}
    	PreOrder(T);
    	cout << endl;
    	return 0;
    }
    

    在这里插入图片描述

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

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

相关文章

2023 | github无法访问或速度慢的问题解决方案

github无法访问或速度慢的问题解决方案 前言: 最近经常遇到github无法访问, 或者访问特别慢的问题, 在搜索了一圈解决方案后, 有些不再有效了, 但是其中有几个还特别好用, 总结一下. 首选方案 直接在github.com的域名上加一个fast > githubfast.com, 访问的是与github完全相…

03-RocketMQ高级原理

目录汇总&#xff1a;RocketMQ从入门到精通汇总 上一篇&#xff1a;02-RocketMQ开发模型 前面的部分我们都是为了快速的体验RocketMQ的搭建和使用。这一部分&#xff0c;我们慢下来&#xff0c;总结并学习下RocketMQ底层的一些概念以及原理&#xff0c;为后面的深入学习做准备。…

使用宝塔面板在Linux上搭建网站,并通过内网穿透实现公网访问

文章目录 前言1. 环境安装2. 安装cpolar内网穿透3. 内网穿透4. 固定http地址5. 配置二级子域名6. 创建一个测试页面 前言 宝塔面板作为简单好用的服务器运维管理面板&#xff0c;它支持Linux/Windows系统&#xff0c;我们可用它来一键配置LAMP/LNMP环境、网站、数据库、FTP等&…

4种实现JS深拷贝的方法

浅拷贝与深拷贝 浅拷贝是创建一个新对象&#xff0c;这个对象有着原始对象属性值的拷贝。如果属性是基本类型&#xff0c;拷贝的就是基本类型的值&#xff0c;如果属性是引用类型&#xff0c;拷贝的是内存地址 。 如果不进行深拷贝&#xff0c;其中一个对象改变了对象的值&am…

使用VS2019测试cJson库

1.代码 //cJson_Test.cpp // cJson_Test.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 //#include <iostream>#include <stdio.h> #include "cJSON.h"int main(void) {cJSON* cjson_test NULL;cJSON* cjson_address NULL;cJ…

深入理解强化学习——序列决策(Sequential Decision Making)

分类目录&#xff1a;《深入理解联邦学习》总目录 在本文中我们将介绍序列决策&#xff08;Sequential Decision Making&#xff09;过程中的各个过程。 智能体与环境 强化学习研究的问题是智能体与环境交互的问题&#xff0c;下图左边的智能体一直在与下图右边的环境进行交互…

springboot农机电招平台springboot37

大家好✌&#xff01;我是CZ淡陌。一名专注以理论为基础实战为主的技术博主&#xff0c;将再这里为大家分享优质的实战项目&#xff0c;本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路…

【Debian】报错:su: Authentication failure

项目场景&#xff1a; 今天我重新刷了一个debian系统。 系统版本&#xff1a; # 查看系统版本 lsb_release -a 我的系统版本&#xff1a; No LSB modules are available. Distributor ID&#xff1a;Debian Description: Debian GNU/Linux 12 &#xff08;bookworm&#xff…

使用LLM在KG上进行复杂的逻辑推理10.12

使用LLM在KG上进行复杂的逻辑推理 摘要介绍相关工作 摘要 在知识图谱上进行推理是一项具有挑战性的任务&#xff0c;这需要深度理解实体之间复杂的关系和它们关系的逻辑。而当前的方法通常依赖于学习 几何形状 以将实体嵌入到向量空间中进行逻辑查询操作&#xff0c;但在复杂查…

ChatGLM:向量化构建本地知识库原理

一、概念 1.向量&#xff1a;是有大小和方向的量&#xff0c;可以使用带箭头的线段表示&#xff0c;箭头指向即为向量的方向&#xff0c;线段的长度表示向量的大小。 2.向量化&#xff1a;将语言模型的数据转化为向量。这通常通过嵌入模型&#xff08;embedding models&#…

【算法-动态规划】0-1 背包问题

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…

解锁 OpenAI 密钥的新利器:OpenAI Key 有效性查询工具,支持GPT4

在寻找新的创意或解决问题时&#xff0c;OpenAI 的 GPT 模型是一个强大的工具。但是&#xff0c;一旦您拥有了自己的 OpenAI 密钥&#xff0c;您可能会遇到一个常见的问题 - 如何验证您的密钥是否有效&#xff1f;这个问题可能令人困扰&#xff0c;但不必再担心了。现在&#x…

多功能按键中断

key1 开关实现led1亮灭,key2开关实现蜂鸣器开关,key3开关实现风扇开关 main.c #include "uart.h" #include "key_it.h" #include "led.h" int main() {char c;char *s;uart4_init();//串口初始化all_led_init();key_it_config();fengshan_init…

Day 05 python学习笔记

循环 应用&#xff1a;循环轮播图 最基础、最核心 循环&#xff1a;周而复始&#xff0c;谓之循环 (为了代码尽量不要重复) while循环 while的格式 索引定义 while 表达式&#xff08;只要结果为布尔值即可&#xff09;&#xff1a; 循环体 通过条件的不断变化&#xff0c;从…

1315. 网格 - 卡特兰数

1315. 网格 - AcWing题库 只要是触及上面这条红线的&#xff0c;就以第一次触及的点为起点沿红线反转&#xff0c;终点的位置与红线对称的位置可以看作触及红线的路线的终点。 yx1 横坐标容易得出时m - 1&#xff0c;&#xff08;m n&#xff09; - (m - 1)得出纵坐标n 1 …

JVM面试题:(四)四种引用方式强弱软虚

四种引用方式&#xff1a; 强引用 强引用是平常中使用最多的引用&#xff0c;强引用在程序内存不足&#xff08;OOM&#xff09;的时候也不会被回收&#xff0c;使用 方式&#xff1a; String str new String(“str”); System.out.println(str); 软引用 软引用在程序内存不…

07-网络篇-抓包分析TCP

为了抓包方便一些&#xff0c;我在ubuntu虚拟机运行服务端程序&#xff0c;而在windows运行客户端程序&#xff0c;关于客户端与服务端程序如下。 ##1.程序 客户端&#xff1a; vs_client.cpp #include "stdafx.h" #include <iostream> #include <winsock2…

MapStruct_概念、如何使用、子集和映射、合并、Spring方式、表达式、自定义切面处理

文章目录 ①. 什么是MapStruct&#xff1f;②. 如何使用MapStruct?③. 子集和映射④. 合并映射⑤. Spring依赖注入⑥. 常量、默认值和表达式⑦. 自定义切面处理 ①. 什么是MapStruct&#xff1f; ①. MapStruct是一款基于Java注解的对象属性映射工具,使用的时候我们只要在接口…

SQL 教程||SQL 简介

SQL 简介&#xff08;开个新坑&#xff09; SQL&#xff08;结构化查询语言&#xff09;是用于访问和操作数据库中的数据的标准数据库编程语言。 SQL是关系数据库系统的标准语言。所有关系数据库管理系统(RDMS)&#xff0c;如MySQL、MS Access、Oracle、Sybase、Informix、Po…

第四章——密码学的数学引论

一.数论 1.素数 200以内的素数&#xff1a; 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 算术基本定理&#xff1a; 任何一个不等于0的正整数a都可以写…