【基本数据结构】平衡二叉树

文章目录

  • 前言
  • 平衡二叉树
    • 1 简介
    • 2 旋转
      • 2.1 左旋
      • 2.2 右旋
      • 2.3 何时旋转
    • 3 插入节点
    • 4 删除节点
    • 5 代码
  • 参考资料
  • 写在最后

前言

本系列专注更新基本数据结构,现有以下文章:

【算法与数据结构】数组.

【算法与数据结构】链表.

【算法与数据结构】哈希表.

【算法与数据结构】二叉查找树

【算法与数据结构】平衡二叉树


平衡二叉树

1 简介

平衡二叉树是为了解决二叉查找树中树退化成链这一问题而提出的。平衡二叉树在插入、删除某一节点之后通过左旋或右旋操作保持动态平衡,从而避免节点失衡(最坏的情况是树退化成链)。

二叉树某节点是否失衡由该节点的 失衡因子 来衡量,失衡因子为左子树节点高度与右子节点高度之差。

当二叉树中某节点的左、右子树高度差不超过1,则表示该节点平衡,高度差不超过 1 对应平衡因子的值为 ±10

左旋与右旋,由于插入、删除节点导致二叉树中出现不平衡节点,此时可以通过左旋或右旋操作调整节点位置以达到二叉查找树的动态平衡。平衡二叉树也是一棵二叉查找树,它的查找、插入、删除都与二叉查找树的操作相同,只是在插入、删除新的节点之后需要通过一些旋转操作来保持其平衡性。

2 旋转

2.1 左旋

左旋,顾名思义,指的是向左旋转节点,或者是逆时针旋转节点。在图 2-1 中,由于插入了一个新的节点到平衡二叉树中,根节点 3 平衡因子变为 0-2=-2,说明此时根节点已经失衡了。为了保持这棵二叉查找树的平衡性,需要左旋根节点。

平衡二叉树-左旋.drawio (1)
图 2-1 左旋

实现代码

// 左旋操作
Node* leftRotate(Node* x) {Node* y = x->right;Node* T2 = y->left;y->left = x;x->right = T2;x->height = std::max(height(x->left), height(x->right)) + 1;y->height = std::max(height(y->left), height(y->right)) + 1;return y;
}

上述代码是左旋的具体操作,传入的一个节点指针 x,表示从该节点开始进行左旋,最终返回的是左旋操作后该子树新的根节点。

以图 2-1 为例,传入的节点为 3,首先记录 3 的右子节点 4,记作 y;接着记录 y 的左子节点空节点,记作 T2;接下来将 4 的左链接连到 3 上,3 的右链接连到空节点上。

最后,还需要更新节点 xy 的高度,为后面计算平衡因子做准备。

2.2 右旋

右旋,指的是向右旋转节点,或者是顺时针旋转节点。在图 2-2 中,由于插入了一个新的节点到平衡二叉树中,根节点 9 平衡因子变为 2-0=2,说明此时根节点已经失衡了。为了保持这棵二叉查找树的平衡性,需要右旋根节点。

平衡二叉树-右旋.drawio (1)
图 2-2 右旋

实现代码

// 右旋操作
Node* rightRotate(Node* y) {Node* x = y->left;Node* T2 = x->right;x->right = y;y->left = T2;y->height = std::max(height(y->left), height(y->right)) + 1;x->height = std::max(height(x->left), height(x->right)) + 1;return x;
}

右旋操作与左旋操作类似,具体实现见代码。

2.3 何时旋转

什么时候需要进行旋转操作,当然是失衡的时候。以下列出的是失衡的四种情况,以及为了保持平衡需要做出的旋转决策:

  • LL型,在当前节点的左子节点的左子节点插入新的节点后,当前节点失衡,此时为了保持平衡需要右旋当前节点。
  • RR型,在当前节点的右子节点的右子节点插入新的节点后,当前节点失衡,此时为了保持平衡需要左旋当前节点。
  • LR型,在当前节点的左子节点的右子节点插入新的节点后,当前节点失衡,此时为了保持平衡需要先左旋当前节点的左子节点,再右旋当前节点。
  • RL型,在当前节点的右子节点的左子节点插入新的节点后,当前节点失衡,此时为了保持平衡需要先右旋当前节点的右子节点,再左旋当前节点。

