C++AVL树的插入平衡和删除平衡:原理+代码(插入篇)

C++AVL树的插入平衡和删除平衡:原理+代码(插入篇)


前言

由于AVL的插入平衡操作与删除平衡操作网上版本很多,不同的插入与删除不能很好的兼容在一个程序里(思路不同),所以抽空写一个自己完成的插入和删除操作,因为比较懒,所以分开写,这次先写插入平衡操作~


一、插入平衡的原理

AVL树是一种活跃在大学里的数据结构,简单来说就是二叉搜索树(BST)的2.0强化版,因为二叉搜索树遇到下图的情况时和一个数组没有区别,也就失去了快速搜索的能力:在这里插入图片描述
AVL树也就应运而生了,讷导致上图二叉搜索树变成这样的原因的就是左右不平衡—失衡了,所以AVL对二叉搜索树追加了定义:“每个节点的平衡因子不能超过1”,那这里就要提一下平衡因子的概念,其中还涉及到高度的概念,就通过下面一幅图来简单说明一下吧在这里插入图片描述
这种定义书面解释都挺奇怪的,反正看个图一般就能李姐(理解)

接受了这个AVL的追加定义(必须平衡树)后,对应的操作就是平衡旋转了,通过每次将不平衡节点进行旋转来达到整体平衡,虽然每次插入删除都需要判断和旋转,但是比起成树后对搜索时间的优化,这么做还是大有裨益的,平衡旋转的定义依旧晦涩,还是通过看图就好:

在这里插入图片描述
诶那么这里就想问了,这么多复杂的情况,只看这么简单的情况有用吗?那先看个比较复杂的情况吧在这里插入图片描述
那个人建议的话,其实对于旋转方式不用过分深究意义,通过大量的归纳总结,所有的旋转情况最总归一到四种基本情况——左旋,右旋,左右旋,右左旋;当然一般的数据结构书上都会给出这四种旋转方式,不要问为什么只有这四种,问就是归纳总结(可能有原因原理),至于具体怎么调节指针书上也都有,这里值得提一嘴的是四种基本情况的后两种其实是前两种的组合,这不仅仅是命名上体现的,仔细研究一下左右旋就可以明白,如上图是对根节点进行了右左旋,换个角度看,其实这是对根节点的右子女先进行了右旋,再对根节点进行左旋后形成的,那至于为什么称之为右左旋,是因为我们在对根节点的右子女进行左旋时,它并不是一个不平衡节点,它的旋转是“强行”旋转,当然这是逻辑上的分类,我们在写左右旋或者右左旋的代码时只需要依次对两个节点调用左旋或右旋就好了,也就是说真正的旋转代码只要写左旋和右旋。

二、插入平衡的实现

1.什么时候左旋,什么时候右旋?

这个问题的答案很简单,但是要讲原理的话也不太好说,我的理解还是归纳总结,看图可以更好的理解,简简单单的四条:
首先定义:插入新节点后出现的不平衡节点叫A,新节点是A的左或者右子女的子树上的节点,那么这个左或者右节点叫B,平衡因子就简称HLB了!
①:A的HLB是+2,B的HLB是+1:右旋
②:A的HLB是+2,B的HLB是 -1:左右旋
③:A的HLB是 -2,B的HLB是+1:右左旋
④:A的HLB是 -2,B的HLB是 -1:左旋
在这里插入图片描述
不要纠结复杂情况会不会不一样,不会的哦!归纳总结!!!
可以理解下这句话:“左右旋或者右左旋的第一步是为了让旋转完的AVL树仍保持AVL树的性质。”
这里就先看下左旋吧,完整代码后续都有!

void myAVL::RotateLL(Node*& p) {
	Node* cur = p->right;      //不平衡节点的右子女
	Node* curL = cur->left;    
	Node* pre = p->_pre;
	if (curL)
		curL->_pre = p;
	p->right = curL;
	if (p == myRoot)     //是否是根节点?
		myRoot = cur;
	else {
		if (pre->left == p)
			pre->left = cur;
		else
			pre->right = cur;
	}
	cur->_pre = pre;
	cur->left = p;
	p->_pre = cur;
	Re(p);       //这是啥? 看下面
}

2.转完HLB怎么办,高度怎么办?

