【C++】AVL树

文章目录

  • 前言
  • 一、AVL树的概念
  • 二、AVL树的操作
    • 1.AVL树节点的定义
    • 2.AVL树的插入
    • 3.AVL树的旋转
      • (1) 左单旋
      • (2) 右单旋
      • (3) 先左单旋再右单旋
      • (4) 先右单旋再左单旋
      • (5)旋转总结
    • 4.AVL树的删除
  • 三、AVL树的验证
  • 四、AVL树的性能
  • 五、AVL树的代码实现
    • 1.AVLTree.h
    • 2.Test.cpp

前言

之前我们对map/multimap/set/multiset进行了简单的介绍,我们发现,这几个容器有个共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。

一、AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
  • 通过引入平衡因子来控制AVL树的左右子树的高度差,平衡因子 = 右子树高度 - 左子树高度
    【C++】AVL树_第1张图片

如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在logN,搜索时间复杂度O(logN)

二、AVL树的操作

1.AVL树节点的定义

和二叉搜索树不同,AVL树我们需要增加一个bf变量即平衡因子来控制树的高度,同时,我们需要将树的节点定义为三叉连结构,即增加一个节点指针指向该节点的父节点,这是为了方便后面插入节点时修改父节点的平衡因子

template<class K,class V>
struct AVLTreeNode
{
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;   // 该节点的左孩子
	AVLTreeNode<K, V>* _right;  //该节点的右孩子
	AVLTreeNode<K, V>* _parent;   // 该节点的双亲
	int _bf;    // balance factor

	AVLTreeNode(const pair<K,V>& kv)
		:_kv(kv)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_bf(0)
	{}
};

2.AVL树的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:

  • 按照二叉搜索树的方式插入新节点
  • 调整节点的平衡因子

AVL树的插入和前面我们二叉搜索树的插入相似–比当前节点大就往右边走,比当前节点小就往左边走,相等就插入失败,走到空位置就进行插入,不够二叉搜索树插入的是键值key,而我们这里插入的是键值对pair,所以比较的时候是使用cur->_kv.first和kv._first进行比较,同时,在链接节点的时候需要注意修改父节点的指针的指向,因为AVL树是三叉链结构

新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了AVL树的平衡性

cur插入后,parent的平衡因子一定需要调整,在插入之前,parent的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:

1.如果cur插入到parent的左侧,只需给parent的平衡因子-1即可

2.如果cur插入到parent的右侧,只需给parent的平衡因子+1即可

此时:parent的平衡因子可能有三种情况:0,正负1, 正负2

1.如果parent的平衡因子为0,说明插入之前parent的平衡因子为正负1,左右子树的盖度不同,插入后被调整成0,说明此次插入插入的是矮的那一边,此时子树的整体高度不变,此时满足AVL树的性质,插入成功,不需要继续向上继续更新祖先节点的平衡因子

2.如果parent的平衡因子为正负1,说明插入前parent的平衡因子一定为0,左右子树的高度相同,插入后被更新成正负1,此时以parent为根的树的高度增加,需要继续向上更新祖先节点的平衡因子,且最多会更新到根节点的平衡因子

3.如parent的平衡因子为正负2,则parent的平衡因子违反平衡树的性质,需要对其进行旋转处理

我们在更新祖先节点的平衡因子过程中,如果祖先节点的平衡因子变为0或者更新到了根节点就停止更新

我们以以下例子进行说明:

【C++】AVL树_第2张图片

【C++】AVL树_第3张图片

【C++】AVL树_第4张图片

第一部分按照二叉搜索进行插入代码:

bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(kv);
		if (parent->_kv.first < cur->_kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
    // 1、更新平衡因子
		while (parent) // parent为空,也就更新到根
		{
			// 新增在右,parent->bf++;
			// 新增在左,parent->bf--;
			if (parent->_left == cur)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}

			// 是否继续更新依据:子树的高度是否变化
			// 1、parent->_bf == 0说明之前parent->_bf是 1 或者 -1
			// 说明之前parent一边高一边低,这次插入填上矮的那边,parent所在子树高度不变,不需要继续往上更新
			// 2、parent->_bf == 1 或 -1 说明之前是parent->_bf == 0,两边一样高,现在插入一边更高了,
			// parent所在子树高度变了,继续往上更新
			// 3、parent->_bf == 2 或 -2,说明之前parent->_bf == 1 或者 -1,现在插入严重不平衡,违反规则
			// 就地处理--旋转

			// 旋转:
			// 1、让这颗子树左右高度不超过1
			// 2、旋转过程中继续保持他是搜索树
			// 3、更新调整孩子节点的平衡因子
			// 4、让这颗子树的高度跟插入前保持一致
            if (parent->_bf == 0)
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				// 旋转
                //...
            }
            
            return true;
        }
}

3.AVL树的旋转

当某一个节点的平衡因子为-2/2时,我们需要以这个节点为根节点的子树进行旋转,让这棵树重新变成为AVL树,旋转的目标如下:

1、让这颗子树左右高度不超过1
2、旋转过程中继续保持他是搜索树
3、更新调整孩子节点的平衡因子
4、让这颗子树的高度跟插入前保持一致,从而不会影响上层,旋转结束

根据节点插入位置的不同,AVL树的旋转可以归为一下四类:

1.左单旋 : 新节点插入较高右子树的右侧–右右

2.右单旋 : 新节点插入较高左子树的左侧–左左

3.先右单旋再左单旋 : 新节点插入较高左子树的右侧 – 左右

4.先右单旋再左单旋 : 新节点插入较高右子树的左侧 – 右左

下面我们一次进行讨论上面这四类情况:

(1) 左单旋

左单旋的抽象图如下:其中a/b/c都是高度为h(h>=0)的三棵子树,30是这棵子树的根,当满足"右子树比左子树高1且在右子树的右边插入节点时–右右(右边高右边插入)"时,我们需要进行左单旋,左单旋的过程如下:让根的右子树(SubR–60)的左子树(SubRL–b)链接到根的右子树,让根链接到右子树(SubR)的左子树,让右子树称为根,将根节点和右子树的根节点置0即可

【C++】AVL树_第5张图片

我们可以这样理解,因为右子树太高了,所以需要将30往下亚,让60来做根,因为右子树的左子树比60,比30及其左子树大,所以链接到30的右边,30及其左子树都比60小,所以链接到原来60的左边,保持了搜索树的规则(大小关系),旋转后原来的根节点和现在的根节点的平衡因子都变为0,插入前和插入后子树的整体高度都为h+1,不会对上面的高度造成影响

我们需要注意的是,上面我们给出的是抽象图,也就是说它可以表示无数种情况,只是这些情况可以被归为一类,处理的方式相同,下面给出几种具体的图,让我们更好的进行理解

h==0

【C++】AVL树_第6张图片

h==1

【C++】AVL树_第7张图片

h==2

【C++】AVL树_第8张图片

代码实现如下:

// 左旋转
void RotateL(Node* parent)
{
	// 右子树
	Node* subR = parent->_right;
	// 右子树的左子树
	Node* subRL = subR->_left;

	// 右子树的左子树链接到parent的右边
	parent->_right = subRL;

	// SubR可能为空
	if (subRL)
		subRL->_parent = parent;

	// 保存parent的父节点
	Node* ppNode = parent->_parent;
	//parent链接到右子树的左边
	subR->_left = parent;
	parent->_parent = subR;

	// 让SubR成为子树的根
	// 如果parent的parent为空,那么parent是整棵树的根节点,此时SubR成为新的根节点
	if (ppNode == nullptr)
	{
		_root = subR;
		_root->_parent = nullptr;
	}
	else
	{
		if (ppNode->_left == parent)
		{
			ppNode->_left = subR;
		}
		else
		{
			ppNode->_right = subR;
		}

		subR->_parent = ppNode;
	}

	// 更新平衡因子
	parent->_bf = subR->_bf = 0;
}

代码实现时要比我们上面分析要复杂得多,因为要考虑父节点指针的执行问题,h==0的情况,parent是整棵树的根节点与子树根节点的情况

(2) 右单旋

