您的位置:首页 > 理论基础 > 数据结构算法

浅谈平衡搜索树之AVL树

2017-11-05 14:21 489 查看
对于上篇的二叉搜索树,我们可以讨论一下它的时间复杂度。在最好的情况下,也就是这颗二叉搜索树是一棵完全二叉树的情况下,它的查找效率是lgN(以2为底N的对数,这里为了方便起见这样写),但是在最坏的情况下,也就是说,在插入的时候是有序插入,这颗二叉搜索树达到了以下的状态



这个时候这颗二叉搜索是一棵极度不平衡的树,它的时间复杂度是N。显然如果这颗树存了10亿个数据,那么最坏的情况要查找10亿次,而对于lgN这种算法,只需要30次就够了,两者的查找效率显而易见。所以我们在二叉搜索树的基础上,提出了二叉平衡搜索树的概念。本篇暂时只讲二叉平衡搜索树的一个分支AVL树,在下一篇我们将讲到另一个分支红黑树。

AVL树的特点

AVL树右称为高度平衡的二叉搜索树,它能够保持二叉树的高度平衡,减少搜索长度,那么它是怎么来实现的呢?

1.首先它是一棵二叉搜索树,它具有二叉搜索树所具有的所有性质。

2.左右子树的高度之差的绝对值不超过1

3.树中的每个左子树和右子树都是AVL树

4.每个节点多了一个平衡因子(_bf)的概念,它等于右子树的高度减去左子树的高度,它的绝对值不超过1(即为1,-1,0)。

若有N个数,这颗树的高度可以保持在lgN,对于插入查找删除的操作效率都是lgN。

AVL树的增(insert)

对于AVL树,增是它的重点也是它的难点,是它最难理解的地方。会了增,其实删除也是很好理解的。所以本篇我们只讨论增,以及它在增的时候是如何保持它的高度平衡的。

1.首先和二叉搜索树一样的是,一样也是比较插入的节点与当前节点的key值,找到一个为空的位置然后进行插入。

2.如果父亲的左树插入的,平衡因子_bf–;右树增加的,平衡因子 _bf++。

3.插入了节点,会影响它祖先这一条线上的高度,我们通过parent的_bf在通过调整之后的值,来作出相应的平衡调整。

①._bf变为0,说明原来的 _bf是1或-1,子树的高度没有变。



②._bf变为1或-1,说明原来的 _bf是0,子树的高度变了,需要继续向上调整。



③._bf变为2或-2,说明原来的 _bf是1或-1,这个时候就需要通过旋转来调整树的高度。



下面来仔细介绍一下旋转这种方法。

1.左旋



如上图所示,10的_bf原来是1,20的 _bf原来是0,在20的右树插入一个节点之后,10的 _bf变为了2,20的 _bf变为了1,这个时候就要以10这个节点为轴进行左旋,将b给10的右,10变成20的左,这样一旋转之后,两者的平衡因子都变成了0。由上图可知,发生左旋的条件是parent的 _bf是2,parent的右孩子的 _bf是1。

2.右旋



右旋与左旋的原理是相似的,这里就不再重复了,只是方向反了一下。发生右旋的条件是parent的_bf是-2,parent的左孩子的 _bf是-1。

3.右左双旋



顾名思义,如上图,右左双旋就是先以30为轴进行一个右旋,再以10为轴进行一个左旋。这有三种情况,一种就是在如上图在c树插入了一个节点改变了高度,另一种就是在b树插入了一个节点,还有一种是20就是新增节点,b、c都不存在。可以发现的是,进行右左双旋的条件是,parent的_bf是2,parent的右孩子的 _bf是-1。

4.左右双旋



与右左双旋类似,先进行左旋,再进行右单旋。也是有三种情况,b插入,c插入,20是新增节点。进行左右双旋的条件是,parent的_bf是-2,parent的左孩子的 _bf是1。

下面我们通过代码来实现一棵AVL树。

#pragma once

#include <iostream>
using namespace std;

#include <assert.h>

template <class K,class V>
struct AVLTreeNode
{
K _key;
V _value;
int _bf;

AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;

AVLTreeNode(const K& key, const V& value)
:_left(NULL)
, _right(NULL)
, _parent(NULL)
, _key(key)
, _value(value)
, _bf(0)
{}
};

template <class K,class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
AVLTree()
:_root(NULL)
{}

bool Insert(const K& key, const V& value)
{
if (_root == NULL)
{
_root = new Node(key, value);
return true;
}

Node* cur = _root;

4000
Node* parent = NULL;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}

cur = new Node(key, value);
//让parent的左或右指向cur,cur的parent指向parent
if (parent->_key < key)
{
parent->_right = cur;
cur->_parent = parent;
}
else
{
parent->_left = cur;
cur->_parent = parent;
}