在 LL型 情况中,我们注意到失衡节点的失衡因子为 2,失衡节点的左子节点的失衡因子为 1。类似的,在 RR型 中,失衡节点的失衡因子为 -2,失衡节点的失衡因子为 -1;在 LR型 中,失衡节点的失衡因子为 2,失衡节点的失衡因子为 -1;在 RL型 中,失衡节点的失衡因子为 -2,失衡节点的失衡因子为 1。于是,我们可以根据失衡节点的失衡因子、失衡节点的左子节点的失衡因子、失衡节点的右子节点的失衡因子来判断当前是什么类型,进而做出相应的旋转决策。

平衡二叉树-LL型.drawio
图 2-3 LL型
平衡二叉树-RR型.drawio
图 2-4 RR型
平衡二叉树-LR型.drawio
图 2-5 LR型
平衡二叉树-RL型.drawio
图 2-6 RL型

3 插入节点

插入节点和在二叉查找树中插入节点一样,先进行未命中的查找,将节点插入到合适的位置。然后根据以上四种情况进行旋转以保持平衡二叉树的平衡性。比如在图 2-6 中插入一个新的节点 8,通过二分查找确定节点 8 的位置为节点 6 右子节点。但是插入节点 8 之后,造成根节点 5 的平衡因子失衡了。因为这是 RL型 情况,所以先右旋根节点的右子树,然后再左旋根节点。

需要注意的是,当插入节点导致多个祖先节点失衡,只需要调整距离插入节点最近的失衡节点即可,其他失衡节点会自动平衡。

平衡二叉树-插入-注意事项.drawio (1)
图 3-1 调整最近的失衡节点

实现代码

// 插入节点
Node* insert(Node* node, int key) {if (node == nullptr)return new Node(key);if (key < node->key)node->left = insert(node->left, key);else if (key > node->key)node->right = insert(node->right, key);elsereturn node;node->height = 1 + std::max(height(node->left), height(node->right));int balance = getBalance(node);// LL 型if (balance > 1 && key < node->left->key)return rightRotate(node);// RR 型if (balance < -1 && key > node->right->key)return leftRotate(node);// LR 型if (balance > 1 && key > node->left->key) {node->left = leftRotate(node->left);return rightRotate(node);}// RL 型if (balance < -1 && key < node->right->key) {node->right = rightRotate(node->right);return leftRotate(node);}return node;
}

在上述插入代码中,我们先进行未命中的查找,在递归查找中插入新的节点。接着,更新当前根节点的高度。最后根据当前节点的平衡因子的不同值来确定是哪一个类型,再根据不同的类型进行相应的调整。具体地:

  • 如果当前节点的失衡因子为 2,并且是在当前节点的左子节点的左子节点插入新的节点,则是 LL 型,右旋该节点即可;
  • 如果当前节点的失衡因子为 -2,并且是在当前节点的右子节点的右子节点插入新的节点,则是 RR 型,左旋节点即可;
  • 如果当前节点的失衡因子为 2,并且是在当前节点的左子节点的右子节点插入新的节点,则是 LR 型,先左旋该节点的左子节点,再右旋该节点;
  • 如果当前节点的失衡因子为 -2,并且是在当前节点的右子节点的左子节点插入新的节点,则是 RL 型,先右旋该节点的右子节点,再左旋该节点;

4 删除节点

删除节点操作也需要先进行查找,递归的进行查找。查找到需要删除的节点 root 之后,按照以下情况进行处理:

  • 如果该节点是叶子节点,直接删除该节点;
  • 如果该节点仅有左子节点或者右子节点,则删除对应的子节点;
  • 如果该节点的左右节点都齐全,则用右子节点中最小的节点更新 root,并删除这个右子节点。

接着更新 root 的高度。最后将失衡的节点旋转,保持平衡。依然是根据当前节点与左、右子节点的平衡因子来判断属于哪种旋转情况,然后进行相应的旋转。

实现代码

