平衡二叉树(AVL树):原理、常见算法及其应用

ops/2024/9/24 7:51:45/

目录

引言

AVL树的基本概念

常见算法

插入节点

查找节点

删除节点

AVL树的应用场景

1. 数据库索引

2. 符号表

3. 字典和词汇表

4. 动态集合

5. GPS导航系统

6. 计算机辅助设计(CAD)

结论


引言

平衡二叉树(Balanced Binary Tree),特别是AVL树(Adelson-Velsky and Landis tree),是一种自平衡的二叉搜索树。它在每次插入或删除操作之后都会自动调整自身,以确保树的高度保持在尽可能小的状态。这种自我调整机制使得AVL树能够在保持较低的高度的同时,提供高效的查找、插入和删除操作。本文将从AVL树的基本概念出发,逐步介绍其原理、常见算法,并通过具体的Java代码示例来加深理解,最后探讨AVL树算法中的实际应用场景。

AVL树的基本概念

AVL树是一种特殊的二叉搜索树,其特点是任何节点的两个子树的高度之差最多为1。这意味着AVL树始终保持在相对平衡的状态,从而保证了操作的高效性。

节点定义

class AVLTreeNode {int val;int height;AVLTreeNode left;AVLTreeNode right;AVLTreeNode(int x) {val = x;height = 1; // 新节点默认高度为1}
}

高度和平衡因子

  • 高度:节点的高度定义为该节点到其最远叶子节点路径上的边数。
  • 平衡因子:节点的平衡因子定义为其左子树的高度减去右子树的高度。

失衡

当我们在一个平衡二叉树上插入一个结点时,有可能会导致失衡,即出现平衡因子绝对值大于1       的结点,如下图,当插入结点后,其中key为53的结点失去了平衡,我们称该结点为失衡结点,我们必须重新调整树的结构,使之恢复平衡。

恢复平衡:

那如何去恢复平衡,使得二叉搜索树依然成立为一棵平衡树?先来看平衡调整的四种类型:

 举个例子:如第一个,当平衡二叉树为AB时,插入一个C结点,使得失衡了,失衡结点为A,此时因为C结点插入的位置为失衡结点的左孩子的左孩子,所以是LL型,以此类推。

为了恢复平衡,针对每一种都不一样,但目的都是调整为平衡的,且不能违背二叉搜索树的性质:如下图是每种失衡类型的解决方法,每个都不太一样,目的都是一样的,把key的值为中等的变为树的根,最小的放在左孩子,做大的放右孩子,通过这一目的,降低树的高度,也不用死记硬背。

 

常见算法

插入节点

插入新节点时,需要按照二叉搜索树的方式找到合适的位置,然后进行必要的旋转操作以恢复AVL树的平衡性。

例题:输入关键字序列(16,3,7,11 ,9,26,18,14,15)给出AVL树

 

对应Java代码实现:

public class AVLTree {private AVLTreeNode root;// AVL树节点类private static class AVLTreeNode {int val;int height;AVLTreeNode left;AVLTreeNode right;AVLTreeNode(int x) {val = x;height = 1; // 新节点默认高度为1}}// 获取节点的高度private int getHeight(AVLTreeNode node) {if (node == null) {return 0;}return node.height;}// 获取节点的平衡因子private int getBalanceFactor(AVLTreeNode node) {if (node == null) {return 0;}return getHeight(node.left) - getHeight(node.right);}// 更新节点的高度private void updateHeight(AVLTreeNode node) {node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;}// 右旋private AVLTreeNode rotateRight(AVLTreeNode y) {AVLTreeNode x = y.left;AVLTreeNode T2 = x.right;// Perform rotationx.right = y;y.left = T2;// Update heightsupdateHeight(y);updateHeight(x);return x;}// 左旋private AVLTreeNode rotateLeft(AVLTreeNode x) {AVLTreeNode y = x.right;AVLTreeNode T2 = y.left;// Perform rotationy.left = x;x.right = T2;// Update heightsupdateHeight(x);updateHeight(y);return y;}// 插入节点public void insert(int value) {root = insertRecursive(root, value);}private AVLTreeNode insertRecursive(AVLTreeNode current, int value) {if (current == null) {return new AVLTreeNode(value);}if (value < current.val) {current.left = insertRecursive(current.left, value);} else if (value > current.val) {current.right = insertRecursive(current.right, value);} else {return current; // Value already exists}// Update height of this ancestor nodeupdateHeight(current);// Check balance factor to determine the rotationsint balanceFactor = getBalanceFactor(current);if (balanceFactor > 1) {if (value < current.left.val) {return rotateRight(current);} else if (value > current.left.val) {current.left = rotateLeft(current.left);return rotateRight(current);}}if (balanceFactor < -1) {if (value > current.right.val) {return rotateLeft(current);} else if (value < current.right.val) {current.right = rotateRight(current.right);return rotateLeft(current);}}return current;}// 打印AVL树public void printAVLTree() {printInOrder(root);System.out.println();}private void printInOrder(AVLTreeNode node) {if (node != null) {printInOrder(node.left);System.out.print(node.val + " ");printInOrder(node.right);}}// 主函数public static void main(String[] args) {AVLTree avlTree = new AVLTree();// 插入关键字序列avlTree.insert(16);avlTree.insert(3);avlTree.insert(7);avlTree.insert(11);avlTree.insert(9);avlTree.insert(26);avlTree.insert(18);avlTree.insert(14);avlTree.insert(15);// 打印AVL树avlTree.printAVLTree(); // 输出:3 7 9 11 14 15 16 18 26}
}

查找节点

查找特定值的节点时,从根节点开始,根据值与当前节点值的关系决定向左还是向右。

Java代码实现

boolean contains(int value) {return containsRecursive(root, value);
}private boolean containsRecursive(AVLTreeNode current, int value) {if (current == null) {return false;}if (value == current.val) {return true;}return value < current.val? containsRecursive(current.left, value): containsRecursive(current.right, value);
}

删除节点

删除节点时需要考虑三种情况:

