平衡二叉树(AVL)
满足以下性质:
1.根节点的值比左子树的值大,比右子树的值小。
2.左子树的高度和右子树的高度差的绝对值不超过2。
性质1应用:从AVL中查找一个数A,使得abs(A-x)最小,查找当前节点如果A == x,那么abs(A-x)最小为0,如果A>x,证明与x相同的数可能在左子树,但是也有可能不存在,如果不存在,就需要将比x大且最小的值保存,这个值可能就是A,因为A是小于右子树所有的值,那么x相对于当前子树的右子树距离根节点最近,每次保存min(abs(A-x))就可以得到最优解,A<x时,同理(只不过根节点相对于左子树最大)。
性质2:保证树不会出现链的形态,保证复杂度在logn。
在AVL最重要的是旋转,旋转的同时还需要保证AVL的性质。
会出现以下4种旋转方式:
从4种旋转形态中其实能看出规律,LL型只需要将不平衡节点的左子树向右旋转一次,RR型只需要将不平衡节点的右子树向左旋转一次,
LR型需要将不平衡节点的左子树进行一次RR型旋转就转换成LL型的形式,根据LL型旋转即可。
RL型需要将不平衡节点的右子树进行一次LL型旋转就转换成RR型的形式,根据RR型旋转即可。
而在旋转过程中只需要知道平衡因子的值就可以知道是什么哪种形式的旋转,设计如下结构体:
struct Tree
{
int x, h, dep;//h:高度 dep:平衡因子
struct Tree * ch[2];
void getHeight ( )
{
h = max ( ch[0] -> h, ch[1] -> h ) + 1;
dep = ch[0] -> h - ch[1] -> h;
}
};
这样设计的好处就是只需要将0定义为左旋,1为右旋,那么就只需要写一个旋转函数即可将两种旋转全部包含(LL型,RR型,LR型和RL型最终都是这两种形态)。
void Rotate ( Tree * &o, int k )
{
Tree * p = o -> ch[k^1];
o -> ch[k^1] = p -> ch[k];
p -> ch[k] = o;
o -> getHeight ( );//o为子节点更新高度和平衡因子
p -> getHeight ( );
o = p;//根节点变为p
}
当传递k为0时,即可完成左旋(建议自己模拟一下,不要将变换的子树丢弃了),这里注意左旋时是将p变为根节点了,p的左子树应为o,但是如果p原来有左子树就需要将左子树接到o的右子树,因为p本来是右子树但是p变为根节点了,而p的左子树比o大,比p小,所以需要接到o的右子树,右旋时和左旋相反。
注意更新高度时先更新子节点o,再更新根节点p。
void RotateOper ( Tree * &T )
{
if ( abs ( T -> dep ) >= 2 )
{
int k = T -> dep > 0 ? 1 : 0;
Tree * p = T -> ch[k^1];
int k2 = p -> dep > 0 ? 1 : 0;
if ( k == k2 )
Rotate ( T, k );
else
{
Rotate ( T -> ch[k^1], k2 ); //用p传递无法更新
Rotate ( T, k );
}
}
}
上面代码实现各种形态需要进行的旋转操作,如果平衡因子超过2,k=0时代表L,1时代表R,k2=0时代表L,1时代表R,这样就包括4中情况LL,LR,RL,RR。
LL和RR就只需要对根节点旋转一次,代表这两种形态的就是k==k2。
不相等就是另外两种情况,LR就是对左子树进行一次左旋再对根节点右旋一次,RL就是对右子树进行一次右旋再对根节点左旋一次。
旋转函数(写一个函数为了删除时直接调用就行)写好后插入就简单了。
void AVL_insert ( Tree * &T, int x )
{
if ( T == null )
{
T = new Tree ( );
T -> x = x;
T -> h = 1;
T -> dep = 0;
T -> ch[0] = T -> ch[1] = null;
return ;
}
if ( T -> x == x )
return ;
if ( T -> x > x )
AVL_insert ( T -> ch[0], x );
else
AVL_insert ( T -> ch[1], x );
T -> getHeight ( );
RotateOper ( T );
}
插入就旋转稍微复杂一点,而删除就需要技巧了,如果直接找到一个节点直接删除,就有可能出现删除此节点导致此节点的子树失去平衡,父节点也失去平衡,会导致多部分不再平衡而且还不好维护,如果这个节点不存在左子树或右子树就直接删除,用左子树或右子树替代(最多只有一个节点),然后只需要向上维护平衡因子就行,如果左子树和右子树都存在那么就可以用前驱的值替代(即当前节点左子树的右子树的右子树..也就是左子树的最大值mx),查找的值只需要变成mx,并从左子树查找mx值,查找到后删除此节点就行,然后往上维护平衡因子。
void AVL_delete ( Tree * &T, int x )
{
if ( T == null )
return ;
int k = T -> x > x ? 0 : 1;
if ( T -> x == x )
{
Tree * p = T, * s;
if ( p -> ch[0] != null && p -> ch[1] != null )
{
s = p -> ch[0];
while ( s -> ch[1] != null )
{
p = s;
s = s -> ch[1];
}
k = 0;
T -> x = s -> x;
x = s -> x;
}
else
{
if ( T -> ch[0] != null )
T = T -> ch[0];
else
T = T -> ch[1];
delete p;
return ;
}
}
AVL_delete ( T -> ch[k], x );
T -> getHeight ( );//更新高度和平衡因子
RotateOper ( T );//根据平衡因子旋转
}
代码中还可以维护一个值s,代表节点的总个数,这样当需要动态查找第k大值(第k小也是一样)就可以根据s的值查找。
推荐验证代码正确性的题目:
http://poj.org/problem?id=3481
不过注意此题的优先级是不同的,如果相同需要注意插入和删除的处理。
附上此题完整代码
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <utility>
#include <map>
#include <set>
#include <queue>
#include <vector>
#include <iostream>
#include <stack>
using namespace std;
#define INF 0x3f3f3f3f
#define eps 1e-6
#define CLR( a, v ) memset ( a, v, sizeof ( a ) )
#define LL long long
#define DBUG printf ( "here!!!" )
#define rep( i, a, b ) for ( int i = ( a ); i < ( b ); i ++ )
#define PB push_back
#define ULL unsigned long long
#define PI acos ( -1.0 )
#define lson l, m, rt << 1
#define rson m+1, r, rt << 1 | 1
#define lowbit( x ) ( ( x )&( -x ) )
#define CASE int Test; scanf ( "%d", &Test ); for ( int cas = 1; cas <= Test; cas ++ )
#define ALL( x ) x.begin ( ), x.end ( )
#define INS( x ) x, x.begin ( )
typedef pair < int, int > Pii;
typedef pair < double, double > Pdd;
typedef set < int > Set;
const int maxn = 100005;
int read_int ( )
{
int res = 0;
int ch;
while ( ( ch = getchar ( ) ) && ! ( ch >= '0' && ch <= '9' ) )
{
if ( ch == -1 )
return -1;
}
while ( ch >= '0' && ch <= '9' )
{
res = res*10+( ch-'0' );
ch = getchar ( );
}
return res;
}
struct Tree
{
int x, h, dep, id;
struct Tree * ch[2];
void getHeight ( )
{
h = max ( ch[0] -> h, ch[1] -> h ) + 1;
dep = ch[0] -> h - ch[1] -> h;
}
}*root, *null;
void Rotate ( Tree * &o, int k )
{
Tree * p = o -> ch[k^1];
o -> ch[k^1] = p -> ch[k];
p -> ch[k] = o;
o -> getHeight ( );
p -> getHeight ( );
o = p;
}
void RotateOper ( Tree * &T )
{
if ( abs ( T -> dep ) >= 2 )
{
int k = T -> dep > 0 ? 1 : 0;
Tree * p = T -> ch[k^1];
int k2 = p -> dep > 0 ? 1 : 0;
if ( k == k2 )
Rotate ( T, k );
else
{
Rotate ( T -> ch[k^1], k2 ); //用p传递无法更新
Rotate ( T, k );
}
}
}
void AVL_insert ( Tree * &T, int x, int id )
{
if ( T == null )
{
T = new Tree ( );
T -> x = x;
T -> h = 1;
T -> id = id;
T -> dep = 0;
T -> ch[0] = T -> ch[1] = null;
return ;
}
if ( T -> x > x )
AVL_insert ( T -> ch[0], x, id );
else
AVL_insert ( T -> ch[1], x, id );
T -> getHeight ( );
RotateOper ( T );
}
void AVL_delete ( Tree * &T, int x )
{
if ( T == null )
return ;
int k = T -> x > x ? 0 : 1;
if ( T -> x == x )
{
Tree * p = T, * s;
if ( p -> ch[0] != null && p -> ch[1] != null )
{
s = p -> ch[0];
while ( s -> ch[1] != null )
{
p = s;
s = s -> ch[1];
}
k = 0;
T -> x = s -> x;
x = s -> x;
}
else
{
if ( T -> ch[0] != null )
T = T -> ch[0];
else
T = T -> ch[1];
delete p;
return ;
}
}
AVL_delete ( T -> ch[k], x );
T -> getHeight ( );
RotateOper ( T );
}
bool AVL_find ( Tree * T, int x )
{
if ( T == null )
return false;
if ( T -> x == x )
return true;
if ( T -> x > x )
return AVL_find ( T -> ch[0], x );
return AVL_find ( T -> ch[1], x );
}
void solve ( )
{
int x, op, id;
null = new Tree ( );
root = null;
while ( ~ scanf ( "%d", &op ), op )
{
if ( op == 1 )
{
scanf ( "%d%d", &id, &x );
AVL_insert ( root, x, id );
}
else if ( op == 3 )
{
if ( root == null )
printf ( "0\n" );
else
{
Tree * p = root;
while ( p -> ch[0] != null )
p = p -> ch[0];
printf ( "%d\n", p -> id );
AVL_delete ( root, p -> x );
}
}
else
{
if ( root == null )
printf ( "0\n" );
else
{
Tree * p = root;
while ( p -> ch[1] != null )
p = p -> ch[1];
printf ( "%d\n", p -> id );
AVL_delete ( root, p -> x );
}
}
}
}
int main ( )
{
solve ( );
return 0;
}