【数据结构】平衡二叉树(插入、查找、删除)解析+完整代码

3.2 平衡二叉树

3.2.1 定义
  • 平衡二叉树,简称平衡树(AVL树)

    树上任一结点的左右子树高度差不超过1。

    结点的平衡因子=左子树高-右子树高

3.2.2 插入操作
  • 插入结点后,可能造成不平衡

    要调整最小不平衡子树,使其恢复平衡。

    调整最小不平衡树A的方法:

    1.LL:在A的左孩子的左子树中插入导致不平衡;

    2.RR:在A的右孩子的右子树中插入导致不平衡;

    3.LR:在A的左孩子的右子树中插入导致不平衡;

    4.RL:在A的右孩子的左子树中插入导致不平衡。

在这里插入图片描述

A.调整最小不平衡子树 LL
  • LL插入后:

    由于在结点A的左孩子(L)的左子树(L)上插入新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要一次向右的旋转操作。

  • 平衡旋转的目标:

    1.恢复平衡;

    2.保持二叉排序树特性:

    ​ 左子树结点值<根结点值<右子树结点值

    ​ BL<B<HR<A<AR

  • LL平衡旋转(右单旋转):

    1.将A的左孩子B向右上旋转代替A成为根结点;

    2.将A结点向右下旋转成为B的右子树的根结点;

    3.而B的原右子树作为A结点的左子树。

    在这里插入图片描述

  • 右旋代码思路

    实现f向右下旋转,p向右上旋转:

    其中,gf是f他爹。

    1.f->lchild=p->rchild;

    2.p->rchild=f;

    3.gf->lchild/rchild=p;

B.调整最小不平衡子树 RR
  • RR插入后:

    由于在结点A的右孩子(R)的右子树(R)上插入新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要一次向左的旋转操作。

  • RR平衡旋转(左单旋转)步骤:

    1.将A的右孩子B向左上旋转代替A成为根结点;

    2.将A结点向左下旋转成为B的左子树的根结点;

    3.将B的原左子树作为A结点的右子树。

    在这里插入图片描述

  • 左旋代码思路

    实现f向左下旋转,p向左上旋转:

    其中gf是f他爹。

    1.f->rchild=p->lchild;

    2.p->lchild=f;

    3.gf->lchild/rchild=p;

C.调整最小不平衡子树 LR
  • LR插入后:

    由于在A的左孩子(L)的右子树(R)上插入新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先左旋再右旋。

  • LR平衡旋转(先左后右双旋转)步骤:

    1.左旋:先将A结点的左孩子B的右子树的根结点C向左上旋转提升到B结点的位置;

    2.右旋:将C结点向右上旋转提升到A结点的位置。

    在这里插入图片描述

D.调整最小不平衡树 RL
  • RL插入后

    由于在A的右孩子(R)的左子树(L)上插入新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先右旋后左旋转。

  • RL平衡旋转(先右后左双旋转)步骤:

    1.右旋:将A结点的右孩子B的左子树的根结点C向右上旋转提升到B结点的位置;

    2.左旋:将C结点向左上旋转提升到A结点的位置。

    在这里插入图片描述

  • 旋转练习

    1.将新结点插入到相应位置;

    2.找到最近的不平衡结点;

    3.根据插入结点的位置旋转。