  1. 节点是叶子节点。
  2. 节点有一个子节点。
  3. 节点有两个子节点。

Java代码实现

void delete(int value) {root = deleteRecursive(root, value);
}private AVLTreeNode deleteRecursive(AVLTreeNode current, int value) {if (current == null) {return current;}if (value < current.val) {current.left = deleteRecursive(current.left, value);} else if (value > current.val) {current.right = deleteRecursive(current.right, value);} else {// Node with only one child or no childif (current.left == null) {return current.right;} else if (current.right == null) {return current.left;}// Node with two children: Get the inorder successor (smallest in the right subtree)current.val = findMinValue(current.right);// Delete the inorder successorcurrent.right = deleteRecursive(current.right, current.val);}// If the node has only one child or no child, then it will be returned by above statements.if (current == null) {return current;}// Update the height of the current nodeupdateHeight(current);// Check balance factor to determine the rotationsint balanceFactor = getBalanceFactor(current);// Left Left Caseif (balanceFactor > 1 && getBalanceFactor(current.left) >= 0) {return rotateRight(current);}// Left Right Caseif (balanceFactor > 1 && getBalanceFactor(current.left) < 0) {current.left = rotateLeft(current.left);return rotateRight(current);}// Right Right Caseif (balanceFactor < -1 && getBalanceFactor(current.right) <= 0) {return rotateLeft(current);}// Right Left Caseif (balanceFactor < -1 && getBalanceFactor(current.right) > 0) {current.right = rotateRight(current.right);return rotateLeft(current);}return current;
}// Find the minimum value in a subtree
int findMinValue(AVLTreeNode node) {return node.left == null ? node.val : findMinValue(node.left);
}

AVL树的应用场景

1. 数据库索引

AVL树可以用于构建数据库的索引,以加速查询速度。

应用原理

  • 数据库记录的键值存储在AVL树的节点中。
  • 查询时,根据键值在树中查找对应的记录。
  • 插入和删除记录时,自动调整树的平衡,保持较低的高度。

场景描述

在数据库管理系统中,索引是提高查询效率的重要手段之一。当数据库需要频繁地按某个字段进行查询时,可以创建基于该字段的AVL树索引。这样,在执行查询操作时,可以迅速定位到指定键值所在的记录,而不需要全表扫描。AVL树的自平衡特性确保了索引结构的高效性,即使在频繁的插入和删除操作下也能保持良好的性能。

2. 符号表

在编程语言的编译器中,符号表用于跟踪变量声明和类型信息。

应用原理

  • 变量名称作为键值存储在AVL树中。
  • 变量的类型和其他相关信息作为键值对应的数据存储。
  • 编译器在处理源代码时,需要维护一个符号表来跟踪所有的变量声明及其属性。

场景描述

编译器在处理源代码时,需要维护一个符号表来跟踪所有的变量声明及其属性。AVL树可以有效地管理这些信息,因为编译器可以根据变量名称快速查找、插入或更新相应的信息。这有助于编译器在编译过程中进行类型检查和其他优化工作。AVL树的自平衡特性使得符号表能够在处理大型代码库时依然保持高效。

3. 字典和词汇表

在自然语言处理中,AVL树可用于构建词汇表或词典。

应用原理

  • 字符串作为键值存储在AVL树中。
  • 字符串的意义或其他附加信息作为键值对应的数据存储。
  • 在处理文本时,需要快速查找和更新词汇信息。

场景描述

在处理自然语言文本时,需要对文本中的词汇进行统计和分析。AVL树可以用于构建词汇表,其中词汇作为键值,出现频率等信息作为键值对应的数据。这有助于在处理文本时快速查找和更新词汇信息,从而提高文本处理的效率。AVL树的自平衡特性使得词汇表能够在处理大量词汇时依然保持高效。

4. 动态集合

AVL树可以用于实现动态集合,支持动态添加、删除元素并保持有序。

应用原理

  • 集合中的元素作为键值存储在AVL树中。
  • 插入新元素时,保持树的有序性。
  • 删除元素时,调整树以保持AVL树的性质。

场景描述

在需要动态管理一组有序元素的应用场景中,如任务队列或优先级队列,AVL树可以有效地支持元素的插入、删除操作,同时保持集合的有序性。这使得在处理动态变化的数据集合时更加高效和灵活。AVL树的自平衡特性使得动态集合在频繁的操作下依然保持良好的性能。

5. GPS导航系统

AVL树可以用于GPS导航系统中的路径规划算法

应用原理

  • 路径规划算法需要快速查找和更新路径信息。
  • AVL树可以用于存储地理坐标信息,并快速查找最近的路径节点。

场景描述

在GPS导航系统中,路径规划算法需要快速查找和更新路径信息。AVL树可以用于存储地理坐标信息,并快速查找最近的路径节点。这有助于导航系统在实时更新路线时保持高效。AVL树的自平衡特性使得路径规划算法能够在处理大量地理数据时依然保持良好的性能。

6. 计算机辅助设计(CAD)

AVL树可以用于CAD系统中的图形对象管理。

应用原理

  • 图形对象的ID作为键值存储在AVL树中。
  • 图形对象的属性作为键值对应的数据存储。
  • CAD系统需要快速查找和更新图形对象的信息。

场景描述

在CAD系统中,需要管理大量的图形对象,并且经常需要快速查找和更新这些对象的信息。AVL树可以用于存储图形对象的ID,并快速查找对应的属性信息。这有助于CAD系统在处理复杂的设计时保持高效。AVL树的自平衡特性使得图形对象管理在处理大量数据时依然保持良好的性能。

结论

AVL树作为一种高效的数据结构,在计算机科学中有广泛的应用。通过自平衡机制,AVL树能够在保持较低的高度的同时,提供高效的查找、插入和删除操作。掌握AVL树的概念和相关算法对于深入理解计算机科学的核心知识至关重要。


http://www.ppmy.cn/ops/115193.html

相关文章

解决启动docker desktop报The network name cannot be found的问题

现象 deploying WSL2 distributions ensuring main distro is deployed: checking if main distro is up to date: checking main distro bootstrap version: getting main distro bootstrap version: open \wsl$\docker-desktop\etc\wsl_bootstrap_version: The network name…

golang学习笔记3-变量的声明

注&#xff1a;本人已有C&#xff0c;C,Python基础&#xff0c;只写本人认为的重点。 一、变量的三种声明方式 func main() {//方式1&#xff0c;指定数据类型&#xff0c;声明后若不赋值&#xff0c;使用默认值//比如int的默认值是0&#xff0c;string的默认值是空串var i in…

购买转化预测_逻辑回归,网格搜索与交叉验证

数据入口&#xff1a;在线书店A/B测试数据集 - Heywhale.com 数据说明 字段说明Theme显示主题&#xff0c;dark&#xff08;深色&#xff09;&#xff1b;light&#xff08;浅色&#xff09;Click Through Rate点击率&#xff1a;用户点击网站上链接或按钮的比例Conversion R…

spring 的启动过程

Spring 框架的启动过程涉及各种模块的初始化、依赖注入、AOP 配置等&#xff0c;以下是 Spring 容器启动的一般过程。 加载配置文件。首先读取配置文件&#xff08;如 XML 配置文件、Java Config 类等&#xff09;。实例化容器。根据配置文件中的信息创建容器 ApplicationCont…

Python 中的 HTTP 编程入门,如何使用 Requests 请求网络

Python 中的 HTTP 编程入门 HTTP&#xff08;超文本传输协议&#xff09;是现代网络通信的基础&#xff0c;几乎所有的网络应用都依赖于 HTTP 协议进行数据交换。在 Python 中&#xff0c;处理 HTTP 请求和响应非常简单&#xff0c;可以通过内置的 http 模块或第三方库如 requ…

【网站架构部署与优化】源码编译安装LAMP

文章目录 LAMP架构概述各组件的主要作用构建LAMP平台的安装顺序 编译安装Apache httpd服务指南1. 准备工作1.1 关闭防火墙并传输软件包1.2 安装环境依赖包 2. 配置软件模块2.1 解压软件包2.2 移动apr组件包2.3 配置httpd 3. 编译及安装4. 优化配置4.1 配置文件路径4.2 添加http…

asp.net core web api 使用apollo配置更改回调监听

安装依赖包 > Com.Ctrip.Framework.Apollo 2.10.0 2.10.0> Com.Ctrip.Framework.Apollo.ConfigAdapter.Yaml 2.9.0 2.9.0 > Com.Ctrip.Framework.Apollo.Configuration 2.10.2 2.10.2> Com.Ctrip.Framework.Apollo.…

patch 命令:补丁的应用

一、命令简介 ​diff​ 和 patch​ 是传统的文件比较和应用差异的工具&#xff0c;而 git​ 是一个更现代、功能更全面的版本控制系统&#xff0c;它内置了 diff​ 和 patch​ 的功能&#xff0c;并且提供了更多用于代码管理和协作的高级特性。 diff, patch 和 git 之间的关系…