// 删除节点
Node* deleteNode(Node* root, int key) {if (root == nullptr)return root;if (key < root->key)root->left = deleteNode(root->left, key);else if (key > root->key)root->right = deleteNode(root->right, key);else {if ((root->left == nullptr) || (root->right == nullptr)) {Node* temp = root->left ? root->left : root->right;if (temp == nullptr) {temp = root;root = nullptr;} else*root = *temp;delete temp;} else {Node* temp = minValueNode(root->right);root->key = temp->key;root->right = deleteNode(root->right, temp->key);}}if (root == nullptr)return root;root->height = 1 + std::max(height(root->left), height(root->right));int balance = getBalance(root);if (balance > 1 && getBalance(root->left) >= 0)return rightRotate(root);if (balance > 1 && getBalance(root->left) < 0) {root->left = leftRotate(root->left);return rightRotate(root);}if (balance < -1 && getBalance(root->right) <= 0)return leftRotate(root);if (balance < -1 && getBalance(root->right) > 0) {root->right = rightRotate(root->right);return leftRotate(root);}return root;
}

5 代码

#include <iostream>
#include <algorithm>class AVLTree {
private:struct Node {int key;Node* left;Node* right;int height;Node(int k) : key(k), left(nullptr), right(nullptr), height(1) {}};Node* root;int height(Node* n) {return n == nullptr ? 0 : n->height;}// 求平衡因子int getBalance(Node* n) {return n == nullptr ? 0 : height(n->left) - height(n->right);}// 右旋操作Node* rightRotate(Node* y) {Node* x = y->left;Node* T2 = x->right;x->right = y;y->left = T2;y->height = std::max(height(y->left), height(y->right)) + 1;x->height = std::max(height(x->left), height(x->right)) + 1;return x;}// 左旋操作Node* leftRotate(Node* x) {Node* y = x->right;Node* T2 = y->left;y->left = x;x->right = T2;x->height = std::max(height(x->left), height(x->right)) + 1;y->height = std::max(height(y->left), height(y->right)) + 1;return y;}// 插入节点Node* insert(Node* node, int key) {if (node == nullptr)return new Node(key);if (key < node->key)node->left = insert(node->left, key);else if (key > node->key)node->right = insert(node->right, key);elsereturn node;node->height = 1 + std::max(height(node->left), height(node->right));int balance = getBalance(node);// LL 型if (balance > 1 && key < node->left->key)return rightRotate(node);// RR 型if (balance < -1 && key > node->right->key)return leftRotate(node);// LR 型if (balance > 1 && key > node->left->key) {node->left = leftRotate(node->left);return rightRotate(node);}// RL 型if (balance < -1 && key < node->right->key) {node->right = rightRotate(node->right);return leftRotate(node);}return node;}// 获得最小节点值Node* minValueNode(Node* node) {Node* current = node;while (current->left != nullptr)current = current->left;return current;}// 删除节点Node* deleteNode(Node* root, int key) {if (root == nullptr)return root;if (key < root->key)root->left = deleteNode(root->left, key);else if (key > root->key)root->right = deleteNode(root->right, key);else {if ((root->left == nullptr) || (root->right == nullptr)) {Node* temp = root->left ? root->left : root->right;if (temp == nullptr) {temp = root;root = nullptr;} else*root = *temp;delete temp;} else {Node* temp = minValueNode(root->right);root->key = temp->key;root->right = deleteNode(root->right, temp->key);}}if (root == nullptr)return root;root->height = 1 + std::max(height(root->left), height(root->right));int balance = getBalance(root);if (balance > 1 && getBalance(root->left) >= 0)return rightRotate(root);if (balance > 1 && getBalance(root->left) < 0) {root->left = leftRotate(root->left);return rightRotate(root);}if (balance < -1 && getBalance(root->right) <= 0)return leftRotate(root);if (balance < -1 && getBalance(root->right) > 0) {root->right = rightRotate(root->right);return leftRotate(root);}return root;}// 查找节点Node* search(Node* root, int key) {if (root == nullptr || root->key == key)return root;if (key < root->key)return search(root->left, key);return search(root->right, key);}// 中序遍历void inOrder(Node* root) {if (root != nullptr) {inOrder(root->left);std::cout << root->key << " ";inOrder(root->right);}}public:AVLTree() : root(nullptr) {}// 插入节点的对外接口void insert(int key) {root = insert(root, key);}// 删除节点的对外接口void deleteNode(int key) {root = deleteNode(root, key);}// 搜索节点的对外接口bool search(int key) {return search(root, key) != nullptr;}// 中序遍历的对外接口void inOrder() {inOrder(root);std::cout << std::endl;}
};int main() {AVLTree tree;tree.insert(10);tree.insert(20);tree.insert(30);tree.insert(40);tree.insert(50);tree.insert(25);std::cout << "中序遍历后的AVL树: ";tree.inOrder();tree.deleteNode(10);std::cout << "删除节点10后的AVL树: ";tree.inOrder();int key = 25;if (tree.search(key))std::cout << "找到节点: " << key << std::endl;elsestd::cout << "未找到节点: " << key << std::endl;return 0;
}