//更新平衡因子
while (parent)
{
//左增加,_bf--
if (cur == parent->_left)
{
parent->_bf--;
}
else
//右增加,_bf++
{
parent->_bf++;
}

//根据parent更新后的平衡因子调整
if (parent->_bf == 0)
//子树高度没变,不用调整,直接退出
{
break;
}
else if (parent->_bf == 1 || parent->_bf == -1)
//子树高度变了,继续向上调整
{
cur = parent;
parent = cur->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)
//进行旋转
{
if (parent->_bf == 2)
{
if (cur->_bf == 1)
{
RotateL(parent);//左旋
}
else
{
RotateRL(parent);//右左双旋
}
}
else
{
if (cur->_bf == -1)
{
RotateR(parent);//右旋
}
else
{
RotateLR(parent);//左右双旋
}
}
//旋转之后不用调整了,直接跳出循环
break;
}
else
//走到这肯定是不平衡了,断言失败
{
assert(false);
}
}
return true;
}

bool IsBalance()
{
//return _IsBalance(_root);
int height = 0;
return _IsBalance(_root,height);
}

void Inorder()
{
_Inorder(_root);
cout << endl;
}
protected:
bool _IsBalance(Node* root)
//效率慢
{
if (root == NULL)
{
return true;
}
//右树的高度-左树的高度和bf比较
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);

return abs(rightHeight - leftHeight) < 2
&& _IsBalance(root->_left)
&& _IsBalance(root->_right);
}

bool _IsBalance(Node* root, int& height)
{
if (root == NULL)
{
height = 0;
return true;
}

int leftHeight = 0;
if (_IsBalance(root->_left, leftHeight) == false)
//只要有一个节点的平衡因子不对立马返回错误,不用再算后面的
{
return false;
}

int rightHeight = 0;
if (_IsBalance(root->_right, rightHeight) == false)
{
return false;
}

if (root->_bf != rightHeight - leftHeight)
//只要平衡因子不等于右树高度-左树高度就是错误
{
cout << "平衡因子异常" << root->_key << endl;
return false;
}

height = leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
//返回左树和右树高的那个的高度+1
return abs(leftHeight - rightHeight) < 2;
}

int _Height(Node* root)
{
if (root == NULL)
{
return 0;
}
int left = _Height(root->_left);
int right = _Height(root->_right);

return left > right ? left + 1 : right + 1;
//返回左树和右树高的那个的高度+1
}

void RotateR(Node* parent)//右旋
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
Node* grandparent = parent->_parent;

parent->_left = subLR;

if (subLR)
//如果subLR存在,则将它的父亲指向parent
{
subLR->_parent = parent;
}

subL->_right = parent;
parent->_parent = subL;

if (grandparent == NULL)
//parent是根节点
{
_root = subL;
subL->_parent = NULL;
}
else
{
if (parent == grandparent->_left)
{
grandparent->_left = subL;
}
else
{
grandparent->_right = subL;
}
subL->_parent = grandparent;
}
//更新它们的平衡因子
subL->_bf = parent->_bf = 0;
}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
Node* grandparent = parent->_parent;

parent->_right = subRL;
if (subRL)
{
subRL->_parent = parent;
}

subR->_left = parent;
parent->_parent = subR;

if (grandparent == NULL)
{
_root = subR;
subR->_parent = NULL;
}
else
{
if (grandparent->_left == parent)
{
grandparent->_left = subR;
}
else
{
grandparent->_right = subR;
}

subR->_parent = grandparent;
}
subR->_bf = parent->_bf = 0;
}

void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;

int bf = subRL->_bf;
RotateR(subR);
RotateL(parent);

if (bf == -1)
//在subRL的左边插入了一个节点
{
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
else if (bf == 1)
//在subRL的右边插入了一个节点
{
parent->_bf = -1;
subR->_bf = 0;
subRL->_bf = 0;
}
else if (bf == 0)
//subRL是新增节点
{
parent->_bf = 0;
subR->_bf = 0;
subRL->_bf = 0;
}
else
{
assert(false);
}
}
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;

int bf = subLR->_bf;
RotateL(subL);
RotateR(parent);

if (bf == -1)
//在subLR的左边插入了一个节点
{
parent->_bf = 0;
subL->_bf = 1;
subLR->_bf = 0;
}
else if (bf == 1)
//在subLR的右边插入了一个节点
{
parent->_bf = -1;
subL->_bf = 0;
subLR->_bf = 0;
}
else if (bf == 0)
//subLR是新增节点
{
parent->_bf = 0;
subL->_bf = 0;
subLR->_bf = 0;
}
else
{
assert(false);
}
}

bool _Inorder(Node* root)
{
if (root == NULL)
{
return false;
}

_Inorder(root->_left);
cout << root->_key << " ";
_Inorder(root->_right);

return true;
}
private:
Node* _root;
};

void TestAVLTree()
{
int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
AVLTree<int, int> t;
for (size_t i = 0; i < sizeof(a) / sizeof(int); ++i)
{
t.Insert(a[i], i);
t.Inorder();
cout << "IsBalance()?" <<  t.IsBalance() << endl;
}
}


这里我们除了实现了一个Insert,还实现了一个判平衡的函数Isbalance。在代码中我们可以看到,用注释注释了的判平衡的算法效率很低,它的思想有一点像用递归方法求斐波那契数列,会有重复求解的过程,效率非常低,所以这里只写出来并不用。

最终测试的代码如下,我们每插入一个值就判断这棵树平不平衡。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息