3.2.3 查找操作
  • 效率分析

    若树高为h,最坏情况下一个关键字对比h次,所以查找的时间复杂度不可能超过 O ( h ) O(h) O(h)

  • 最少结点数

    假设以 n h n_h nh表示深度为h的平衡树中含有的最少结点数,

    则有 n 0 = 0 , n 1 = 1 , n 2 = 2 n_0=0,n_1=1,n_2=2 n0=0,n1=1,n2=2

    n h = n h − 1 + n h − 2 + 1 n_h=n_{h-1}+n_{h-2}+1 nh=nh1+nh2+1

    • 应用:对有9个结点的平衡二叉树,树高最大为4。(因为 n 4 = 7 , n 5 = 12 , 而 n 4 < 9 < n 5 n_4=7,n_5=12,而n_4<9<n_5 n4=7,n5=12,n4<9<n5
  • 含有n个结点的平衡二叉树

    最大深度: O ( l o g 2 n ) O(log_2n) O(log2n)

    平均查找长度: O ( l o g 2 n ) O(log_2n) O(log2n)

3.2.4 删除操作
  • 删除和插入操作相同,也要保持二叉排序树的特性不变。

  • 删除操作的步骤

    1.删除结点(方法同二叉排序树);

    ​ 若删除结点是叶子结点,直接删。

    ​ 若删除结点只有一个子树,用子树顶替删除位置。

    ​ 若删除结点有两棵子树,用前驱(或后继)结点顶替,并转换为对前驱(或后继)结点的删除。

    2.一路向上找到最小不平衡子树;

    3.找到最小不平衡子树下,个头最高的儿子和孙子;

    ​ 如这里最高的儿子是80,最高的孙子是90:

    4.根据孙子的位置,调整平衡(LL/RR/LR/RL);

    ​ 孙子在LL:儿子右单旋

    ​ 孙子在RR:儿子左单旋

    ​ 孙子在LR:孙子先左旋,再右旋

    ​ 孙子在RL:孙子先右旋,再左旋

    5.如果不平衡向上传导,继续第二步。

    ​ 对最小不平衡子树的旋转可能导致树变矮,从而导致上层祖先不平衡(不平衡向上传递)

    ​ 如:

*完整代码 平衡二叉树
#include <stdio.h>
#include <stdlib.h>typedef struct Node {int key;struct Node* left;struct Node* right;int height;
} Node;// 计算节点的高度
int height(Node* node) {if (node == NULL)return 0;return node->height;
}// 返回两个整数中较大的一个
int max(int a, int b) {return (a > b) ? a : b;
}// 创建一个新节点
Node* newNode(int key) {Node* node = (Node*)malloc(sizeof(Node));node->key = key;node->left = NULL;node->right = NULL;node->height = 1;return node;
}// 右旋转子树
Node* rightRotate(Node* y) {Node* x = y->left;Node* T2 = x->right;x->right = y;y->left = T2;y->height = max(height(y->left), height(y->right)) + 1;x->height = 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 = max(height(x->left), height(x->right)) + 1;y->height = max(height(y->left), height(y->right)) + 1;return y;
}// 获取节点的平衡因子
int getBalance(Node* node) {if (node == NULL)return 0;return height(node->left) - height(node->right);
}// 插入节点
Node* insert(Node* node, int key) {if (node == NULL)return newNode(key);if (key < node->key)node->left = insert(node->left, key);else if (key > node->key)node->right = insert(node->right, key);else // 如果键值已经存在,则不插入return node;node->height = 1 + max(height(node->left), height(node->right));int balance = getBalance(node);// 如果节点不平衡,根据不同情况进行旋转if (balance > 1 && key < node->left->key)return rightRotate(node);if (balance < -1 && key > node->right->key)return leftRotate(node);if (balance > 1 && key > node->left->key) {node->left = leftRotate(node->left);return rightRotate(node);}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 != NULL)current = current->left;return current;
}// 删除节点
Node* deleteNode(Node* root, int key) {if (root == NULL)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 == NULL) || (root->right == NULL)) {Node* temp = root->left ? root->left : root->right;if (temp == NULL) {temp = root;root = NULL;} else*root = *temp;free(temp);} else {Node* temp = minValueNode(root->right);root->key = temp->key;root->right = deleteNode(root->right, temp->key);}}if (root == NULL)return root;root->height = 1 + 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 == NULL || root->key == key)return root;if (root->key < key)return search(root->right, key);return search(root->left, key);
}// 中序遍历二叉树
void inorder(Node* root) {if (root != NULL) {inorder(root->left);printf("%d ", root->key);inorder(root->right);}
}// 主函数
int main() {Node* root = NULL;root = insert(root, 10);root = insert(root, 20);root = insert(root, 30);root = insert(root, 40);root = insert(root, 50);root = insert(root, 25);printf("Inorder traversal of the constructed AVL tree:\n");inorder(root);printf("\n");Node* foundNode = search(root, 30);if (foundNode != NULL)printf("Node with key 30 found!\n");elseprintf("Node with key 30 not found.\n");root = deleteNode(root, 30);printf("Inorder traversal after deletion of 30:\n");inorder(root);printf("\n");foundNode = search(root, 30);if (foundNode != NULL)printf("Node with key 30 found!\n");elseprintf("Node with key 30 not found.\n");return 0;
}

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

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