右单旋的抽象图如下,当满足"左子树比右子树高1且左子树插入节点时–左左(左边高且左边插入)",此时进行向右旋转

【C++】AVL树_第9张图片

上图在插入前,AVL树是平衡的,新节点插入到30的左子树(注意:此处不是左孩子)中,30左子树增加了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子树增加一层,即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点的平衡因子即可。在旋转过程中,有以下几种情况需要考虑:

1.30节点的右孩子可能存在,也可能不存在

2.60可能是根节点,也可能是子树

如果是根节点,旋转完成后,要更新根节点

如果是子树,可能是某个节点的左子树,也可能是右子树

代码实现:

// 右旋转
void RotateR(Node* parent)
{
	// 左子树
	Node* subL = parent->_left;
	// 左子树的右子树
	Node* subLR = subL->_right;

	// 将左子树的右子树链接到根的左边
	parent->_left = subLR;
	// 不为空才进行链接h==0时为空
	if (subLR)
	{
		subLR->_parent = parent;
	}

	// 记录parent的parent节点
	Node* ppNode = parent->_parent;
	// 将根链接到左子树的右边
	subL->_right = parent;
	parent->_parent = subL;

	// 让SubL成为子树的根
	//if (_root == parent)
	// 为空说明parent就是整棵树的根节点
	if (ppNode == nullptr)
	{
		_root = subL;
		_root->_parent = nullptr;
	}
	else
	{
		if (ppNode->_left == parent)
		{
			ppNode->_left = subL;
		}
		else
		{
			ppNode->_right = subL;
		}

		subL->_parent = ppNode;
	}

	// 更新平衡因子
	subL->_bf = parent->_bf = 0;
}

(3) 先左单旋再右单旋

前面的两种情况是插入节点之后三个节点是一条直线,但是我们新插入节点在上面插入节点的另外一遍进行插入呢,此时它就变成了一个折线了,如下:

【C++】AVL树_第10张图片

我们可以看到,如果插入后形成的是一条直线,即在较高右子树的右侧插入或者在较高左子树的左侧进行插入,那么我们只需要进行一次左单旋或者右单旋就可以解决问题,但是如果插入之后是一条折线,即在较高右子树的左侧插入或者在较高左子树的右侧进行插入,此时我们进行单纯的左单旋或者右单旋并不能够解决问题

如上图,情况3经过左单旋之后变成了情况4,情况4经过右单旋之后变成了情况3,所以我们需要先将折线变成一条直线,使之变成情况1或者情况2 ,再使用左单旋或者右单旋,从而将其的高度降下来。所以此时我么需要左单旋和右单旋配合从而使得子树变成AVL树,即进行左右双旋或者右左双旋

左右双旋的抽象图如下,其中a/d是高度为h的AVL树,b/c是高度为h-1的AVL树,90是这棵树的根,当满足"左子树比右子树高1且在左子树的右侧进行插入节点时–左右(左边高右边插)",我们就先进行左单旋,再进行右单旋

思路:将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再考虑平衡因子的更新。

【C++】AVL树_第11张图片

我们下面画出具体的图来更好的理解:

h==0

【C++】AVL树_第12张图片

h==0时60就新增

h==1

【C++】AVL树_第13张图片

h==2

【C++】AVL树_第14张图片

关于旋转,我们可以直接在左右双旋中调用左单旋和右单旋即可,但是真正难的地方在于平衡因子的更新,我们不能依靠左单旋和右单旋的代码逻辑来更新左右双旋的平衡因子,因为他们是直接将平衡因子变为了0,对于双旋中平衡因子我们需要单独进行处理

为了更好的理解平衡因子的变化,我们可以不将他划分为先左旋再右旋两步,而是直接将这个过程看做一个步骤,相当于60的左子树被链接到30的右子树,60的右子树链接到90的左子树,然后30和90及其子树分为成为了60的左右子树,那么60的平衡因子不管怎样都是0,但是30和90的平衡因子会被分为3种情况:

1.当新节点插入到60的左子树,旋转后60的平衡因子为0,30的平衡因子也为0,90的平衡因子为1,因为60的右子树高度没有变化,而它最终被链接到了90的左子树

