您的位置:首页 > 其它

平衡搜索树---AVL树

2016-10-30 10:50 405 查看
AVL树是一种高度平衡的二叉搜索树,它的每个结点都有一个平衡因子,这个平衡因子的取值是-1,0,1(平衡因子 = 右子树高度 - 左子树高度)

AVL树具有的性质:

1.左子树和右子树的高度差不超过1;

2.树中的各子树都是AVL树

一、AVL树结点的定义

为了判断每个子树是否平衡,因此在定义AVL树的结点的时候就应该在搜索二叉树的基础上加一个平衡因子_bf

template<class K,class V>
struct AVLTreeNode
{
AVLTreeNode(const K& key,const V& value)
:_left(NULL)
,_right(NULL)
,_parent(NULL)
,_key(key)
,_value(value)
,_bf(0)
{}

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

int _bf;	//平衡因子
};
二、AVL树的操作---插入

①当插入结点的key与某个结点的_key相等时,插入失败,返回false;

②每插入一个结点,就应该检查是否平衡,判断的依据就是_bf的值不超过1

a.当插入一个结点后,父亲结点的_bf由-1/1变为0,说明在插入之前父亲节点有一个子树,此时整棵树的高度不变



b.当插入一个结点后,父亲结点的_bf由0变为-1/1,说明在插入之前父亲节点没有子树,此时整棵树的高度一定变



如上图所示的这种情况插入一个结点后整棵树仍然保持平衡的,但如果此时以cur作为父亲节点,插入一个结点后还会平衡嘛?!



此时的ppNode的平衡因子已经变为-2,显然已经不满足AVL树的性质。为了解决这种情况,就要对树进行旋转,以达到平衡。

(PS:我对旋转这块的内容写了一篇博客,在这里就不写了,详情请戳这里>>平衡搜索树中的左单旋&右单旋&双旋

插入的几种情况总结起来就是:

当parent->_bf变为0,就停止更新(平衡因子);当parent->_bf变为-1/1,继续向上更新平衡因子;当parent->_bf变为-2/2,进行旋转

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)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if(cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else
{
//已经有key,插入失败
return false;
}
}
cur = new Node(key,value);
cur->_parent = parent;
if (cur->_key < parent->_key)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
//插入新的结点后,检查是否满足平衡二叉树,不满足就进行旋转
while(parent)
{
if (parent->_left == cur)	//新增结点在左,_bf--
{
parent->_bf --;
}
else if(parent->_right == cur)	//新增结点在右,_bf++
{
parent->_bf ++;
}
//判断父亲的平衡因子,若parent->_bf为2、-2,就进行旋转
if (parent->_bf == 0)
{
break;	//parent的平衡因子为0,说明这棵树一定满足平衡二叉树
}
else if (parent->_bf == -1 || parent->_bf == 1)
{
//继续更新平衡因子
cur = parent;
parent = cur->_parent;
}
else	//parent->_bf == -2 / 2
{
if (parent->_bf == -2)
{
if(cur->_bf == -1)
RotateRight(parent);
else
RotateLR(parent);
}
else
{
if (cur->_bf == -1)
RotateRL(parent);
else
RotateLeft(parent);
}
}
}
return true;
}
旋转:

void RotateLeft(Node* parent)	//左单旋
{
Node* subR = parent->_right;
Node* subRL = subR->_left;

parent->_right = subRL;	//先改变parent的右指针
if (subRL)	//subRL可能为NULL
{
subRL->_parent = parent;
}

Node* ppNode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;

if (ppNode == NULL)
{
_root = subR;
subR->_parent = NULL;
}
else
{
//判断subR应链接在ppNode的左子树还是右子树
if (ppNode->_left == parent)
ppNode->_left = subR;
else
ppNode->_right = subR;

subR->_parent = ppNode;
}

subR->_bf = parent->_bf = 0;
}
void RotateRight(Node* parent)	//右单旋
{
Node* subL = parent->_left;
Node* subLR = subL->_right;

parent->_left = subLR;
if (subLR)
{
subLR->_parent = parent;
}

Node* ppNode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;

if (ppNode == NULL)	//说明parent结点为根节点
{
_root = subL;
subL->_parent = NULL;
}
else
{
//如果parent不为根节点,判断其在上一个结点的右还是左
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;
Node* subLR = subL->_right;
int bf = subLR->_bf;

RotateLeft(parent->_left);
RotateRight(parent);

if (bf == 0)		//subLR本身就是新增节点
{
parent->_bf = subL->_bf = subLR->_bf = 0;
}
else if(bf == 1)	//subLR的右子树是新增节点
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else	//bf == -1----subLR的左子树是新增结点
{
parent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
}
void RotateRL(Node* parent)		//右左双旋
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;

RotateRight(parent->_right);
RotateLeft(parent);

if (bf == 0)		//subRL本身就是新增节点
{
parent->_bf = subR->_bf = subRL->_bf = 0;
}
else if(bf == 1)	//subRL的右子树是新增节点
{
parent->_bf = -1;
subR->_bf = 0;
subRL->_bf = 1;
}
else	//bf == -1----subR L的左子树是新增结点
{
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = -1;
}
}
三、AVL树的效率

假设一棵AVL树有N个结点,那么它的高度可以保持在lgN(log以2为底的N的对数),

插入时最好的情况就是第一次就能找到这个结点的位置进行插入,时间复杂度为O(1);最坏的情况是需要遍历整棵树的高度次,时间复杂度为O(lgN)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息