相关文章

武装部三维电子沙盘-行动路径规划分析平台

武装部三维电子沙盘-行动路径规划分析平台是一款集成了大数据、云计算、虚拟现实、物联网、AI等先进技术的综合性系统。它真实模拟地形、地貌、地物&#xff0c;结合地理空间专业数据、执勤兵力部署、基干民兵信息、普通民兵信息、潜力资源信息、军队相关专业数据、兵要地志数据…

【回溯 代数系统】679. 24 点游戏

本文涉及知识点 回溯 代数系统 LeetCode679. 24 点游戏 给定一个长度为4的整数数组 cards 。你有 4 张卡片&#xff0c;每张卡片上都包含一个范围在 [1,9] 的数字。您应该使用运算符 [‘’, ‘-’, ‘*’, ‘/’] 和括号 ‘(’ 和 ‘)’ 将这些卡片上的数字排列成数学表达式…

SQLserver - 笔记

1 SQLserver - 用户管理 4、SQL SERVER数据库用户管理_哔哩哔哩_bilibili 创建用户 - user 2.选择用户&#xff0c;修改属性

Qt---绘图和绘图设备

一、QPainter绘图 绘图事件 void paintEvent() 声明一个画家对象&#xff0c;OPainter painter(this) this指定绘图设备 画线、画圆、画矩形、画文字 设置画笔QPen 设置画笔宽度、风格 设置画刷QBrush 设置画刷风格 代码示例&#xff1a; #includ…

LeetCode---循环队列

循环队列就是只有固定的内存&#xff0c;存数据&#xff0c;出数据&#xff0c;但是也和队列一样&#xff0c;先进先出。如下图所示&#xff0c;这是他的样子 在head出&#xff0c;tail进&#xff0c;但是这个如果用数组解决的话&#xff0c;就有问题&#xff0c;力扣给我们的接…

libx265的交叉编译

目标平台&#xff1a;aarch64 编译器版本&#xff1a;aarch64-linux-gnu- 操作系统&#xff1a;麒麟v10 CPU&#xff1a;rk3588 为了方便编译aarch64版本&#xff0c;建议x265版本选择3.5版本&#xff0c;此版本build中包含了aarch64配置选项&#xff0c;比较码放。下载地…

Building3D An Urban-Scale Dataset and Benchmarks 论文阅读

文章主页 Building3D 任务 提出了一个城市规模的数据集&#xff0c;由超过 16 万座建筑物以及相应的点云、网格和线框模型组成&#xff0c;覆盖爱沙尼亚的 16 个城市&#xff0c;面积约 998 平方公里。 动机 现有的3D建模数据集主要集中在家具或汽车等常见物体上。缺乏建…

CAPL入门之使用CAPL记录测试Logging

0 前言 以往测试的log都是直接从trace导出&#xff0c;但是最近发现trace中能导出的数据是有限的&#xff0c;如果测试的时间过长&#xff0c;新的数据就会把之前的数据全部覆盖&#xff0c;并且对于长时间的测试&#xff0c;直接导出trace的内容也会造成查找效率低下的问题。因…

centos7下使用docker安装fastdfs服务

先查看容器是否已经存在 docker ps -a 删除掉之前的tracker及storage服务 docker rm tracker docker rm storage 1、没有镜像先下载镜像 docker pull morunchang/fastdfs 2、运行服务 a、不指定物理服务器路径 docker run -d --name tracker --nethost morunchang/fastdfs sh…