2.当新节点插入到60的右子树时,旋转后60的平衡因子 为0,30的平衡因子为-1,90的平衡因子为0,因为60的左子树的高度位于+1,而它最终被链接到30的右子树

3.当h==0时,此时60自己为新增节点,旋转后30,60,90的平衡因子均为0

我们是如何判断插入是属于上面的哪一种情况呢,其实是通过判断旋转前60的平衡印象的值来进行判断–60的平衡因子为0,说明60本身就是新增节点,属于情况3,如果60的平衡因子为-1,说明在60的左子树进行插入,属于情况1,如果60 的平衡因子为1,说明在60的右子树进行插入,属于情况2

代码如下:

// 左右双旋
void RotateLR(Node* parent)
{
	Node* subL = parent->_left;  // 左子树30
	Node* subLR = subL->_right;  // 左子树的右子树60
	int bf = subLR->_bf;   // 记录SubLR的平衡因子

	// 先以左子树为轴进行左单旋
	RotateL(parent->_left);
	// 再进行右单旋
	RotateR(parent);

	if (bf == -1) // subLR左子树新增
	{
		subL->_bf = 0;
		parent->_bf = 1;
		subLR->_bf = 0;
	}
	else if (bf == 1) // subLR右子树新增
	{
		parent->_bf = 0;
		subL->_bf = -1;
		subLR->_bf = 0;
	}
	else if (bf == 0) // subLR自己就是新增
	{
		parent->_bf = 0;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

(4) 先右单旋再左单旋

右左双旋的抽象图如下,当满足"右子树比左子树高1且在右子树的左侧进行插入节点时–右左(右边高左边插)",此时就进行右单旋,再进行左单旋:

【C++】AVL树_第15张图片

代码如下:

// 右左双旋
void RotateRL(Node* parent)
{
	Node* SubR = parent->_right;  //左子树60
	Node* SubRL = SubR->_left;// 右子树的左子树90
	int bf = SubRL->_bf;// 记录SubRLd 平衡因子

	// 先以SubR为轴进行右单旋
	RotateR(parent->_right);
	// 再进行左单旋
	RotateL(parent);

	// subRL左子树新增
	if (bf == -1)
	{
		parent->_bf = 0;
		SubRL->_bf = 0;
		SubR->_bf = 1;
	}
	// subRL右子树新增
	else if (bf == 1)
	{
		parent->_bf = -1;
		SubRL->_bf = 0;
		SubR->_bf = 0;
	}
	// subRL自己就是新增
	else if (bf == 0)
	{
		parent->_bf = 0;
		SubRL->_bf = 0;
		SubR->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

(5)旋转总结

总结:

假如以parent为根的子树不平衡,即parent的平衡因子为2或者-2,分以下情况考虑

1.parent的平衡因子为2,说明parent的右子树高,设parent的右子树的根为SubR

当SubR的平衡因子为1时,执行左单旋

当SubR的平衡因子为-1时,执行右左双旋

2.parent的平衡因子为-2,说明parent的左子树高,设parent的左子树的根为SubL

当SubL的平衡因子为-1是,执行右单旋

当SubL的平衡因子为1时,执行左右双旋

旋转完成后,原parent为根的子树个高度降低,已经平衡,不需要再向上更新4

4.AVL树的删除

因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不过与二叉搜索树删除不同的是,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置

删除一个分为三种情况:

1.删除节点的左边为空,则托孤直接删除

2.删除节点的右边为空,则托孤直接删除

3.删除节点的左右都不为空,则替换删除(替换左边的最大节点或者右边的最小节点进行删除)

删除之后我们也需要更新父节点的平衡因子,只是删除后父节点及其祖先节点的平衡因子变化和插入节点时平衡因子的变化是相反的,删除左孩子时父节点的平衡因子应该++,删除右还在父节点的陪你过后因子应该–

如果祖先的平衡因子变成了2/-2后,则需要进行旋转,旋转仍然分为四种:左单旋,右单旋,左右双旋,右左双旋

所以AVL树的删除比AVL树的删除更复杂一点,但是原理基本相同,所以我们作为了解即可。具体实现学生们可参考《算法导论》或《数据结构-用面向对象方法与C++描述》殷人昆版。

三、AVL树的验证

我们自己实现了AVL树之后,需要对其进行验证这棵树是否为AVL树,我们需要从下面两个部分来进行验证:

1.验证是否为二叉搜索树

2.验证二叉搜索树是否平衡

对于第一个条件很简单,我们只需要向树中插入数据,然后进行中序遍历,看得到的数据是否有序

void InOrder()
{
	_InOrder(_root);
}

void _InOrder(Node* root)
{
	if (root == nullptr)
		return;

	_InOrder(root->_left);
	cout << root->_kv.first << ":" << root->_kv.second << endl;
	_InOrder(root->_right);
}

验证是否为平衡二叉树,我们需要求出每个节点的左右子树的高度,看他们的差值是否为-1/0/1

int Height(Node* root)
{
	if (root == nullptr)
		return 0;

	int lh = Height(root->_left);
	int rh = Height(root->_right);

	return lh > rh ? lh + 1 : rh + 1;
}

bool IsBalance()
{
	return _IsBalance(_root);
}

bool _IsBalance(Node* root)
{
	if (root == nullptr)
		return true;

	int leftHight = Height(root->_left);
	int rightHight = Height(root->_right);

	if (rightHight - leftHight != root->_bf)
	{
		cout << "平衡因子异常" << endl;
		return false;
	}

	return abs(rightHight - leftHight) < 2
		&& _IsBalance(root->_left)
		&& _IsBalance(root->_right);
}

我们除了使用一个少量的数据进行验证,还应该使用大量的随机数来进行验证,如果随机数没有问题,那么我们写的AVL树大概就没有问题了

四、AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,所以AVL树是无限接近于满二叉树,这样可以保证查询时高效的时间复杂度,即O(logN)。

但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合

五、AVL树的代码实现

我们这里只给出了AVL部分的 代码,包括AVL树的插入和旋转的代码。

1.AVLTree.h

#pragma once
#include 
#include 

template<class K,class V>
struct AVLTreeNode
{
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;   // 该节点的左孩子
	AVLTreeNode<K, V>* _right;  //该节点的右孩子
	AVLTreeNode<K, V>* _parent;   // 该节点的双亲
	int _bf;    // balance factor

	AVLTreeNode(const pair<K,V>& kv)
		:_kv(kv)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_bf(0)
	{}
};

template<class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(kv);
		if (parent->_kv.first < cur->_kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		// 1、更新平衡因子
		while (parent) // parent为空,也就更新到根
		{
			// 新增在右,parent->bf++;
			// 新增在左,parent->bf--;
			if (parent->_left == cur)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}

			// 是否继续更新依据:子树的高度是否变化
			// 1、parent->_bf == 0说明之前parent->_bf是 1 或者 -1
			// 说明之前parent一边高一边低,这次插入填上矮的那边,parent所在子树高度不变,不需要继续往上更新
			// 2、parent->_bf == 1 或 -1 说明之前是parent->_bf == 0,两边一样高,现在插入一边更高了,
			// parent所在子树高度变了,继续往上更新
			// 3、parent->_bf == 2 或 -2,说明之前parent->_bf == 1 或者 -1,现在插入严重不平衡,违反规则
			// 就地处理--旋转

			// 旋转:
			// 1、让这颗子树左右高度不超过1
			// 2、旋转过程中继续保持他是搜索树
			// 3、更新调整孩子节点的平衡因子
			// 4、让这颗子树的高度跟插入前保持一致

			if (parent->_bf == 0)
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				// 旋转
				if (parent->_bf == 2 && cur->_bf == 1)
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == -1)
				{
					RotateR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(parent);
				}
				else
				{
					assert(false);
				}

				break;
			}
			else
			{
				assert(false);
			}
		}

		return true;
	}

	// 左旋转
	void RotateL(Node* parent)
	{
		// 右子树
		Node* subR = parent->_right;
		// 右子树的左子树
		Node* subRL = subR->_left;

		// 右子树的左子树链接到parent的右边
		parent->_right = subRL;

		// SubR可能为空
		if (subRL)
			subRL->_parent = parent;

		// 保存parent的父节点
		Node* ppNode = parent->_parent;
		//parent链接到右子树的左边
		subR->_left = parent;
		parent->_parent = subR;

		// 让SubR成为子树的根
		// 如果parent的parent为空,那么parent是整棵树的根节点,此时SubR成为新的根节点
		if (ppNode == nullptr)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}

			subR->_parent = ppNode;
		}

		// 更新平衡因子
		parent->_bf = subR->_bf = 0;
	}


	// 右旋转
	void RotateR(Node* parent)
	{
		// 左子树
		Node* subL = parent->_left;
		// 左子树的右子树
		Node* subLR = subL->_right;

		// 将左子树的右子树链接到根的左边
		parent->_left = subLR;
		// 不为空才进行链接h==0时为空
		if (subLR)
		{
			subLR->_parent = parent;
		}

		// 记录parent的parent节点
		Node* ppNode = parent->_parent;
		// 将根链接到左子树的右边
		subL->_right = parent;
		parent->_parent = subL;

		// 让SubL成为子树的根
		//if (_root == parent)
		// 为空说明parent就是整棵树的根节点
		if (ppNode == nullptr)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}

			subL->_parent = ppNode;
		}

		// 更新平衡因子
		subL->_bf = parent->_bf = 0;
	}

	// 左右双旋
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;  // 左子树30
		Node* subLR = subL->_right;  // 左子树的右子树60
		int bf = subLR->_bf;   // 记录SubLR的平衡因子

		// 先以左子树为轴进行左单旋
		RotateL(parent->_left);
		// 再进行右单旋
		RotateR(parent);

		if (bf == -1) // subLR左子树新增
		{
			subL->_bf = 0;
			parent->_bf = 1;
			subLR->_bf = 0;
		}
		else if (bf == 1) // subLR右子树新增
		{
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else if (bf == 0) // subLR自己就是新增
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
	// 右左双旋
	void RotateRL(Node* parent)
	{
		Node* SubR = parent->_right;  //左子树60
		Node* SubRL = SubR->_left;// 右子树的左子树90
		int bf = SubRL->_bf;// 记录SubRLd 平衡因子

		// 先以SubR为轴进行右单旋
		RotateR(parent->_right);
		// 再进行左单旋
		RotateL(parent);

		// subRL左子树新增
		if (bf == -1)
		{
			parent->_bf = 0;
			SubRL->_bf = 0;
			SubR->_bf = 1;
		}
		// subRL右子树新增
		else if (bf == 1)
		{
			parent->_bf = -1;
			SubRL->_bf = 0;
			SubR->_bf = 0;
		}
		// subRL自己就是新增
		else if (bf == 0)
		{
			parent->_bf = 0;
			SubRL->_bf = 0;
			SubR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	void InOrder()
	{
		_InOrder(_root);
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}


	int Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int lh = Height(root->_left);
		int rh = Height(root->_right);

		return lh > rh ? lh + 1 : rh + 1;
	}

	bool IsBalance()
	{
		return _IsBalance(_root);
	}

	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
			return true;

		int leftHight = Height(root->_left);
		int rightHight = Height(root->_right);

		if (rightHight - leftHight != root->_bf)
		{
			cout << "平衡因子异常" << endl;
			return false;
		}

		return abs(rightHight - leftHight) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}

private:
	Node* _root = nullptr;
};

2.Test.cpp

#define _CRT_SECURE_NO_WARNINGS 1

#include 
using namespace std;

#include "AVLTree.h"

void TestAVLTree1()
{
	//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	AVLTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(make_pair(e, e));
	}

	t.InOrder();

	cout << t.IsBalance() << endl;
}

void TestAVLTree2()
{
	srand(time(0));
	const size_t N = 100000;
	AVLTree<int, int> t;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand();
		t.Insert(make_pair(x, x));
		//cout << t.IsBalance() << endl;
	}

	//t.Inorder();

	cout << t.IsBalance() << endl;
}
int main()
{
	//TestAVLTree1();
	TestAVLTree2();
	return 0;
}

你可能感兴趣的:(C++,c++,数据结构,算法,开发语言)