简介
自平衡二叉树(AVL)属于二叉平衡树的一类,此类树主要完成一个从键到值的查找过程,即字典(或映射),它维护树高度的方式与其他数据结构不同。
自平衡规则:
- AVL树的左、右子树都是AVL树
- 左、右子树的高度差不超过1
在数据结构中,最常见的数据间关系的抽象就是集合(Collection)和字典(Dictionary)。
集合就是线性表(元素允许重复),而字典是一种非多键映射关系(键不允许重复)。
对集合而言,一个班中的所有学生构成一个集合,可以是有序的(有序集合)也可以是无序的(无序集合),查找的时间复杂度一般是O(n),很低。集合的典型就是C#中的List,STL中的vector。集合主要用于存储数据,很少用于查找。如要用于查找,那么可以选择基于有序表的二分查找,相应时间复杂度是O(logn),而要想从无序表转变为有序表,就是各种排序算法的使命了。
而对字典而言,如同根据一个学生的学号找到他这次考试的成绩一样,主要是用于查找的。这是从键到值的单值映射,键不能重复,值可以重复。根据键找到值是字典的工作。如果是平衡查找树,就为O(logn),实现一般以红黑树为主(比AVL简单),如Java的TreeMap、STL的map;如果是哈希表,就为O(1),如Java的HashMap。
源码
由于网上的算法通常是以递归形式出现的,且结点有父指针,更加*。但是在这里,没有递归,没有父指针,这就意味着,算法的实现难度要更高,算法的维护与调试用了两天时间。
解决递归问题靠栈,解决无父指针问题靠栈,殊途同归。
avltree.h(AVL Tree) AVL树模版
baltree.h(Balance Tree) 平衡树基类
btree.cpp(Binary Tree and Console) main函数
树的建立
在这里,只讲元素的插入,而对于删除操作,由于其复杂性,暂且不能实现,参考网上关于树的旋转等资料,实现了删除操作,但没有进行完整测试。
结点的数据结构为:
template<class T>
struct AVLNode
{
T data;
AVLNode *lchild, *rchild;
int BF; // 平衡因子
};
前面与二叉树一样,BF用于累计高度差,因为查询高度是一个枯燥的递归操作。
这里用到了数学归纳的知识,一开始,树的BF是满足要求的,那么,我们对树的每个操作若可以保证BF满足条件要OK,由于后面的操作建立在前面的操作之上,只要保证了每一步操作的可靠性,那么BF的正确性就是可以保障的。这就要求对于每一个插入和删除操作,要保证类似于数据库那样的ACID特性中的一致性,类比数据库,只要一步操作出错,就算后面的操作是正确的,数据库也没法正常使用了。
创建结点:
template<class T, class N>
N* AVLTree<T, N>::New()
{
N* newp = BiTree<T, N>::New();
newp->BF = 0;
return newp;
}
按数组插入:
template<class T, class N>
AVLTree<T, N>::AVLTree(const vector<T>& s)
{
if (s.empty()) throw "数组不能为空";
root = new N;
root->data = s[0];
root->BF = 0;
root->lchild = NULL;
root->rchild = NULL;
for (int i = 1; i < s.Length(); i++) Insert(s[i]);
}
树的旋转
1)LL
template<class T, class N>
inline TStatus AVLTree<T, N>::RotateLL(N *&p, N *&parent, N *ancestor)
{
// 前
// A(parent,root),BF(2)
// |
// B(p),BF(1)__________|_____AR
// |
// BL(X)_____|_____BR // 后
// B(p,root),BF(0)
// |
// BL(X)_____|___________________A(parent),BF(0)
// |
// BR_____|_____AR bool descent = !parent->rchild;
this->RotateRight(p, parent, ancestor); parent->BF = p->BF == 1 ? 0 : 1;
p->BF--;
N *swap; swap = p; p = parent; parent = swap;
return (ancestor && descent) ? parent == ancestor->lchild ? T_DECL : T_DECR : T_OK;
}
2)RR
template<class T, class N>
inline TStatus AVLTree<T, N>::RotateRR(N *&p, N *&parent, N *ancestor)
{
// 前
// A(parent,root),BF(-2)
// |
// AL_____|___________________|
// B(p),BF(-1)
// BL_____|_____BR(X) // 后
// B(p,root),BF(0)
// |
// A(parent),BF(0)_____|_____BR(X)
// |
// AL_____|_____BL bool descent = !parent->lchild;
this->RotateLeft(p, parent, ancestor); parent->BF = p->BF == -1 ? 0 : -1;
p->BF++;
N *swap; swap = p; p = parent; parent = swap;
return (ancestor && descent) ? parent == ancestor->lchild ? T_DECL : T_DECR : T_OK;
}
3)LR
template<class T, class N>
inline TStatus AVLTree<T, N>::RotateLR(N *&p, N *&parent, N *ancestor)
{
////////////////////////////////////////////////////////////////////////// // >>>> 情况一 // 前
// A(parent,root),BF(2)
// |
// B(p),BF(-1)_________|_____AR
// |
// BL_____|_____C(pc),BF(1)
// |
// CL(X)______|______CR // 后(减少一层)
// C(pc,root),BF(0)
// |
// B(p),BF(0)____|_____A(parent),BF(-1)
// | |
// BL_____|_____CL(x) CR_____|_____AR ////////////////////////////////////////////////////////////////////////// // >>>> 情况二 // 前
// A(parent,root),BF(2)
// |
// B(p),BF(-1)_________|_____AR
// |
// BL_____|_____C(pc),BF(-1)
// |
// CL______|______CR(X) // 后(减少一层)
// C(pc,root),BF(0)
// |
// B(p),BF(1)____|_____A(parent),BF(0)
// | |
// BL_____|_____CL CR(x)_____|_____AR N *pc = p->rchild;
this->RotateLeft(pc, p, parent);
this->RotateRight(pc, parent, ancestor); if (pc->BF == 1)
{
p->BF = 0;
parent->BF = -1;
pc->BF = 0;
}
else if (pc->BF == -1)
{
p->BF = 1;
parent->BF = 0;
pc->BF = 0;
}
else
{
parent->BF = 0;
p->BF = 0;
}
parent = pc;
return ancestor ? parent == ancestor->lchild ? T_DECL : T_DECR : T_OK;
}
4)RL
template<class T, class N>
inline TStatus AVLTree<T, N>::RotateRL(N *&p, N *&parent, N *ancestor)
{
////////////////////////////////////////////////////////////////////////// // >>>> 情况一 // 前
// A(parent,root),BF(-2)
// |
// AL_____|___________________B(p),BF(1)
// |
// C(pc),BF(-1)__|______BR
// |
// CL_____|_____CR(X) // 后(减少一层)
// C(pc,root),BF(0)
// |
// A(parent),BF(1)|_____B(p),BF(0)
// | |
// AL_____|_____CL CR(x)_____|_____BR ////////////////////////////////////////////////////////////////////////// // >>>> 情况二 // 前
// A(parent,root),BF(-2)
// |
// AL_____|___________________B(p),BF(1)
// |
// C(pc),BF(1)___|______BR
// |
// CL(X)_____|_____CR // 后(减少一层)
// C(pc,root),BF(0)
// |
// A(parent),BF(0)|_____B(p),BF(-1)
// | |
// AL_____|_____CL(x) CR_____|_____BR N *pc = p->lchild;
this->RotateRight(pc, p, parent);
this->RotateLeft(pc, parent, ancestor); if (pc->BF == -1)
{
parent->BF = 1;
p->BF = 0;
pc->BF = 0;
}
else if (pc->BF == 1)
{
parent->BF = 0;
p->BF = -1;
pc->BF = 0;
}
else
{
parent->BF = 0;
p->BF = 0;
}
parent = pc;
return ancestor ? parent == ancestor->lchild ? T_DECL : T_DECR : T_OK;
}
5)左旋
template<class T, class N>
void BALTree<T, N>::RotateLeft(N *p, N *parent, N *ancestor)
{
// 前
// A(parent,root)
// |
// AL_____|___________________|
// B(p)
// BL_____|_____BR(X) // 后
// B(p,root)
// |
// A(parent)___________|_____BR(X)
// |
// AL_____|_____BL parent->rchild = p->lchild;
p->lchild = parent; if (ancestor != NULL)
{
if (ancestor->lchild == parent) // 修改p的双亲为ancestor
ancestor->lchild = p;
else
ancestor->rchild = p;
}
else
{
this->root = p;
}
}
6)右旋
template<class T, class N>
void BALTree<T, N>::RotateRight(N *p, N *parent, N *ancestor)
{
// 前
// A(parent,root)
// |
// B(p)________________|_____AR
// |
// BL(X)_____|_____BR // 后
// B(p,root)
// |
// BL(X)_____|___________________A(parent)
// |
// BR_____|_____AR parent->lchild = p->rchild;
p->rchild = parent; if (ancestor != NULL)
{
if (ancestor->lchild == parent) // 修改p的双亲为ancestor
ancestor->lchild = p;
else
ancestor->rchild = p;
}
else
{
this->root = p;
}
}
结点的插入
对于AVL,结点的插入是一项复杂的工作。由于插入操作,树原本的平衡被破坏,需要重新调整一次。
基本步骤是:
- 二分查找,找到相应的空位置,并插入(若已存在则忽略),同时存储查找路径
- 从路径逆着向上找到第一个最小不平衡子树的根结点的父结点,接下来就是对这个子树进行调整
- 调整子树,有LL、LR、RL、RR四种情况
1)添加结点
template<class T, class N>
bool BALTree<T, N>::Insert(T data)
{
N* newp; if (this->root == NULL)
{
newp = this->New();
newp->data = data;
this->root = newp;
this->HandleRoot();
return true;
} stack<N*> path; // 存储插入前的查找路径(便于回溯) //////////////////////////////////////////////////////////////////////////
// 插入操作
N *p = this->root;
while (true)
{
path.push(p);
if (data < p->data) // 插入值小于根结点,入左子树
{
if (p->lchild != NULL)
{
p = p->lchild; // 值小于LL,则递归入L
}
else
{
newp = this->New();
newp->data = data;
path.push(newp);
p->lchild = newp;
break; // 根结点无左孩子,正好插入
}
}
else if (data > p->data) // 插入值大于根结点,入右子树
{
if (p->rchild != NULL)
{
p = p->rchild; // 值大于RR,则递归入R
}
else
{
newp = this->New();
newp->data = data;
path.push(newp);
p->rchild = newp;
break; // 根结点无右孩子,正好插入
}
}
else // 插入值等于根结点,返回
{
return false;
}
} // 插入完毕 ////////////////////////////////////////////////////////////////////////// // 调整插入路径上的结点
BalanceAfterInsert(path);
return true;
}
2)插入后的平衡
template<class T, class N>
void BALTree<T, N>::BalanceAfterInsert(stack<N*>& path)
{
N *child = NULL; // *child作为*p的孩子结点
N *p = path.top();
path.pop();
N *parent = path.top(); // *parent是*p的父结点
path.pop();
N *ancestor;
while (true)
{
ancestor = path.empty() ? NULL : path.top();
TStatus status = CheckPathAfterInsert(child, p, parent, ancestor);
switch (status)
{
case T_OK:
return;
case T_BREAK:
BalanceInternalAfterInsert(child, p, parent, ancestor);
return;
case T_CONTINUE:
break;
} if (path.empty())
return; child = p;
p = parent;
parent = path.top(); // 由path向上回溯
path.pop();
}
}
3)检查平衡
template<class T, class N>
TStatus AVLTree<T, N>::CheckPathAfterInsert(N *child, N *p, N *parent, N *ancestor)
{
if (parent->lchild == p) // p是parent的左孩子,那么parent的BF++
parent->BF++; // BF的变化:-1->0->1->2
else // p是parent的右孩子,那么parent的BF--
parent->BF--; // BF的变化:1->0->-1->-2 if (parent->BF == 2 || parent->BF == -2) // *parent是失衡结点(第一个|BF|>=2)
return T_BREAK; // 找到最小不平衡子树的根结点 if (parent->BF == 0) // 在插入新结点后,*parent的左右子树高度相等(BF为0),说明以*parent为根的子树高度未增加
return T_OK; // 所以路径中的其余祖先结点无需调整BF return T_CONTINUE;
}
4)调整平衡
template<class T, class N>
TStatus AVLTree<T, N>::BalanceInternalAfterInsert(N *child, N *p, N *parent, N *ancestor)
{
////////////////////////////////////////////////////////////////////////// if (parent->BF == 2 && p->lchild == child) // LL
{
RotateLL(p, parent, ancestor);
} ////////////////////////////////////////////////////////////////////////// else if (parent->BF == -2 && p->rchild == child) // RR
{
RotateRR(p, parent, ancestor);
} ////////////////////////////////////////////////////////////////////////// else if (parent->BF == 2 && p->rchild == child) // LR
{
RotateLR(p, parent, ancestor);
} ////////////////////////////////////////////////////////////////////////// else if (parent->BF == -2 && p->lchild == child) // RL
{
RotateRL(p, parent, ancestor);
} ////////////////////////////////////////////////////////////////////////// return T_OK;
}
调整过程全部是旋转操作
结点的删除
结点的删除比插入要复杂得多。删除某一结点后,可能需要多次调整。
基本步骤是:
- 二分查找,找到要删除的结点,并用最近的结点替换掉它,然后删除它,调整BF
- 从路径逆着向上找到第一个最小不平衡子树的根结点的父结点,接下来就是对这个子树进行调整
- 调整子树,有LL、LR、RL、RR四种情况,如果调整后子树高度下降(即父结点BF改变),那么跳到步骤2
1)定位结点
template<class T, class N>
bool BALTree<T, N>::Delete(T data)
{
if (this->root == NULL)
{
return false;
} stack<N*> path; // 存储删除前的查找路径(便于回溯) //////////////////////////////////////////////////////////////////////////
// 查找操作
N *p = this->root;
while (true)
{
path.push(p);
if (data < p->data) // 插入值小于根结点,入左子树
{
if (p->lchild != NULL)
{
p = p->lchild; // 值小于LL,则递归入L
}
else
{
return false; // 没找到
}
}
else if (data > p->data) // 插入值大于根结点,入右子树
{
if (p->rchild != NULL)
{
p = p->rchild; // 值大于RR,则递归入R
}
else
{
return false; // 没找到
}
}
else // 找到
{
break;
}
} // 定位成功 ////////////////////////////////////////////////////////////////////////// // 调整删除路径上的结点
BalanceAfterDelete(path);
return true;
}
2)删除结点
template<class T, class N>
void BALTree<T, N>::BalanceAfterDelete(stack<N*>& path)
{
// 替代结点
Replace(path); if (path.empty())
return; // 删除结点并平衡 N *p = NULL;
N *child = NULL; // *child作为*p的孩子结点
N *parent = path.top(); // *parent是*p的父结点
path.pop();
N *ancestor;
TStatus status = T_OK;
while (true)
{
ancestor = path.empty() ? NULL : path.top();
if (status == T_CONTINUE_STATUS)
status = ancestor ? parent == ancestor->lchild ? T_DECL : T_DECR : T_OK;
status = CheckPathAfterDelete(child, p, parent, ancestor, status);
switch (status)
{
case T_OK:
return;
case T_BREAK:
status = BalanceInternalAfterDelete(child, p, parent, ancestor);
if (status == T_OK)
return;
break;
case T_CONTINUE:
status = T_OK;
break;
case T_CONTINUE_STATUS:
break;
} if (path.empty())
return; child = p;
p = parent;
parent = path.top(); // 由path向上回溯
path.pop();
}
}
3)替换结点
删除结点前,先将结点与其他结点进行替换,使得对于结点的删除转变为对于叶子结点的删除。
template<class T, class N>
void AVLTree<T, N>::Replace(stack<N*>& path)
{
N *p = path.top();
path.pop();
N *parent = path.empty() ? NULL : path.top();
if (!p->lchild && !p->rchild) // p为叶子结点,直接删除
{
if (!parent) // 根结点
{
this->root = NULL;
}
else if (parent->lchild == p)
{
parent->lchild = NULL;
parent->BF--;
}
else
{
parent->rchild = NULL;
parent->BF++;
}
}
else if (p->lchild && p->rchild) // 双子树,转化为单子树
{
// 注意:替换时,BF也要替换
if (p->lchild->rchild) // 替换左子树最右结点
{
vector<N*> new_path;
new_path.push_back(p->lchild);
N *lr = p->lchild->rchild;
while (lr)
{
new_path.push_back(lr);
lr = lr->rchild;
}
path.push(new_path.back());
lr = path.top();
new_path.pop_back();
for (vector<N*>::iterator it = new_path.begin(); it != new_path.end(); it++)
{
path.push(*it);
}
lr->BF = p->BF;
if (!parent) // 根结点
this->root = lr;
else if (parent->lchild == p)
parent->lchild = lr;
else
parent->rchild = lr;
path.top()->rchild = lr->lchild;
path.top()->BF++;
lr->lchild = p->lchild;
lr->rchild = p->rchild;
}
else if (p->rchild->lchild) // 替换右子树最左结点
{
vector<N*> new_path;
new_path.push_back(p->rchild);
N *rl = p->rchild->lchild;
while (rl)
{
new_path.push_back(rl);
rl = rl->lchild;
}
path.push(new_path.back());
rl = path.top();
new_path.pop_back();
for (vector<N*>::iterator it = new_path.begin(); it != new_path.end(); it++)
{
path.push(*it);
}
rl->BF = p->BF;
if (!parent) // 根结点
this->root = rl;
else if (parent->lchild == p)
parent->lchild = rl;
else
parent->rchild = rl;
path.top()->lchild = rl->rchild;
path.top()->BF--;
rl->lchild = p->lchild;
rl->rchild = p->rchild;
}
else // 替代孩子
{
if (!parent) // 根结点
{
if (this->root->BF != -1)
{
this->root = p->lchild;
this->root->rchild = p->rchild;
this->root->BF = p->BF - 1;
}
else
{
this->root = p->rchild;
this->root->lchild = p->lchild;
this->root->BF = p->BF + 1;
}
}
else if (parent->lchild == p)
{
if (parent->BF != -1)
{
parent->lchild = p->lchild;
parent->lchild->rchild = p->rchild;
parent->lchild->BF = p->BF - 1;
}
else
{
parent->lchild = p->rchild;
parent->lchild->lchild = p->lchild;
parent->lchild->BF = p->BF + 1;
}
path.push(parent->lchild);
}
else
{
if (parent->BF != -1)
{
parent->rchild = p->lchild;
parent->rchild->rchild = p->rchild;
parent->rchild->BF = p->BF - 1;
}
else
{
parent->rchild = p->rchild;
parent->rchild->lchild = p->lchild;
parent->rchild->BF = p->BF + 1;
}
path.push(parent->rchild);
}
}
}
else if (p->lchild) // 左单子树
{
if (!parent) // 根结点
{
this->root = p->lchild;
}
else
{
if (parent->lchild == p)
{
parent->lchild = p->lchild;
parent->BF--;
}
else
{
parent->rchild = p->lchild;
parent->BF++;
}
}
}
else // 右单子树
{
if (!parent) // 根结点
{
this->root = p->rchild;
}
else
{
if (parent->lchild == p)
{
parent->lchild = p->rchild;
parent->BF--;
}
else
{
parent->rchild = p->rchild;
parent->BF++;
}
}
}
delete p;
}
4)检查平衡
template<class T, class N>
TStatus AVLTree<T, N>::CheckPathAfterDelete(N *child, N *p, N *parent, N *ancestor, TStatus status)
{
if (status == T_OK)
{
if (p && p->BF == 0)
{
if (parent->lchild == p) // p是parent的左孩子,p的BF为0,说明p层数减少一,故parent的BF--
parent->BF--;
else // p是parent的右孩子,p的BF为0,说明p层数减少一,故parent的BF++
parent->BF++;
}
}
else
{
if (status == T_DECL) //平衡后,子树高度减少,那么要向上检查平衡性
{
parent->BF--;
return parent->BF == 0 ? T_CONTINUE_STATUS : T_OK;
}
else if (status == T_DECR) //平衡后,子树高度减少,那么要向上检查平衡性
{
parent->BF++;
return parent->BF == 0 ? T_CONTINUE_STATUS : T_OK;
}
} if (parent->BF == 2 || parent->BF == -2) // *parent是失衡结点(第一个|BF|>=2)
return T_BREAK; // 找到最小不平衡子树的根结点 if (parent->BF != 0) // 在删除结点后,*parent的左右子树高度差绝对值为1(|BF|为1),说明以*parent为根的子树高度未改变
return T_OK; // 所以路径中的其余祖先结点无需调整BF return T_CONTINUE;
}
5)调整平衡
template<class T, class N>
TStatus AVLTree<T, N>::BalanceInternalAfterDelete(N *child, N *p, N *parent, N *ancestor)
{
////////////////////////////////////////////////////////////////////////// if (parent->BF == 2 && (!p || parent->rchild == p)) // L
{
p = parent->lchild; if (p->BF == -1) // LR
{
return RotateLR(p, parent, ancestor);
}
else // LL
{
return RotateLL(p, parent, ancestor);
}
} ////////////////////////////////////////////////////////////////////////// else if (parent->BF == -2 && (!p || parent->lchild == p)) // R
{
p = parent->rchild; if (p->BF == 1) // RL
{
return RotateRL(p, parent, ancestor);
}
else // RR
{
return RotateRR(p, parent, ancestor);
}
} //////////////////////////////////////////////////////////////////////////
return T_OK;
}
总结
虽然算法的实现思路很简单,但实际要考虑很多因素,稍微一个小错误都可能导致结果不一致的现象(BF未调整)。然而,在理解AVL上的基础上,理解红黑树就不那么困难了。
相比较AVL的递归算法而言,可以看出,递归算法是具有简洁美的,这是由二叉树的递归性质决定的。
由于AVL操作的复杂性,因而不常用AVL,现在主要使用红黑树(RBT),它们的区别是:
- AVL讲究整体平衡,因而要求严格、操作复杂 ,子树高度差不大于1
- RBT追求局部平衡,因而实现较AVL简单,最长路径比上最短路径小于2