参考资料

【视频】平衡二叉树(AVL树)


写在最后

如果您发现文章有任何错误或者对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。

如果大家觉得有些地方需要补充,欢迎评论区交流。

最后,感谢您的阅读,如果有所收获的话可以给我点一个 👍 哦。

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

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

相关文章

【斯坦福因果推断课程全集】1_随机对照试验1

目录 The average treatment effect Difference-in-means estimation IID Sampling and Population Asymptotics Example: The linear model Regression adjustments with a linear model 随机对照试验&#xff08;RCT&#xff09;是统计因果推论的基础。如果有的话&#…

关于FPGA 使用SPI FLASH固化时如何配置固化参数

关于FPGA 使用SPI FLASH固化时如何配置固化参数 EDA工具&#xff1a;Vivado 关于FPGA 使用SPI FLASH固化时如何配置固化参数一、引言二、如何设置固化参数&#xff1a;使用50M的速度 &#xff0c;SPI为X4 &#xff0c;以及bit压缩第一&#xff1a;点open implenment design第二…

安装软件缺少dll文件怎么办,分享多种解决dll问题的方法

在计算机使用过程中&#xff0c;我们经常会遇到安装软件时提示缺少dll文件的问题。这种情况通常会导致软件无法正常运行或启动。为了解决这个问题&#xff0c;我总结了以下五种方法&#xff0c;希望对大家有所帮助。 一&#xff0c;了解DLL文件是什么 动态链接库&#xff08;D…

简单说说我对集成学习算法的一点理解

概要 集成学习&#xff08;Ensemble Learning&#xff09;是一种机器学习技术框架&#xff0c;它通过构建并结合多个学习器&#xff08;也称为个体学习器或基学习器&#xff09;来完成学习任务。 集成学习旨在通过组合多个基学习器的预测结果来提高整体模型的性能。每个基学习…

常见仪表盘指示灯的含义,这次够全了!

汽车是当前主要的交通工具之一&#xff0c;给人们的工作、生活提供了便利。大家在学会开车的同时&#xff0c;也得了解一些基本的汽车常识&#xff0c;可以及时的发现车辆的问题&#xff0c;并作出正确的判断&#xff0c;以此降低车辆的损耗和维修成本。其中最基本的&#xff0…

房产证上加名?手把手教你操作,省钱又省心!

随着《民法典》的实施&#xff0c;房产的权属问题愈发受到重视。夫妻双方及其亲属常希望能在房产证上增添自己的名字&#xff0c;以保障各自的权益。那么&#xff0c;房产证上到底能写几个名字呢&#xff1f;以下是对这一问题的详细解答。 一、房产证命名无固定限制 在购房时&…

民国漫画杂志《时代漫画》第39期.PDF

时代漫画39.PDF: https://url03.ctfile.com/f/1779803-1248636473-6bd732?p9586 (访问密码: 9586) 《时代漫画》的杂志在1934年诞生了&#xff0c;截止1937年6月战争来临被迫停刊共发行了39期。 ps: 资源来源网络!

SpringBoot注解--10--@Bean,对象注入的三种方法

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 Bean一、如何使用方法注解注意Bean 的命名规则&#xff0c;当没有设置 name 属性时&#xff0c;那么 bean 默认的名称就是方法名&#xff0c;当设置了 name 属性之后…

33【Aseprite 作图】树——拆解

1 树叶 画树叶真累啊&#xff0c;可以先画一个轮廓&#xff0c;细节一点点修 2 1 2 &#xff1b;2 2 2 &#xff08;横着横&#xff09;&#xff0c;这样一点点画树叶 填充颜色&#xff0c;用了喷雾工具 2 树干部分 轮廓部分&#xff0c;左边的是3 3 3 &#xff1b;上下都是…

网页音频提取在线工具有哪些 网页音频提取在线工具下载

