平衡搜索树之AVLTree

今天我想要在这里写下个人觉得比较难的数据结构---AVL树的一些些心得。

一。了解一种新的数据结构,首先要搞懂它的定义

AVL树又称为高度平衡的二叉搜索树,是1962年有俄罗斯的数学家G.M.Adel'son-Vel'skii和E.M.Landis提出来的。它能保持二叉树的高度
平衡,尽量降低二叉树的高度,减少树的平均搜索长度。所以严格点来说,对于一棵搜索二叉树,能达到O(logn)的只是AVL树,因为他对于二叉树的深度控制的最为严格
,那么这是为什么呢?让我们来看看AVL树的性质

  1. 左子树和右子树的高度之差的绝对值不超过1
  2.  树中的每个左子树和右子树都是AVL树
  3.  每个节点都有一个平衡因子(balance factor--bf),任一节点的平衡因子是-1,0,1。(每个节点的平衡因子等于右子树的高度减去左子树的高度 )

整棵二叉树的每个节点都要满足这三个条件才算是一棵AVL树。其中第三个条件是核心,可以说AVL树是围绕平衡因子建树的。

在这里要说一说AVL树的优点

我们先来看看二叉搜索树吧(因为AVL树本质上是一棵二叉搜索树),假设有这么一种极端的情况:二叉搜索树的结点为0 1、2、3、4,也就是:


   聪明的你是不是发现什么了呢?显而易见——这棵二叉搜索树其实等同于一个链表了,也就是说,它在查找上的优势已经全无了——在这种情况下,查找一个结点的时间复杂度是O(N)!

好,那么假如是AVL树(别忘了AVL树还是二叉搜索树),则会是:


可以看出,AVL树的查找平均时间复杂度要比二叉搜索树低——它是O(logN)。也就是说,在大量的随机数据中AVL树的表现要好得多。


二。AVL实现

     话不多说,开始分析AVL的插入操作,在此只分析插入操作,其他删除,查找等操作与插入类似。

(一)结点结构

<span style="font-family:Microsoft YaHei;font-size:14px;">template<class K,class V>
struct AVLNode
{
	K _key;
	V _value;
	int _bf;//平衡因子
	AVLNode<K, V>* _parent;
	AVLNode<K, V>* _left;
	AVLNode<K, V>* _right;
	AVLNode(const K& key,const V& value)
		:_key(key)
		, _value(value)
		, _bf(0)
		, _parent(NULL)
		, _left(NULL)
		, _right(NULL)
	{}
};</span>

  AVL树的结点可以看作是一个三叉链,方便我们调整数的平衡。节点内有key和value,使用key建树。

(二)插入操作

<span style="font-family:Microsoft YaHei;font-size:14px;">bool Insert(const K& key, const V& value)
	{
		if (_root == NULL)
		{
			_root = new Node(key, value);
			return true;
		}
		Node* cur = _root;
		Node* parent = NULL;
		//找到要插入的地方
		while (cur)
		{
			parent = cur;
			if (cur->_key > key)
			{
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				cur = cur->_right;
			}
			else
				return false;
		}
		//插入结点
		cur = new Node(key, value);
		if (parent->_key < key)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		//更新平衡因子
		while (parent)
		{
			//插入结点后影响父结点的平衡因子改变
			if (parent->_left == cur)
				--parent->_bf;
			else
				++parent->_bf;

			//0 / 1 || -1 /2 || -2

			if (parent->_bf == 0)
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				cur = parent;
				parent = cur->_parent;
			}
			else//parent->_bf == 2 || parent->_bf == -2
			{
				if (parent->_bf == -2)
				{
					if (cur->_bf == -1)
					{
						RotateR(parent);
					}
					else//cur->_bf == 1
					{
						RotateLR(parent);
					}
				}
				else //parent->_bf == 2
				{
					if (cur->_bf == 1)
					{
						RotateL(parent);
					}
					else//cur->_bf == -1
					{
						RotateRL(parent);
					}
				}
				break;
			}
		}
		return true;
	}</span>
插入一个结点,步骤如下:

    (1)像世界上所有排序二叉树一样,把新的结点的平衡因子设置为0,然后插入当前保持AVL性质的二叉树T中。(千万注意不要插到哨兵后面去……)    

    (2).从插入的结点开始,递归向上回溯(记得判断是否到树根),过程如下:

  • 判断当前结点是父结点的左孩子还是右孩子:如果是左孩子则给父结点平衡因子+1,否则对平衡因子-1,代表当前插入结点对所在子树平衡性的影响
  •  如果父亲结点的平衡因子为0,说明插入节点后以父节点为根的子树高度没有发生变化且该子树没有失衡,直接结束处理;如果父亲结点的平衡因子在[-1,1]中,说明当前子树仍然保持平衡,但子树高度发生变化,递归向上处理父结点;否则此时以此父结点为根的子树失去平衡,需要平衡处理
  •  此过程直到遇到根直接返回原树T或者遇到失衡子树返回调整后的树T‘!之所以调平最小失衡子树后平衡调整过程就可以结束,是因为在调整失衡子树后整棵子树的高度恢复到了插入结点之前的高度,不再影响其他部分。

左单旋处理:


<span style="font-family:Microsoft YaHei;">void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		//旋转
		parent->_right = subRL;
		subR->_left = parent;

		if (subRL)
			subRL->_parent = parent;

		//链接父节点
		Node* ppNode = parent->_parent;
		parent->_parent = subR;
		if (ppNode == NULL)
		{
			_root = subR;
			subR->_parent = NULL;
		}
		else
		{
			if (ppNode->_left == parent)
				ppNode->_left = subR;
			else
				ppNode->_right = subR;
			subR->_parent = ppNode;
		}
		//更新平衡因子
		subR->_bf = parent->_bf = 0;
	}</span>
右单旋处理:



<pre name="code" class="cpp">
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;


//旋转
parent->_left = subLR;
subL->_right = parent;


if (subLR)
subLR->_parent = parent;


//链接父节点
Node* ppNode = parent->_parent;
parent->_parent = subL;
if (ppNode == NULL)
{
_root = subL;
subL->_parent = NULL;
}
else
{
if (ppNode->_left == parent)
ppNode->_left = subL;
else
ppNode->_right = subL;
subL->_parent = ppNode;
}
//更新平衡因子
subL->_bf = parent->_bf = 0;
}

 
左右双旋: 



<span style="font-family:Microsoft YaHei;font-size:14px;">void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;

		RotateL(parent->_left);
		RotateR(parent);

		//LR/1:L=1;-1:P=1;
		if (bf == 1)
		{
			//parent->_bf = 0;
			subL->_bf = 1;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			//subL->_bf = 0;
		}
		else
		{
		}
		//subLR->_bf = 0;
	}</span>
右左双旋:




<span style="font-family:Microsoft YaHei;font-size:14px;">void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		RotateR(parent->_right);
		RotateL(parent);

		//RL/1:P=-1;-1:R=1;
		if (bf == 1)
		{
			parent->_bf = -1;
			subR->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
		}
		else
		{
		}
		subRL->_bf = 0;
	}</span>

最后我们来看插入一个序列完整的旋转过程:




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值