Java面试八股之String s = “String“;和String s = new String(“String“);有什么区别

Java中String s "String";和String s new String("String");有什么区别 字符串字面量&#xff08;"String"&#xff09;&#xff1a; 常量池&#xff1a;使用字面量方式创建字符串时&#xff0c;Java虚拟机&#xff08;JVM&#xff09;会在运…

什么是Java中的设计模式?请列举几种常见的设计模式

一、引言 在软件开发中&#xff0c;设计模式是解决特定设计问题的最佳实践或通用解决方案。Java作为一种广泛使用的编程语言&#xff0c;其设计模式在软件设计和架构中起着至关重要的作用。设计模式不仅提高了代码的可读性和可维护性&#xff0c;还使得代码更加灵活和可扩展。…

Jmeter接口测试之参数化

在接口测试中&#xff0c;某些时候一些场景会使用到参数化的场景&#xff0c;参数化简单的说就是同一个请求需要不同的数据&#xff0c;比如在性能测试中需要并发多个用户的场景&#xff0c;这样的目的是为了模拟真实的用户场景&#xff0c;需要模拟不同的账号&#xff0c;这里…

「TypeScript系列」TypeScript变量声明

文章目录 一、TypeScript 变量声明二、TypeScript 变量作用域1. 全局作用域2. 函数作用域&#xff08;局部作用域&#xff09;3. 块级作用域4. 模块作用域 三、TypeScript 类型断言四、TypeScript 类型推断1. 类型推断的示例变量类型推断函数参数和返回值的类型推断 2. 类型推断…

28、Flink 为管理状态自定义序列化

为管理状态自定义序列化 a&#xff09;概述 对状态使用自定义序列化&#xff0c;包含如何提供自定义状态序列化程序、实现允许状态模式演变的序列化程序。 b&#xff09;使用自定义状态序列化程序 注册托管 operator 或 keyed 状态时&#xff0c;需要 StateDescriptor 来指…

从零学算法14

14. 最长公共前缀 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 “”。 示例 1&#xff1a; 输入&#xff1a;strs [“flower”,“flow”,“flight”] 输出&#xff1a;“fl” 示例 2&#xff1a; 输入&#xff1a;strs [“d…

算法练习第21天|216.组合总和|||、17.电话号码的字母组合

216.组合总和 III 216. 组合总和 III - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/combination-sum-iii/ 题目描述&#xff1a; 找出所有相加之和为 n 的 k 个数的组合&#xff0c;且满足下列条件&#xff1a; 只使用数字1到9每个数字 最多使用一…

北亚MF2200手机取证平台介绍

一、产品介绍。 北亚MF2200手机取证平台是由北亚企安科技&#xff08;北京&#xff09;有限公司&#xff08;Frombyte&#xff09;自主研发的一款针对智能手机&#xff08;iPhone、Android&#xff09;及 iPad 取证分析的法证平台。本平台采集速度快&#xff0c;可通过自动提取…

【VUE】VUE3绘制箭头组件

效果预览&#xff1a; 长、宽、粗细等等根据情况合理调整即可。 组件&#xff1a; <template><div class"line" :style"props.arrowsColor"></div> </template><script setup> import { defineProps, ref, onMounted } fr…

为什么使用AI 在游戏中不犯法

使用AI在游戏中本身并不违法&#xff0c;甚至在很多情况下&#xff0c;游戏公司自己也会在游戏中集成AI来提高游戏体验&#xff0c;例如通过AI驱动的非玩家角色&#xff08;NPC&#xff09;来增加游戏的互动性和挑战性。然而&#xff0c;使用AI是否违法取决于AI的使用方式和目的…

36. 有效的数独 - 力扣(LeetCode)

基础知识要求&#xff1a; Java&#xff1a;方法、for循环、if判断、数组 Python&#xff1a; 方法、for循环、if判断、列表、集合 题目&#xff1a; 请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 &#xff0c;验证已经填入的数字是否有效即可。 数字 1-9 在每一…