IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 算法 二叉树 AVL树 -> 正文阅读

[数据结构与算法]算法 二叉树 AVL树

作者:recommend-item-box type_blog clearfix

AVL树的定义

一颗AVL树或者是空树,或者是具有下列性质的二叉搜索树:它的左子树和右子树都是AVL树,且左子树和右子树的高度只差绝对值不超过 1
在这里插入图片描述

结点的平衡因子 balance

  • 每个结点附加一个数字,给出该结点右子树的高度减去左子树的高度所得的高度差,这个数字即为结点的平衡因子balance
  • 根据AVL 树的定义,任一结点的平衡因子只能取 -1、0 和 1
  • 如果一个结点的平衡因子的绝对值大于1,则这颗二叉搜索树就失去了平衡,不再是AVL树
  • 如果一棵二叉搜索树是高度平衡的,它就成为AVL树,如果它有 n 个结点,其高度可保持在O(log2n),平均搜索长度也可保持在O(log2n)

AVL树的结构

typedef int KeyType;
typedef struct AVLNode
{
	KeyType key;
	struct AVLNode* leftchild;
	struct AVLNode* parent;
	struct AVLNode* rightchild;
	int balance;  //-1 0 1
}AVLNode, * AVLTree;

AVL树的插入

在向一颗本来是高度平衡(0,1,-1)的AVL树中插入一个新结点和在BST树一样,区别是,如果树中某个结点的平衡因子的绝对值 [balance] > 1,则出现了不平衡,需要做平衡化处理,使得树中各结点重新平衡化

平衡化旋转

  • 如果在一棵平衡的二叉搜索树中插入一个新结点,造成了不平衡。此时必须调整树的结构,使之平衡化
  • 平衡化旋转有两类:
    1. 单旋转(左旋和右旋)
    2. 双旋转(左平衡和右平衡)
  • 每插入一个新结点时,AVL树中相关结点的平衡状态会发生改变,因此,在插入一个新结点后,需要从插入位置沿通向根的路径回溯,检查各结点的平衡因子(左、右子树的高度差)
  • 如果在某一结点发现高度不平衡,停止回溯
  • 从发生不平衡的结点起,沿刚才回溯的路径取直接下两层的结点
  • 如果在三个结点处于一条直线上,则采用单旋转进行平衡化,单旋转可按其方向分为左单旋转和右单旋转,其中一个是另一个的镜像,其方向与不平衡的形状相关
  • 如果这三个结点处于一条折线上,则采用双旋转进行平衡化,双旋转分为先左后右和先右后左两类

在这里插入图片描述

左单旋转

在这里插入图片描述
在这里插入图片描述
根据上图,当我们插入了结点85,70平衡因子变为1,50平衡因子变为2,失去平衡性;

  • 那么将结点50作为ptr指针,将其进行旋转 ,使得ptr的右节点指向结点60,
  • 令结点70的左子树指向结点50,继而规整成一个新的结点
void RotateLeft(AVLTree& tree, AVLNode* ptr)
{
	AVLNode* newroot = ptr->rightchild; //a
	newroot->parent = ptr->parent;   //1 切换父节点
	
	ptr->rightchild = newroot->leftchild;//b
	if (newroot->leftchild != nullptr)
	{
		newroot->leftchild->parent = ptr;//2
	}

	newroot->leftchild = ptr;   //c
	if (ptr == tree)
	{
		tree = newroot;
	}
	else
	{
		if (ptr->parent->leftchild == ptr)
		{
			ptr->parent->leftchild = newroot;
		}
		else
		{
			ptr->parent->rightchild = newroot;
		}
	}
	ptr->parent = newroot;  //3
}

这三句代码分别对应:
结点50右子树指向结点70左子树结点60,接着将结点70左子树指向结点50

右单旋转

在这里插入图片描述

void RotateRight(AVLTree& tree, AVLNode* ptr)
{
	AVLNode* newroot = ptr->leftchild;
	newroot->parent = ptr->parent;

	if (newroot->rightchild  != nullptr)
	{
		newroot->rightchild->parent = ptr;
	}
	ptr->leftchild = newroot->rightchild;

	newroot->rightchild = ptr;
	if (ptr == tree)
	{
		tree = newroot;
	}
	else
	{
		if (ptr->parent->leftchild == ptr)
		{
			ptr->parent->leftchild = newroot;
		}
		else
		{
			ptr->parent->rightchild = newroot;
		}
	}

	ptr->parent = newroot;
}

与左单旋转类似,仅需要对其进行相对变形

左双旋转

