C++数据结构之平衡二叉搜索树(一)——AVL的实现(zig与zag/左右双旋/3+4重构)

这篇具有很好参考价值的文章主要介绍了C++数据结构之平衡二叉搜索树(一)——AVL的实现(zig与zag/左右双旋/3+4重构)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

00.BBST——平衡二叉搜索树

本文是介绍众多平衡二叉搜索树(BBST)的第一篇——介绍AVL树。故先来引入BBST的概念。由于上一篇介绍的二叉搜索树(BST)在极度退化的情况下,十分不平衡,不平衡到只朝一侧偏,成为一条链表,复杂度可达 O ( n ) O(n) O(n),所以我们要在“平衡”方面做一些约束,以防我们的树结构退化得那么严重。

具体来说,含 n n n个节点,高度为 h h h的BST,若满足 h = O ( l o g 2 n ) h=O(log_2 n) h=O(log2n),则称为称为平衡二叉搜索树。

01.AVL树

AVL树是一种BBST(稍后会证明)。它约束自己是否平衡,主要靠一个指标——平衡因子。定义:平衡因子=左子树高度-右子树高度。如果满足 − 2 < 全部平衡因子 < 2 -2<全部平衡因子<2 2<全部平衡因子<2,则该AVL树处于平衡状态;否则,需要靠一系列措施,将其恢复平衡。

首先先证明AVL树满足BBST的要求,即 h = O ( l o g 2 n ) h=O(log_2 n) h=O(log2n)(下式)。我们可转而证明n=Ω(Φh)(即,AVL的节点数不会太少)
C++数据结构之平衡二叉搜索树(一)——AVL的实现(zig与zag/左右双旋/3+4重构),THU数据结构,c++,数据结构,重构

