平衡树
二叉搜索树(BST)
定义
- 二叉搜索树(Binary Search Tree,BST)是一种特殊的二叉树,又称为排序二叉树、二叉排序树。二叉搜索树的递归定义如下:
- 要么一棵二叉搜索树是一棵空树。
- 要么二叉查找树由根节点、左子树(可空)、右子树(可空)组成,其中左子树和右子树都是二叉查找树,且左子树上的所有结点的数据域均小于根节点,右子树上的所有结点的数据域均大于根节点。
- 性质:根据定义,可以知道二叉搜索树的中序遍历序列是一个有序的序列。
二叉搜索树的基本操作
二叉搜索树的基本操作有查找、插入、删除。建树操作可以看做依次插入所有结点。
数的数据结构定义如下:
struct node{
int val; //数据域,以int型为例
node* left;
node* right;
};
查找
查找的根为root,需要查找的值是x,步骤如下:
- 如果结点为空,则查找失败。
- 如果结点的值root->val等于x,说明查找成功,返回。
- 如果结点的值root->val大于x,则递归查找左子树root->left。
- 如果结点的值root->val小于x,则递归查找右子树root->right。
void search(node* root,int x){
if(!root){
printf("查找失败\n");
return;
}
if(root->val == x)
printf("%d查找成功\n",root->val);
else if(root->val > x)
search(root->left,x);
else
search(root->right,x);
return;
}
插入
插入结点和查询结点的步骤类似,只不过是要在结点为空的位置插入一个数据域x的结点。
void insert(node*& root,int x){
if(!root){ //插入
root = new node();
root->val = x;
return;
}
if(root->val == x)
printf("值为%d的结点已存在\n",x);
else if(root->val > x)
insert(root->left,x);
else
insert(root->right,x);
return;
}
删除
当需要删除结点时,如下图。将会变成两棵树,则需要找到一个新的根节点合并这两棵树。有两种方案。
方案一:找到一个比待删除结点(137)大的最小结点(即后继结点),作为新的根节点。
node* findmax(node* root){
if(root->right)
root = root->right;
return root;
}
方案二:找到一个比待删除结点(137)小的最大结点(即前驱结点),作为新的根节点。
node* findmin(node* root){
if(root->left)
root = root->left;
return root;
}
综上,当需要删除的是叶子结点时,可以直接删除,如果要删除的结点有左孩子,则通过方案一找到前驱结点,代替根节点。之后需要把左子树的对应的前驱结点(肯定是叶子结点)给删除掉,同理,如果左子树不存在的话就从右子树中找到后继结点代替根节点,在把右子树中的后继结点给删除掉。
void deletenode(node*& root,int x){
if(!root) return; //不存在值为x的结点
if(root->val==x){
if(root->left==NULL&&root->right==NULL){
delete root; //叶子结点直接删除
root=NULL;
}
else if(root->left!=NULL){
node* pre = findmax(root);
root->val = pre->val;
deletenode(root->left,pre->val);
}else{
node* next = findmin(root);
root->val = next->val;
deletenode(root->right,next->val);
}
}else if(root->val>x){
deletenode(root->left,x);
}else{
deletenode(root->right,x);
}
return;
}
注意:如果需要删除的结点只有左子树或者只有右子树,那么直接把结点删除,根节点指向左孩子/右孩子是也没问题的。
平衡二叉树(AVL)
在上面的二叉搜索树中,如果结点变成了一条链,那么进行各种操作的复杂度将会退化成O(N),显示不是我们需要的。因此,需要对树的结构进行调整,使得每次操作后仍然能保持O(logN)的级。
定义
AVL树是一棵二叉查找树,所谓平衡,是指左右子树的高度之差的绝对值不大于1。其中左子树与右子树的高度之差称为平衡因子。
数据结构
为了得到平衡因子,因此需要在数据结构体加入height记录以当且结点为根节点时,子树的高度。
struct node{
int val; //数据域,以int型为例
int height; //当前子树的高度,只有一个结点时为1
node* left;
node* right;
};
创建一个新结点时,使用如下写法:
node* newnode(int v){
node* root = new node;
root->val = v;
root->height = 1;
root->left=root->right = NULL;
return root;
}
有个子树的高度,就可以得到每个结点的平衡因子,如下:
int getheight(node* root){ //获得root结点的高度
if(!root) return 0;
else return root->height;
}
int getbalancefactor(node* root){
//左子树高度减右子树
return getheight(root->left)-getheight(root->right);
}
更新结点的height的函数如下:
void updateheight(node* root){
root->height = max(getheight(root->left),getheight(root->right)) + 1;
return;
}
AVL树的基本操作
查找操作过于简单,略
插入操作
在进行插入操作之前,先了解一下二叉树的左旋和右旋。
左旋
见下图,即根节点A的右孩子B代替A成为根节点,需要进行左旋。
根据上图可以很容易的得到左旋的代码(记得更新height),如下:
void L(node*& root){ //左旋
node* rs = root->right; //rs:root的右孩
root->right = rs->left;
rs->left = root;
updateheight(root); //此时root是rs的左孩子,因此要先更新root
updateheight(rs);
root = rs;
return;
}
右旋
同理,右旋是让root的左边孩子代替root成为根节点,和右旋相反,可以得到如下代码.
void R(node*& root){ //右旋
node* ls = root->left;
root->left = ls->right;
ls->right = root;
updateheight(root); //此时root是ls的右孩子,因此要先更新root
updateheight(ls);
root = ls;
return;
}
左右旋的意义
观察上面的左旋示意图,可以发现左旋以后A结点和B结点的高度(加或减1了!)
插入结点分析
当插入一个结点时,不满足了AVL树的定义,则需要对树进行调整,此时距离插入结点的不符合定义结点的平衡因子一定是2或者-2(因为插入结点只可能使得高度差加或者减1)。则该结点的插入结点的那个孩子(比如在左子树插入结点就是指左孩子),的平衡因子一定是1或-1(因为如果是0那么最大深度没有发生变化,就不会让父结点的平衡因子变成2),最终可以分情况进行调整。
当A结点平衡因子是2时,见上图,
- 对于左边情况1(LL型)只需要对结点A进行右旋。
- 对于情况2(LR)型,先对C进行做旋,再对A进行右旋即可。
如果A结点的平衡因子是-2时,对应RL型,RR型两种情况。都可以通过左右旋解决。
插入操作代码
void insert(node*& root,int x){
if(!root){ //插入
root = newnode(x);
return;
}
if(root->val == x)
printf("值为%d的结点已存在\n",x);
else if(root->val > x){ //往左子树插
insert(root->left,x);
updateheight(root);
if(getbalancefactor(root->left)==2){
if(getbalancefactor(root->left)==1){ //LL型
R(root);
}
else if(getbalancefactor(root->left)==-1){ //LR型
L(root->left);
R(root);
}
}
}
else{ //往右子树插
insert(root->right,x);
updateheight(root);
if(getbalancefactor(root->right)==-2){
if(getbalancefactor(root->right)==-1){ //RR型
L(root);
}
else if(getbalancefactor(root->right) == 1){ //RL型
R(root->right);
L(root);
}
}
}
return;
}