在这里插入图片描述
当我们在插入过程中,出现上面的情况,当我们新的结点插入F,或者G无论哪一个结点,出现了A->B->E 这样的一个折线,那么我们就需要进行双旋转进行二叉树的平衡
在这里插入图片描述
加入我们在F进行插入,E的平衡因子变为-1,B的平衡因子变为1,A的平衡因子变为-2,进行双旋转,首先以 B 作为 ptr 进行左单旋转,变为图(c ) 再将 A 作为 ptr E为旋转点进行右单旋转变为图(d)
在这里插入图片描述

插入 insert

在这里插入图片描述
AVL树的插入与BST树的插入相似,但是需要进行高度平衡,继而要进行左右单旋转与左右双旋转,通过旋转还平衡二叉树,以及通过平衡因子的操作旋转,上图为左双旋转的实例,无论新节点插入F,或者G都需要进行左双旋转,因为ptr、leftsub、rightsub 组成了一个折线

typedef int KeyType;
typedef struct AVLNode
{
	KeyType key;
	struct AVLNode* leftchild;
	struct AVLNode* parent;
	struct AVLNode* rightchild;
	int balance;  //-1 0 1
}AVLNode, * AVLTree;


AVLNode* Buynode(KeyType kx)
{
	AVLNode* s = (AVLNode*)malloc(sizeof(AVLNode));
	if (s == NULL) exit(1);
	memset(s, 0, sizeof(AVLNode));
	s->key = kx;
	s->balance = 0;
	return s;
}
AVLNode* MakeRoot(KeyType kx)
{
	AVLNode* s = Buynode(kx);
	s->key = kx;
	return s;
}
void RotateLeft(AVLTree& tree, AVLNode* ptr)
{
	AVLNode* newroot = ptr->rightchild;
	newroot->parent = ptr->parent;

	if (newroot->leftchild != nullptr)
	{
		newroot->leftchild->parent = ptr;
	}
	ptr->rightchild = newroot->leftchild;

	newroot->leftchild = ptr;
	if (ptr == tree)
	{
		tree = newroot;
	}
	else
	{
		if (ptr->parent->leftchild == ptr)
		{
			ptr->parent->leftchild = newroot;
		}
		else
		{
			ptr->parent->rightchild = newroot;
		}
	}

	ptr->parent = newroot;
}
void RotateRight(AVLTree& tree, AVLNode* ptr)
{
	AVLNode* newroot = ptr->leftchild;
	newroot->parent = ptr->parent;
	ptr->leftchild = newroot->rightchild;
	if (newroot->rightchild != nullptr)
	{
		newroot->rightchild->parent = ptr;
	}
	
	newroot->rightchild = ptr;
	if (ptr == tree)
	{
		tree = newroot;
	}
	else
	{
		if (ptr->parent->leftchild == ptr)
		{
			ptr->parent->leftchild = newroot;
		}
		else
		{
			ptr->parent->rightchild = newroot;
		}
	}

	ptr->parent = newroot;
}
void RightBalance(AVLTree& tree, AVLNode* ptr)  //右双旋转  左双旋的镜像
{	
	AVLNode* rightsub = ptr->rightchild, * leftsub = nullptr;
	switch (rightsub->balance)
	{
	case 0: cout << "right balance" << endl; break;
	case 1:   
		ptr->balance = 0;
		rightsub->balance = 0;
		RotateLeft(tree, ptr);//左上右下的一条斜线 进行左单旋   下面左双旋的时候同理
		break;
	case -1:   
		leftsub = rightsub->leftchild;
		switch (leftsub->balance)
		{
		case -1: 
			rightsub->balance = 1 ; 
			ptr->balance = 0;     
			break;
		case 1: 
			rightsub->balance = 0;
			ptr->balance = -1;  
			break;
		case 0:   
			rightsub->balance = 0;
			ptr->balance = 0;
			break;
		}  
		leftsub->balance = 0; 
		RotateRight(tree, rightsub);
		RotateLeft(tree, ptr);  
		break;
	}
}
void LeftBalance(AVLTree& tree, AVLNode* ptr)
{
	//传入的ptr是平衡因子为-2的结点
	AVLNode* leftsub = ptr->leftchild, * rightsub = nullptr;
						//  根节点的左边 折线的中间节点
	switch (leftsub->balance)
	{
	case 0: cout << "left balance" << endl; break;
	case -1:   //向左延申的直线
		ptr->balance = 0;
		leftsub->balance = 0;
		RotateRight(tree, ptr); 
		break;
	case 1:    //一条折线 左双旋转
		rightsub = leftsub->rightchild;
		//根节点左边的右边,折线的下面节点
		switch (rightsub->balance)
		{
		case -1:  //插入在左边
			leftsub->balance = 0; //原本ptr的左孩子 现在也是ptr的左孩子
			ptr->balance = 1;     //ptr做双旋后变为了右孩子
			break;
		case 1:   //插入在右边
			leftsub->balance = -1;
			ptr->balance = 0;
			break;
		case 0:   //左双旋转后为0
			leftsub->balance = 0;
			ptr->balance = 0;
			break;
		}
		rightsub->balance = 0; //最终作根的结点,树两边高度相同
		RotateLeft(tree, leftsub);//左单旋转  ptr(最上面节点)是折线中间节点
		RotateRight(tree, ptr);   //右单旋转  ptr 是折线上面节点
		break;
	}
}
void PassBalance(AVLTree& tree, AVLNode* p)
{
	AVLNode* pa = p->parent;
	bool tall = true; //判断高度是否变化
	for (; pa != nullptr && tall;)
	{
		if (pa->leftchild == p) //判断左孩子或右孩子
		{
			switch (pa->balance)
			{
			case 0: pa->balance = -1; break;
			case 1: pa->balance = 0; tall = false;  break;
			case -1: 
				LeftBalance(tree, pa);
				tall = false;
				break;
			}
		}
		else
		{
			switch (pa->balance)
			{
			case 0: pa->balance = 1; break;
			case -1: pa->balance = 0; tall = false; break;
			case 1:
				RightBalance(tree, pa);
				tall = false;
				break;
			}
		}
		p = pa;
		pa = p->parent;
	}
}
bool Insert(AVLNode*& tree, KeyType kx)
{
	if (tree == nullptr)
	{
		tree = MakeRoot(kx);
		return true;
	}
	AVLNode* pa = nullptr;
	AVLNode* p = tree;
	while (p != nullptr && p->key != kx)
	{
		pa = p;
		p = kx < p->key ? p->leftchild : p->rightchild;
	}
	if (p != nullptr && p->key == kx) return false;
	p = Buynode(kx);
	p->parent = pa;
	if (kx < pa->key)
	{
		pa->leftchild = p;
	}
	else
	{
		pa->rightchild = p;
	}
	PassBalance(tree, p); //回溯
	return true;
}
void InOrder(AVLNode* ptr)
{
	if (ptr != nullptr)
	{
		InOrder(ptr->leftchild);
		cout << ptr->key << " " << ptr->balance << endl;
		InOrder(ptr->rightchild);
	}
}