这个问题其实是比较复杂的,没有动手实践过的宝可能体会不到,本人做此实验时查阅了网上的多数资料发现又是类似归纳总结的东西,转完后把哪个节点的HLB设为1,哪个节点的HLB设为0,搞了半天也不知其所以然,所以我就采取了暴力!通过设计了一个函数来更新一个节点的高度和HLB,再对旋转后的节点进行“逐个”更新,听起来似乎是很低效的一种做法,但实际上却是比较高效,因为每次旋转后,需要更新高度和HLB的节点只有被动了指针的那三个和不平衡节点的父辈节点,这里的父辈是指爸爸,爷爷……直至根节点,也就是说,n个节点的AVL树父辈节点就大约 log ⁡ 2 n \log_2n log2n个,数据量大的时候,也还是比较划的来的!(当然这里在定义节点时就增加了父亲指针_pre)
代码如下(示例):

void myAVL::Reheight(Node*& p) {      //更新p节点的高度和不平衡度
	if (!p->left && !p->right) {      //无子情况           
		p->height = 1;
		p->HLB = 0;
	}
	else if (p->left && !p->right) {  //只有左子女
		p->height = p->left->height + 1;
		p->HLB = p->left->height - 0;
	}
	else if (p->right && !p->left) {  //只有右子女
		p->height = p->right->height + 1;
		p->HLB = 0 - p->right->height;
	}
	else {                            //双子情况
		p->height = 
		(p->right->height > p->left->height ? p->right->height : p->left->height) + 1;
		p->HLB = p->left->height - p->right->height;
	}
}
void myAVL::Re(Node*& p) {   //对p节点以上的元素都进行重新计算高度和不平衡度
	Node* t = p;
	while (t) {
		Reheight(t);
		t = t->_pre;
	}
}

3.怎么找不平衡节点?

清楚了上面两个问题后,只需要再搞清楚这个问题,那么插入平衡就大功告成了,按照1里的定义,这个模块里我们需要寻找的是平衡因子的绝对值是2的节点,当然附带我们还需要知道新插入节点是不平衡节点的左子女还是右子女的子女(有点拗口,仔细理解),这是我们做出如何旋转的重要判据,那么大体上就是遍历了,这里先看代码(具体怎么插入就不说了):

	while (parent) {                                       //AVL调整
			if (parent->HLB == 0)
				break;                                         //parent平衡度=0 不需要调整 
			else if (abs(parent->HLB) == 1) {                  //parent平衡度=+/-1 上层情况未知 向上继续探索
				cur = parent;
				parent = parent->_pre;
			}
			else if (abs(parent->HLB) == 2) {                  //parent平衡度=+/-2 出现了不平衡情况  
				if (parent->HLB == 2 && cur->HLB == 1) {       
					RotateRR(parent);                           //左左大->右旋
				}
				else if (parent->HLB == -2 && cur->HLB == -1) {
					RotateLL(parent);                           //右右大->左旋
				}
				else if (parent->HLB == 2 && cur->HLB == -1) {
					RotateLR(parent);                           //左右大->左右旋
				}
				else if (parent->HLB == -2 && cur->HLB == 1) {
					RotateRL(parent);                           //右左大->右左旋
				}
				break;
			}
		}
		Size++;                                                 //size加一
	}
}

这里的parent节点顾名思义是新节点的父亲节点,我们通过对parent节点的HLB的判断来决定是否继续向上遍历:
①:插入一个节点后使得其父亲节点的HLB变为0了,说明这个节点为平衡做出了贡献,整个AVL树更平衡了,那当然不需要再向上遍历了,break即可!
②:插入一个节点后使得其父亲节点的HLB的绝对值变为1了,那么就不清楚它是否破坏了平衡了,所以我们向上继续遍历,为什么不清楚?就是不清楚呀!清楚还需要遍历吗?
③:插入一个节点后使得其父亲节点的HLB的绝对值变为2了?显然这是不可能的嘛,这个 if 的条件块显然是为向上遍历准备的,那既然找到了我们的目标,只要按我们的规则对其旋转就好了!

那么到这里所有的操作就完成啦!!!

四、源代码:

四类旋转:

void myAVL::RotateRR(Node*& p) {
	Node* cur = p->left;
	Node* curR = cur->right;
	Node* pre = (p==myRoot?NULL:p->_pre);
	if (p == myRoot) 
		myRoot = cur;
	
	p->_pre = cur;       //step one
	p->left = curR;      // step two
	cur->right = p;      //step three 
	
	//pre指针的重置
	
	if (curR)
		curR->_pre = p;
	else {
		if (pre) {
			if (pre->left == p)
				pre->left = cur;
			else
				pre->right = cur;
		}
	}                  
	cur->_pre = pre;
	Re(p);
}

void myAVL::RotateLL(Node*& p) {
	Node* cur = p->right;
	Node* curL = cur->left;
	Node* pre = p->_pre;
	if (curL)
		curL->_pre = p;
	p->right = curL;
	if (p == myRoot)
		myRoot = cur;
	else {
		if (pre->left == p)
			pre->left = cur;
		else
			pre->right = cur;
	}
	cur->_pre = pre;
	cur->left = p;
	p->_pre = cur;
	Re(p);
}

void myAVL::RotateLR(Node*& p) {
	Node* pL = p->left;
	Node* pLR = pL->right;
	RotateLL(pL);
	RotateRR(p);
}

void myAVL::RotateRL(Node*& p) {
	Node* pL = p->right;
	Node* pLR = pL->left;
	RotateRR(pL);
	RotateLL(p);
}

更新高度和不平衡度:

void myAVL::Reheight(Node*& p) {      //更新p节点的高度和不平衡度
	if (!p->left && !p->right) {      //无子情况           
		p->height = 1;
		p->HLB = 0;
	}
	else if (p->left && !p->right) {  //只有左子女
		p->height = p->left->height + 1;
		p->HLB = p->left->height - 0;
	}
	else if (p->right && !p->left) {  //只有右子女
		p->height = p->right->height + 1;
		p->HLB = 0 - p->right->height;
	}
	else {                            //双子情况
		p->height = 
		(p->right->height > p->left->height ? p->right->height : p->left->height) + 1;
		p->HLB = p->left->height - p->right->height;
	}
}

void myAVL::Re(Node*& p) {   //对p节点以上的元素都进行重新计算高度和不平衡度
	Node* t = p;
	while (t) {
		Reheight(t);
		t = t->_pre;
	}
}

insert 插入

void myAVL::Insert(string a, string b) {
	if (Search(a) != NULL) {                                   //判断是否已存在
 		cout << "账号已经存在" << endl;
		return;
	}
	if (myRoot == NULL) {                                      //空树开始创建
		myRoot = new Node(a, b);
		Reheight(myRoot);
		Size++;
	}
	else {
		Node* cur = myRoot;
		Node* parent = NULL;
		while (cur != NULL) {
			if (a < cur->user.getuserName()) {
				parent = cur;
				cur = cur->left;
			}
			else if (a > cur->user.getuserName()) {
				parent = cur;
				cur = cur->right;
			}
		}                                                      //找到待插入位置 
		cur = new Node(a, b);
		cur->_pre = parent;                                    //构建父节点
		if (a < parent->user.getuserName())
			parent->left = cur;
		else
			parent->right = cur;
		Re(cur);                                              
		 //调整沿元素向上指针以上的所有元素的位置参数
		//-------------------------------------------------------------------//
		while (parent) {                                       //AVL调整
			if (parent->HLB == 0)
				break;                                        
				 //parent平衡度=0 不需要调整 
			else if (abs(parent->HLB) == 1) {                  
			//parent平衡度=+/-1 上层情况未知 向上继续探索
				cur = parent;
				parent = parent->_pre;
			}
			else if (abs(parent->HLB) == 2) {                 
			 //parent平衡度=+/-2 出现了不平衡情况  
				if (parent->HLB == 2 && cur->HLB == 1) {       
					RotateRR(parent);                           //左左大->右旋
				}
				else if (parent->HLB == -2 && cur->HLB == -1) {
					RotateLL(parent);                           //右右大->左旋
				}
				else if (parent->HLB == 2 && cur->HLB == -1) {
					RotateLR(parent);                           //左右大->左右旋
				}
				else if (parent->HLB == -2 && cur->HLB == 1) {
					RotateRL(parent);                           //右左大->右左旋
				}
				break;
			}
		}
		Size++;                                                 //size加一
	}
}

预告

下期更新删除的平衡操作,有时间可能会做一期关于纵向输出AVL树形的内容!记得三连!!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值