别再到处去借会员账号啦。教你一招&#xff0c;无视版权和地区限制&#xff0c;直接下载网页中的音频文件。没有复杂的操作步骤&#xff0c;也不用学习任何代码。只要是网页中播放的音频文件&#xff0c;都可以把它下载到本地保存。 一、网页音频提取在线工具有哪些 市面上的…

【数据结构】二叉树:简约和复杂的交织之美

专栏引入&#xff1a; 哈喽大家好&#xff0c;我是野生的编程萌新&#xff0c;首先感谢大家的观看。数据结构的学习者大多有这样的想法&#xff1a;数据结构很重要&#xff0c;一定要学好&#xff0c;但数据结构比较抽象&#xff0c;有些算法理解起来很困难&#xff0c;学的很累…

Transformer中的位置编码PE(position encoding)

Transformer中的位置编码PE(position encoding) 1.提出背景 transformer模型的attention机制并没有包含位置信息&#xff0c;即一句话中词语在不同的位置时在transformer中是没有区别的 2.解决背景 给encoder层和decoder层的输入添加了一个额外的向量Positional Encoding&a…

【专利 超音速】一种光伏检测系统

申请号CN202410053901.0公开号&#xff08;公开&#xff09;CN118032774A申请日2024.01.12申请人&#xff08;公开&#xff09;超音速人工智能科技股份有限公司发明人&#xff08;公开&#xff09;张俊峰(总); 叶长春(总); 许春夏 摘要 本发明公开一种光伏检测系统&#xff0…

iotdb时序库在火电设备锅炉场景下的实践【原创文字,IoTDB社区可进行使用与传播】

一.概述 1.1 说明 本文章主要介绍iotdb数据库在电站锅炉工业场景下&#xff0c;对辅助智能分析与预警的使用介绍。 【原创文字&#xff0c;IoTDB社区可进行使用与传播】 1.2 项目背景 随着人工智能算法在电力领域的发展&#xff0c;以及燃煤锅炉设备精细化调整需求的增加&…

数据结构——经典链表OJ(二)

乐观学习&#xff0c;乐观生活&#xff0c;才能不断前进啊&#xff01;&#xff01;&#xff01; 我的主页&#xff1a;optimistic_chen 我的专栏&#xff1a;c语言 点击主页&#xff1a;optimistic_chen和专栏&#xff1a;c语言&#xff0c; 创作不易&#xff0c;大佬们点赞鼓…

chatgpt之api的调用问题

1.调用api过程中&#xff0c;出现如下报错内容 先写一个测试样例 import openaiopenai.api_key "OPEN_AI_KEY" openai.api_base"OPEN_AI_BASE_URL" # 是否需要base根据自己所在地区和key情况进行completion openai.ChatCompletion.create(model"g…

【intro】GNN中异构图(heterogeneous graph)综述

本篇博客内容是读两篇论文&#xff0c;两篇论文连接如下&#xff1a; Heterogeneous graph neural networks analysis: a survey of techniques, evaluations and applications A Survey on Heterogeneous Graph Embedding: Methods, Techniques, Applications and Sources …

瓦罗兰特国际服 外服游玩教程 瓦罗兰特外服下载注册游玩指南

瓦罗兰特国际服 外服游玩教程 瓦罗兰特外服下载注册游玩指南 瓦罗兰特作为当今游戏圈顶流的一款热门FPS。游戏&#xff0c;作为拳头游戏公司划时代的一款游戏。游戏不仅延续了传统FPS游戏的玩法&#xff0c;还添加许多新玩法&#xff0c;这也是游戏可以吸引大批量玩家的原因之…

基于电导增量MPPT控制算法的光伏发电系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于电导增量MPPT控制算法的光伏发电系统simulink建模与仿真。输出MPPT跟踪后的系统电流&#xff0c;电压以及功率。 2.系统仿真结果 3.核心程序与模型 版本&#xff1a;MAT…

cocos creator 3.x实现手机虚拟操作杆

简介 在许多移动游戏中&#xff0c;虚拟操纵杆是一个重要的用户界面元素&#xff0c;用于控制角色或物体的移动。本文将介绍如何在Unity中实现虚拟操纵杆&#xff0c;提供了一段用于移动控制的代码。我们将讨论不同类型的虚拟操纵杆&#xff0c;如固定和跟随&#xff0c;以及如…