AVL平衡二叉树
前言:这里学习和实现下相关平衡二叉树,之前windows内核中就有看到AVL树,那时候也只会看到这个结构体,但是具体的实现觉得自己没必要了解,但是在学习数据结构的时候又发现了这个,那自己想反正内核也有,那自己就一起好好学习实现下
参考文章:https://zhuanlan.zhihu.com/p/56066942
Windows采用平衡二叉树把这些离散的地址范围管理起来。
在32位Windows系统中,进程在用户态可用的地址空间范围是低2G(x64下是低8192G)。随着进程不断的申请和释放内存,这个2G的地址空间,有的地址范围是保留状态(reserved),有的地址范围是提交状态(映射到了物理页面,committed),有的地址范围是空闲的。
平衡二叉树的定义
平衡二叉查找树:简称平衡二叉树。由前苏联的数学家 Adelse-Velskil 和 Landis 在 1962 年提出的高度平衡的二叉树,根据科学家的英文名也称为 AVL 树。它具有如下几个性质:
-
可以是空树。
-
假如不是空树,任何一个结点的左子树与右子树都是平衡二叉树,并且高度之差的绝对值不超过 1。
它的定义任意一个结点的左右子树的高度相差不超过1的树为平衡二叉树。
例如下面的图不是平衡二叉树,因为结点 60 的左子树不是平衡二叉树。
下面的图也不是二叉树,因为虽然任何一个结点的左子树与右子树都是平衡二叉树,但高度之差已经超过1。
什么是平衡因子
定义:某节点的左子树与右子树的高度(深度)差即为该节点的平衡因子(BF,Balance Factor)。
平衡二叉树中不存在平衡因子大于 1 的节点。
在一棵平衡二叉树中,节点的平衡因子只能取 0 、1 或者 -1 ,分别对应着左右子树等高,左子树比较高,右子树比较高,其实1和-1都一样,只是右子树比左子树高了1个高度,反过来其实都是一样的
如何理解这个平衡因子,比如如下图所示,这颗树的左右结点的高度都是N,那么它们差就是0,而平衡因子就是左子树的高度减去右子树的高度的一个数值
平衡因子为0的情况如下图所示,那么就说明当前节点的左右子树高度差为0
平衡因子为1的情况如下图所示,那么就说明当前节点的左右子树高度差为1
最小失衡子树
最小失衡子树的定义就是在新插入的结点向上查找,以第一个平衡因子的绝对值超过 1 的结点为根的子树称为最小不平衡子树。
简单的理解在一棵平衡二叉树中插入一个节点,插入了之后导致当前的树不再处于平衡的状态,也就是平衡因子超过1,那么以当前节点向上走,从当前节点到平衡因子超过1的节点之间的节点就被称作为
左旋LL LR / 右旋 RR RL
平衡二叉树的失衡调整主要是通过旋转最小失衡子树来实现的。根据旋转的方向有两种处理方式,左旋与右旋。
这里的话可以把左旋和右旋这种方式理解为修复平衡的手段,当我们擅自插入了一个结点之后,可能就会导致当前树不再是平衡树,而通过相关的操作(左旋和右旋)能够将其平衡,具体就是修复其最小平衡二叉树,让其整个树来达到平衡的效果。
#include<stdio.h> #include<stdlib.h> #include<string.h> #define OK 1 #define ERROR 0 typedef int ElemType; typedef int Status; typedef struct _BTreeNode{ struct _BTreeNode* pLeftBtreeNode; struct _BTreeNode* pRightBtreeNode; ElemType height; ElemType nodeData; }BTreeNode, *PBTreenode; typedef struct _SqTree{ BTreeNode* pRootTreeNode; }SqTree, *PSqTree; // init root tree node Status initRootTreeNode(SqTree* pSqTree, ElemType elem) { if (pSqTree->pRootTreeNode != NULL) return ERROR; pSqTree->pRootTreeNode = malloc(sizeof(BTreeNode)); memset(pSqTree->pRootTreeNode, 0, sizeof(BTreeNode)); pSqTree->pRootTreeNode->nodeData = elem; if (pSqTree->pRootTreeNode != NULL) return OK; else return ERROR; } // 平衡 - LL旋转类型 void LLolation() { } // 平衡 - RR旋转类型 void RRotation() { } // 平衡 - RL旋转类型 void RLotation() { } // 平衡 - LR旋转类型 void LRotation() { } // 二叉排序树的结点插入 // 在插入的过程中同样实现保持平衡的功能 // 一共有四种情况,LL / RR / LR / RL // 不用这种方式写了,我不知道该怎么写,我换了一种方法写,因为如果我这样写的话它会造成一种很混乱的现象 /* void insertBTreeNode(BTreeNode* pBTreeNode, BTreeNode* pPreBTreeNode, ElemType elem) { // 如果为空的话,那么就要插入到pBTreeNode左节点或者右节点了 BTreeNode* pNewBTreeNode = NULL; if (pBTreeNode == NULL) { if (pNewBTreeNode == NULL) { pNewBTreeNode = malloc(sizeof(BTreeNode)); memset(pNewBTreeNode, 0, sizeof(BTreeNode)); pNewBTreeNode->nodeData = elem; if (pPreBTreeNode->nodeData > elem) pPreBTreeNode->pLeftBtreeNode = pNewBTreeNode; else if (pPreBTreeNode->nodeData < elem) pPreBTreeNode->pRightBtreeNode = pNewBTreeNode; } } else if (pBTreeNode->nodeData < elem) { // 如果是大的话,那么传入到右子树中 pPreBTreeNode = pBTreeNode; insertBTreeNode(pBTreeNode->pRightBtreeNode, pPreBTreeNode, elem); if (pPreBTreeNode->nodeData > elem) { } } else if (pBTreeNode->nodeData > elem) { // 如果是小的话,那么传入到左节点中 pPreBTreeNode = pBTreeNode; insertBTreeNode(pBTreeNode->pLeftBtreeNode, pPreBTreeNode, elem); } pNewBTreeNode->height = getMax(getHeight(pNewBTreeNode->pLeftBtreeNode), getHeight((pNewBTreeNode)->pRightBtreeNode)) + 1; }*/ // 获取高度 ElemType getNodeHeight(BTreeNode* pTreeNode) { return pTreeNode ? pTreeNode->height : 0; } void insertBTreeNode2(BTreeNode** pTreeNode, ElemType elem) { // 如果*pTreeNode为空的话,那么就初始化,因为为空的话那么就意味着是要被新增的节点 if (*pTreeNode == NULL) { *pTreeNode = malloc(sizeof(BTreeNode)); if (*pTreeNode == NULL) return; (*pTreeNode)->height = 0; (*pTreeNode)->nodeData = elem; (*pTreeNode)->pLeftBtreeNode = NULL; (*pTreeNode)->pRightBtreeNode = NULL; } // 如果比父节点小的话,那么则插入到左节点中,可以确认的是当前的位置是位于根节点的左侧 if ((*pTreeNode)->nodeData > elem) { insertBTreeNode2(&(*pTreeNode)->pLeftBtreeNode, elem); // 来判断当前的节点是否处于平衡的情况 ElemType iRightHeight = getNodeHeight((*pTreeNode)->pRightBtreeNode); ElemType iLeftHeight = getNodeHeight((*pTreeNode)->pLeftBtreeNode); // 可能是LL或者LR if (iLeftHeight - iRightHeight == 2) { // 如果是LL的话,那么当前节点的左节点的nodeData会大于elem if ((*pTreeNode)->pLeftBtreeNode->nodeData > elem) { // LL旋转 -> 失衡节点在当前节点的左节点的左节点 BTreeNode* pTemp = (*pTreeNode)->pLeftBtreeNode; (*pTreeNode)->pLeftBtreeNode = pTemp->pRightBtreeNode; pTemp->pRightBtreeNode = (*pTreeNode); /* 6 6 5 7 3 7 (3) -> 2 5 2 4 4 */ // 旋转平衡之后,还需要修正当前节点左右子树的高度 (*pTreeNode)->height = max(getNodeHeight((*pTreeNode)->pLeftBtreeNode), getNodeHeight((*pTreeNode)->pRightBtreeNode)) + 1; pTemp->height = max(getNodeHeight(pTemp->pLeftBtreeNode), getNodeHeight(pTemp->pRightBtreeNode)) + 1; // 根节点变换 *pTreeNode = pTemp; } else if ((*pTreeNode)->pLeftBtreeNode->nodeData < elem) { // 如果是LR的话,那么当前节点的左节点的nodeData会小于elem // LR旋转 -> 失衡节点在当前节点的左节点的右节点 } } } else if ((*pTreeNode)->nodeData < elem) { insertBTreeNode2(&(*pTreeNode)->pRightBtreeNode, elem); // 来判断当前的节点是否处于平衡的情况 ElemType iRightHeight = getNodeHeight((*pTreeNode)->pRightBtreeNode); ElemType iLeftHeight = getNodeHeight((*pTreeNode)->pLeftBtreeNode); // 可能是RR或者RL if (iRightHeight - iLeftHeight == 2) { // 如果是RR的话,那么当前节点的右节点的nodeData会大于elem // RR旋转 -> 失衡节点在当前节点的右节点的右节点 if ((*pTreeNode)->pRightBtreeNode->nodeData < elem) { BTreeNode* pTemp = (*pTreeNode)->pRightBtreeNode; (*pTreeNode)->pRightBtreeNode = pTemp->pLeftBtreeNode; pTemp->pLeftBtreeNode = *pTreeNode; /* 2 2 1 (4) 1 6 6 -> 4 8 5 8 5 */ // 旋转平衡之后,还需要修正当前节点左右子树的高度 (*pTreeNode)->height = max(getNodeHeight((*pTreeNode)->pLeftBtreeNode), getNodeHeight((*pTreeNode)->pRightBtreeNode)) + 1; pTemp->height = max(getNodeHeight(pTemp->pLeftBtreeNode), getNodeHeight(pTemp->pRightBtreeNode)); // 根节点变换 *pTreeNode = pTemp; } else if ((*pTreeNode)->pRightBtreeNode->nodeData > elem) { // 如果是RL的话,那么当前节点的右节点的nodeData会大于elem // RL旋转 -> 失衡节点在当前节点的右节点的左节点 BTreeNode* pTemp = (*pTreeNode)->pRightBtreeNode; } } } // 每次都要变化当前节点的高度,为后面平衡二叉树做准备 (*pTreeNode)->height = max(getNodeHeight((*pTreeNode)->pLeftBtreeNode), getNodeHeight((*pTreeNode)->pRightBtreeNode)) + 1; } void preOrder(BTreeNode* pBTreeNode) { if (pBTreeNode != NULL) { printf("%d ", pBTreeNode->nodeData); preOrder(pBTreeNode->pLeftBtreeNode); preOrder(pBTreeNode->pRightBtreeNode); } } int main() { SqTree sqTree; memset(&sqTree, 0, sizeof(SqTree)); // initRootTreeNode(&sqTree, 1); int a[6] = { 6, 5, 3, 2, 4, 7 }; // LL类型 for (int i = 0;i<6;i++) insertBTreeNode2(&sqTree.pRootTreeNode, a[i]); preOrder(sqTree.pRootTreeNode); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2020-04-04 Thinkphp 5.0.x 未开启强制路由导致的RCE 漏洞分析