int main()
{
	int ar[] = { 12,23,34,45,56,67,78,89,90,100 };
	AVLTree root = nullptr;
	for (auto& x : ar)
	{
		cout << Insert(root, x) << endl;
	}
	InOrder(root);
	cout << endl;
	return 0;
}

AVL 树的删除

如果被删除的节点 x 最多只有一个子女,那么问题比较简单,如果被删除节点 x 右两个子女,首先搜索 x 在中序次序下的直接前驱 y (同样可以找直接后继);再把节点 y 的内容传送给结点 x ,现在问题转移到删除结点 y ,把结点 y 当作被删除的结点 x

将结点 x 从树中删去,因为结点 x 最多右一个子女,那么我们可以简单地把 x 的双亲结点中原来指向 x 的指针改指到这个子女结点;如果结点 x 没有子女,x 双亲结点的相应指针置为 NULL,然后将原来以结点 x 为根的子树的高度减 1

  • 必须沿 x 通过向根路径反向追踪高度的变化对路经上各个结点的影响
  • 用一个布尔遍历 shorter 来指向子树的高度是否被缩短,在每个结点上要做的操作取决于 shorter 的值和结点的 balance 有时还要依赖子女的 balance
  • 布尔变量 shorter 的值初始化为 True,然后对于从 x 的双亲到根的路径上的各个结点 p,在shorter 保持为 True 时执行下面的操作,如果 shorter 变成 False,算法终止

在这里插入图片描述
case 1:虽然删掉节点使得p的平衡因子变为了1,但是对于树的总体高度没有变化,依旧为h
case 2:当删掉节点后,p的平衡因子变为0,但是该树的总体高度由h+1变为了h,使得其父亲结点的平衡因子也会随之变化,那么就需要进行回溯
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总结来说,就是要分析在平衡树的过程中是否对树的总体高度进行了影响,如果影响到了就需要进行回溯

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-05-10 12:08:32  更:2022-05-10 12:10:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 3:31:53-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码