[结论] 高度为 h h h的AVL Tree 至少有 f i b ( ( h + 3 ) − 1 fib((h+3)-1 fib((h+3)1 个节点
[证明]
C++数据结构之平衡二叉搜索树(一)——AVL的实现(zig与zag/左右双旋/3+4重构),THU数据结构,c++,数据结构,重构

C++数据结构之平衡二叉搜索树(一)——AVL的实现(zig与zag/左右双旋/3+4重构),THU数据结构,c++,数据结构,重构
C++数据结构之平衡二叉搜索树(一)——AVL的实现(zig与zag/左右双旋/3+4重构),THU数据结构,c++,数据结构,重构

02.AVL的插入

插入一个节点会导致一串祖先的失衡,删除一个节点至多导致一个祖先失衡。但是,通过后续代码就可发现,删除节点比插入节点复杂的多。原因是,插入节点只要调整好了一处,这条路径上的所有祖先都可平衡,复杂度是O(1)。而删除节点是,调整好了一处平衡,另一处就会不平衡,自下而上层层调整,复杂度是O(n)

2.1单旋——zig 与 zag

zig 与 zag 分别对应右单旋和左单旋。单旋的操作改变的是两个节点的相对位置。改变的是三条线:一上一下一子树。新树根上行指向原根,新树根原子树给到原根。如下图,V到Y那去,Y到C那去。

C++数据结构之平衡二叉搜索树(一)——AVL的实现(zig与zag/左右双旋/3+4重构),THU数据结构,c++,数据结构,重构

2.2插入节点后的单旋实例

在下图C++数据结构之平衡二叉搜索树(一)——AVL的实现(zig与zag/左右双旋/3+4重构),THU数据结构,c++,数据结构,重构处添加一个节点,自上而下更新高度(或平衡因子),g会率先进入不平衡状态。观察g,p,v呈一条线,而非“之”字,所以用单旋调整(之字形对应双旋)。具体来说,对g左单旋。
C++数据结构之平衡二叉搜索树(一)——AVL的实现(zig与zag/左右双旋/3+4重构),THU数据结构,c++,数据结构,重构

2.3手玩小样例

例题:将1,2,3,4,5,6依次插入空的AVL Tree,最终AVL Tree长成什么样?

[过程]首先正常插入1,2;插入3时,1是第一个发现不平衡的节点,zag(1),即对1进行左单旋,成功解决;正常插入4
C++数据结构之平衡二叉搜索树(一)——AVL的实现(zig与zag/左右双旋/3+4重构),THU数据结构,c++,数据结构,重构

插入5时,3是第一个发现不平衡的节点,zag(3),即对3进行左单旋,成功解决
C++数据结构之平衡二叉搜索树(一)——AVL的实现(zig与zag/左右双旋/3+4重构),THU数据结构,c++,数据结构,重构
插入6时,2是第一个发现不平衡的节点,zag(2),即对2进行左单旋,成功解决
C++数据结构之平衡二叉搜索树(一)——AVL的实现(zig与zag/左右双旋/3+4重构),THU数据结构,c++,数据结构,重构

2.4双旋实例

双旋的操作改变的是三个节点的相对位置。分为两种情况——zig-zag与zag-zig。

在下图C++数据结构之平衡二叉搜索树(一)——AVL的实现(zig与zag/左右双旋/3+4重构),THU数据结构,c++,数据结构,重构处添加一个节点,自上而下更新高度(或平衡因子),g会率先进入不平衡状态。观察g,p,v呈“之”字,所以用双旋。具体来说,先zig§,再zag(g).
C++数据结构之平衡二叉搜索树(一)——AVL的实现(zig与zag/左右双旋/3+4重构),THU数据结构,c++,数据结构,重构

2.5小结

AVL树中插入节点引发失衡,经旋转调整后重新平衡,此时包含节点g,p,v的子树高度是不变的子树高度复原,更高祖先也必平衡,全树复衡。故在AVL树中修正插入节点引发的失衡不会出现失衡传播。

03.AVL的删除

删除一个节点至多导致一个祖先失衡。

3.1单旋删除

C++数据结构之平衡二叉搜索树(一)——AVL的实现(zig与zag/左右双旋/3+4重构),THU数据结构,c++,数据结构,重构

3.2双旋删除

C++数据结构之平衡二叉搜索树(一)——AVL的实现(zig与zag/左右双旋/3+4重构),THU数据结构,c++,数据结构,重构

3.3小结

AVL树中删除节点引发失衡,经旋转调整后重新平衡,此时包含节点g,p,v的子树高度有可能不变也有可能减小1,故在AVL树中修正删除节点引发的失衡有可能出现失衡传播。

04.3+4重构

通过观察以上插入和删除的结果示意图,发现结构是一样的——三个节点按顺序呈三角形,四个子树按原来的顺序分别挂在两个孩子节点的下边。(如下图)
C++数据结构之平衡二叉搜索树(一)——AVL的实现(zig与zag/左右双旋/3+4重构),THU数据结构,c++,数据结构,重构

那我们就不必关注具体的技巧了,而是将三个节点和四个子树拆开,像暴力组装魔方那样(先拆散)拼上。

template <typename T>
BinNode<T> * BST<T>::connect34(BinNode<T> * a, BinNode<T> * b, BinNode<T> * c, BinNode<T> * T1, BinNode<T> * T2, BinNode<T> *T3, BinNode<T> * T4)
{
	b->left = a;  b->right = c;
	a->left = T1; a->right = T2;
	c->left = T3; c->right = T4;

	a->parent = b; c->parent = b;

	if (T1) T1->parent = a;
	if (T2) T2->parent = a;
	if (T3) T3->parent = c;
	if (T4) T4->parent = c;
	a->updateHigh(); b->updateHigh(); c->updateHigh();
	return b;
}

template <typename T>
BinNode<T> * BST<T>::rotateAt(BinNode<T> * v)
{
	BinNode<T> * p = v->parent;
	BinNode<T> * g = p->parent;
	BinNode<T> * T1, *T2, *T3, *T4, *a, *b, *c;

	if (p == g->left && v == p->left)
	{
		a = v; b = p; c = g;
		T1 = v->left; T2 = v->right; T3 = p->right; T4 = g->right;
	}
	else if (p == g->left && v == p->right)
	{
		a = p; b = v; c = g;
		T1 = p->left; T2 = v->left; T3 = v->right; T4 = g->right;
		
	}	
	else if (p == g->right && v == p->left)
	{
		a = g; b = v; c = p;
		T1 = g->left; T2 = v->left; T3 = v->right; T4 = p->right;
	}
	else
	{
		a = g; b = p; c = v;
		T1 = g->left; T2 = p->left; T3 = v->left; T4 = v->right;
	}
	b->parent = g->parent; //向上链接
	return connect34(a, b, c, T1, T2, T3, T4);

}

05.综合评价AVL

5.1优点

  1. 查找、插入、删除,最坏时间复杂度为 O ( l o g n ) O(logn) O(logn)
  2. O ( n ) O(n) O(n)的存储空间

5.2缺点

  1. 需要额外维护高度或平衡因子这一指标(后续Splay Tree可改善这一问题)
  2. 删除操作后,最多需旋转 Ω ( l o g n ) \Omega(logn) Ω(logn)
  3. 单次动态调整后,全树拓扑结构的变化量可能高达 Ω ( l o g n ) \Omega(logn) Ω(logn) (RedBlack Tree可缩到 O ( 1 ) O(1) O(1)

谢谢观看~

06.代码

注意

  1. fromParentTo()是根节点的情况
  2. connect34()向上链接别忘

插入算法

为什么不用现成的BST::insert(val)? BST::insert自带更新一串高度,旋转调整之后还得把这一串更新回来。

BinNode<T> * insert(T const & val)
		{
			BinNode<T> * & X = BST<T>::search(val);
			if (!X)
			{
				X = new BinNode<T>(val, BST<T>::hot); 
				BinTree<T>::size++;
				BinNode<T> * X_copy = X;

				while (X_copy && AvlBalanced(X_copy))
				{
					X_copy->updateHigh();
					X_copy = X_copy->parent;
				}

				if (X_copy) //说明是因为遇到了不平衡节点才退出了while,现在解决不平衡问题
				{
					BinNode<T> * & tmp = BinTree<T>::fromParentTo(X_copy);
					tmp = BST<T>::rotateAt(tallerChild(tallerChild(X_copy))); // 内部自带单个节点更新高度
				}
				return X;
			}
		}

删除算法

受限于BST::remove的返回值仅仅是bool,所以用底层的removeAt. removeAt的返回值是接替者,但有时,接替者是NULL。还好有BST::hot,存放被删节点的父亲。实际上,BST::remove的更新高度也是从hot开始的

bool remove(T const & val) 
		{
			BinNode<T> * & X = BST<T>::search(val);
			if (!X) return false;
			else
			{
				
				BST<T>::removeAt(X, BST<T>::hot);
				BinTree<T>::size--;

				// 与insert不同的是,remove可能要调整很多次
				for (BinNode<T> * g = BST<T>::hot; g; g = g->parent)
				{
					int i = BF(g);
					if (!AvlBalanced(g))
					{
						BinNode<T> * & tmp = BinTree<T>::fromParentTo(g);
						tmp = BST<T>::rotateAt(tallerChild(tallerChild(g))); 
					}
					else g->updateHigh();
				}
				return true;
			}
		}

完整代码:AVL.h

# pragma once
# include "BST.h"

# define BF(x) (int)(getHigh(x->left) - getHigh(x->right))
# define AvlBalanced(x)  ( -2 < BF(x) && BF(x) < 2 )

template <typename T>
BinNode<T> * tallerChild(BinNode<T> * x)
{
	return  (getHigh(x->left) > getHigh(x->right)) ? x->left : x->right;
}

template <typename T>
class AVL :public BST<T>
{
	public:
		bool remove(T const & val) 
		{

			BinNode<T> * & X = BST<T>::search(val);
			if (!X)  return false;
			else
			{
				
				BST<T>::removeAt(X, BST<T>::hot);
				BinTree<T>::size--;

				// (可优化:直到到某祖先,高度不变,停止上行。那就要在刚刚更新高度时记录中途退出的位置,以便在此处判断)
				for (BinNode<T> * g = BST<T>::hot; g; g = g->parent)
				{
					int i = BF(g);
					if (!AvlBalanced(g))
					{
						BinNode<T> * & tmp = BinTree<T>::fromParentTo(g);
						tmp = BST<T>::rotateAt(tallerChild(tallerChild(g))); // 内部自带单个节点更新高度
					}
					else g->updateHigh();
				}
				return true;
			}
		}
		BinNode<T> * insert(T const & val)
		{
			BinNode<T> * & X = BST<T>::search(val);
			if (!X)
			{
				X = new BinNode<T>(val, BST<T>::hot); //这一句话将两个关系连接
				BinTree<T>::size++;
				BinNode<T> * X_copy = X;

				while (X_copy && AvlBalanced(X_copy))
				{
					X_copy->updateHigh();
					X_copy = X_copy->parent;
				}

				if (X_copy) //说明是因为遇到了不平衡节点才退出了while,现在解决不平衡问题
				{
					BinNode<T> * & tmp = BinTree<T>::fromParentTo(X_copy);
					tmp = BST<T>::rotateAt(tallerChild(tallerChild(X_copy))); // 内部自带单个节点更新高度
				}

				return X;
				
			}
		}
};

感谢观看~

附上前传:
C++数据结构之BinaryTree(二叉树)的实现
C++数据结构之BST(二叉搜索树)的实现文章来源地址https://www.toymoban.com/news/detail-636886.html

到了这里,关于C++数据结构之平衡二叉搜索树(一)——AVL的实现(zig与zag/左右双旋/3+4重构)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】AVL(平衡二叉搜索树)树的原理及实现

    文章目录 一、引言 二、AVL树的概念 三、AVL树的插入 3、1 AVL树的简单插入 3、2 旋转分类  3、2、1 新节点插入较高右子树的右侧:左单旋 3、2、2 新节点插入较高左子树的左侧:右单旋 3、2、3 新节点插入较高左子树的右侧:左右双旋(先左后右) 3、2、4 新节点插入较高右

    2024年02月13日
    浏览(34)
  • 数据结构奇妙旅程之二叉平衡树进阶---AVL树

    ꒰˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好,我是xiaoxie.希望你看完之后,有不足之处请多多谅解,让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如需转载还请通知˶⍤⃝˶ 个人主页:xiaoxieʕ̯

    2024年03月13日
    浏览(75)
  • C/C++数据结构(十一)—— 平衡二叉树(AVL树)

    二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。 因此,两位俄罗斯的数学家 G.M.Adelson-Velskii 和 E.M.Landis 在 1962 年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,

    2024年02月02日
    浏览(37)
  • 计算机基础--->数据结构(6)【AVL树(平衡二叉树)】

    平衡二叉树是一种特殊的二叉搜索树,他的左子树与右子树的高度差不超过1。并且左右两个子树都是一颗平衡二叉树。保持平衡的特性使得平衡二叉树的查询、插入和删除操作在平均和最坏情况下的时间复杂度都是 O(logn) ,其中n是树中节点的数量。 相比于普通的二叉搜索树

    2024年02月12日
    浏览(45)
  • 【数据结构】7.平衡搜索树(AVL树和红黑树)

    对于普通的搜索树,如果一直插入比第一个元素小的元素,它会退化成一个无限向左下角眼神的单链表,使得时间复杂度退化为O(n)。如果我们在插入时保持树的结构是平衡的,则可以保证查找、插入和删除的时间复杂度有对数级的时间性能,下面讲到的AVL树和红黑树都是平衡

    2024年02月08日
    浏览(31)
  • 数据结构之进阶二叉树(二叉搜索树和AVL树、红黑树的实现)超详细解析,附实操图和搜索二叉树的实现过程图

    绪论​ “生命有如铁砧,愈被敲打,愈能发出火花。——伽利略”;本章主要是数据结构 二叉树的进阶知识,若之前没学过二叉树建议看看这篇文章一篇掌握二叉树,本章的知识从浅到深的 对搜索二叉树的使用进行了介绍和对其底层逻辑的实现进行了讲解 ,希望能对你有所

    2024年02月04日
    浏览(36)
  • 数据结构:搜索二叉树 | 平衡二叉树

    博客写的代码都放在这里:gitee仓库链接 1.二叉搜索树 1.1.基本概念 二叉搜索树又称二叉排序树, 可以为空,如果不为空具有以下性质的二叉树 : 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值 若它的右子树不为空,则右子树上所有节点的值都大于根节点的

    2024年01月23日
    浏览(45)
  • 【C++庖丁解牛】自平衡二叉搜索树--AVL树

    🍁你好,我是 RO-BERRY 📗 致力于C、C++、数据结构、TCP/IP、数据库等等一系列知识 🎄感谢你的陪伴与支持 ,故事既有了开头,就要画上一个完美的句号,让我们一起加油 前面对map/multimap/set/multiset进行了简单的介绍,在其文档介绍中发现,这几个容器有个共同点是:其底层都

    2024年04月09日
    浏览(84)
  • 算法与数据结构--二叉搜索树与自平衡二叉搜索树

    注:字典的 \\\"member运算\\\" 指的是检查字典中是否存在某个特定的键的操作,即查询操作。 如果我们使用数组来实现字典/map,虽然使用二分法查询也可以达到logn,但是的话插入和删除太慢了。使用链表实现的话虽然插入和删除是O(1),但是查询的话达到了O(n),也不可取。 因此人

    2024年02月04日
    浏览(27)
  • 数据结构与算法-基础(十)平衡二叉搜索树

    摘要 二叉搜索树的特性-节点的左侧部分比它小,右侧部分比它大,使得二叉搜索树在查找节点有 二分法 的效果,也提高了它的添加和删除处理,毕竟添加和删除也是先查找位置,然后再处理。 平衡二叉搜索树 就是持续保证这样的高效性,进入正题: 二叉搜索树 在添加或

    2024年02月08日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包