数据结构----AVL平衡树----AVL平衡树的基本操作
2017-03-29 14:11
681 查看
一、二叉查找树
我们先来讲讲二叉查找树。
大家应该听说过二分查找吧,二分查找是对一个有序序列的快速查找,时间复杂度为O(log2(n)),
但是二分查找也有它的缺点,当序列加入一个元素时,我们就需要对这个有序序列进行维护,要么就用sort(),要么就用插入排序(附带大量的数据移动),时间复杂度就会陡然提升。
于是就有了一种新的数据结构:二叉查找树!
二叉查找树的运用比较灵活,支持插入O(log2(n))、查找O(log2(n))、删除和前驱后继的查询。
插入操作:
(1)将要插入的节点与根节点比较,如果没有根节点,则把该节点作为根节点。
(2)如果插入的节点小于根节点,就把该节点插入到左子树,并把左子树的根节点作为比较对象。
(3)如果插入的节点大于根节点,就把该节点插入到右子树,并把右子树的根节点作为比较对象。
(4)递归进行以上操作,直到达到叶子节点。
(5)将它插入到叶子节点的下方。
查找操作:
(1)将它与根节点比较,如果比根节点小,就向左子树查找;如果比根节点大,就向右子树查找。
(2)如果到了叶子节点下方还没有找到返回-1。
(3)如果找到了,就返回该节点的下表或值。
删除操作:
(1)找到该节点a。(最重要的一步)
(2)在这个节点的左子树里找到最大的节点b(即前驱)。
(3)删掉节点b,因为节点b没有右儿子,所以就把节点b的左子树的根节点来代替节点b。
(4)用节点b来代替节点a。
查询前驱与后继操作:
(1)找到这个节点a。
(2)a的前驱为它的左子树里最右的儿子。
(3)a的后继为它的右子树里最左的儿子。
但是,二叉查找树也有它的缺点,因为当输入数据是有序的时候(如:1 2 3 4 5 6),经过插入操作之后,建成的树为这个样子:
之后,所有的操作的时间复杂度都变成了O(n)了,怎么处理这样的情况呢?
于是就有了二叉查找树的升级版:AVL平衡树!
二、AVL平衡树
AVL平衡树其实就是在每一个节点上面加了一个平衡因子h,h表示的是以这个节点为根节点的树总深度,当一个节点的左右儿子的平衡因子的差大于1时,就会对此进行平衡化旋转操作。
平衡树的定义方法:(注意maxn大小最好为输入数据数量n的4倍,即4n)
平衡化旋转大致分为4类
1、zig旋转:
当平衡树中插入的节点在第一个不平衡的节点的左子树的左子树中,我们就要对平衡树进行zig旋转
代码:
2、zag旋转:
当平衡树中插入的节点在第一个不平衡的节点的右子树的右子树中,我们就要对平衡树进行zag旋转
代码:
3、zigzag旋转:
当平衡树中插入的节点在第一个不平衡的节点的左子树的右子树中,我们就要对平衡树进行zigzag旋转
代码:
4、zagzig旋转:
当平衡树中插入的节点在第一个不平衡的节点的右子树的左子树中,我
4000
们就要对平衡树进行zagzig旋转
代码:
插入操作跟二叉查找树差不多,只不过要注意插入后的调整:
删除操作的思路跟二叉查找树的思路是一样的,但是调整整棵树就比较麻烦了(因为一次旋转不能达到平衡),所以说就要写一个maintain()函数,在每次调用dele()的最后都要调用一下maintain()。
AVL仍然可以采用惰性删除,对于要删除的节点,只需要给它进行一个标记。平衡树的高度仍然是log(N),这样并不会降低效率,而删除操作却要快速的多。
如果遇到那些删除过后还要恢复的节点,则惰性删除更优,不需要额外占用空间,将原来的节点恢复即可。
如果遇到那种允许节点重复的AVL,惰性删除更可取,将删除标记设为整型变量,表示该节点出现的数量即可。
查询操作和二叉查找树也大致相同。以下代码是查询一个数在序列中与其他数的最小的差值(即最接近的数与它的差)。
三、例题
Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况。
Tiger拿出了公司的账本,账本上记录了公司成立以来每天的营业额。分析营业情况是一项相当复杂的工作。由于节假日,大减价或者是其他情况的时候,营业额会出现一定的波动,当然一定的波动是能够接受的,但是在某些时候营业额突变得很高或是很低,这就证明公司此时的经营状况出现了问题。经济管理学上定义了一种最小波动值来衡量这种情况:
该天的最小波动值 = min {该天以前某一天的营业额 - 该天营业额}
当最小波动值越大时,就说明营业情况越不稳定。
而分析整个公司的从成立到现在营业情况是否稳定,只需要把每一天的最小波动值加起来就可以了。你的任务就是编写一个程序帮助Tiger来计算这一个值。
第一天的最小波动值为第一天的营业额。
天数小于100000.
第一行为正整数 ,表示该公司从成立一直到现在的天数
接下来的n行每行有一个整数(一定有数据小于〇) ,表示第i天公司的营业额。
输出文件仅有一个正整数,即每一天的最小波动值之和。答案保证在int范围内。
结果说明:5+|1-5|+|2-1|+|5-5|+|4-5|+|6-5|=5+4+1+0+1+1=12
四、分析
一道典型的平衡树题目。
思路:
(1)输入一个数a。如果a为第一个,则sum+=a,进行步骤(3)。
(2)查找最接近a的数b与a之差,即abs(a-b)。sum+=abs(a-b)。
(3)插入a。
代码:
我们先来讲讲二叉查找树。
大家应该听说过二分查找吧,二分查找是对一个有序序列的快速查找,时间复杂度为O(log2(n)),
但是二分查找也有它的缺点,当序列加入一个元素时,我们就需要对这个有序序列进行维护,要么就用sort(),要么就用插入排序(附带大量的数据移动),时间复杂度就会陡然提升。
于是就有了一种新的数据结构:二叉查找树!
二叉查找树的运用比较灵活,支持插入O(log2(n))、查找O(log2(n))、删除和前驱后继的查询。
插入操作:
(1)将要插入的节点与根节点比较,如果没有根节点,则把该节点作为根节点。
(2)如果插入的节点小于根节点,就把该节点插入到左子树,并把左子树的根节点作为比较对象。
(3)如果插入的节点大于根节点,就把该节点插入到右子树,并把右子树的根节点作为比较对象。
(4)递归进行以上操作,直到达到叶子节点。
(5)将它插入到叶子节点的下方。
查找操作:
(1)将它与根节点比较,如果比根节点小,就向左子树查找;如果比根节点大,就向右子树查找。
(2)如果到了叶子节点下方还没有找到返回-1。
(3)如果找到了,就返回该节点的下表或值。
删除操作:
(1)找到该节点a。(最重要的一步)
(2)在这个节点的左子树里找到最大的节点b(即前驱)。
(3)删掉节点b,因为节点b没有右儿子,所以就把节点b的左子树的根节点来代替节点b。
(4)用节点b来代替节点a。
查询前驱与后继操作:
(1)找到这个节点a。
(2)a的前驱为它的左子树里最右的儿子。
(3)a的后继为它的右子树里最左的儿子。
但是,二叉查找树也有它的缺点,因为当输入数据是有序的时候(如:1 2 3 4 5 6),经过插入操作之后,建成的树为这个样子:
之后,所有的操作的时间复杂度都变成了O(n)了,怎么处理这样的情况呢?
于是就有了二叉查找树的升级版:AVL平衡树!
二、AVL平衡树
AVL平衡树其实就是在每一个节点上面加了一个平衡因子h,h表示的是以这个节点为根节点的树总深度,当一个节点的左右儿子的平衡因子的差大于1时,就会对此进行平衡化旋转操作。
平衡树的定义方法:(注意maxn大小最好为输入数据数量n的4倍,即4n)
struct node{ int lc,rc,h,v; }tree[maxn];
平衡化旋转大致分为4类
1、zig旋转:
当平衡树中插入的节点在第一个不平衡的节点的左子树的左子树中,我们就要对平衡树进行zig旋转
代码:
int zig(int r) { int t=tree[r].lc; tree[r].lc=tree[t].rc; tree[t].rc=r; tree[r].h=max(tree[tree[r].rc].h,tree[tree[r].lc].h)+1; tree[t].h=max(tree[tree[t].rc].h,tree[tree[t].lc].h)+1; return t; }
2、zag旋转:
当平衡树中插入的节点在第一个不平衡的节点的右子树的右子树中,我们就要对平衡树进行zag旋转
代码:
int zag(int r) { int t=tree[r].rc; tree[r].rc=tree[t].lc; tree[t].lc=r; tree[r].h=max(tree[tree[r].rc].h,tree[tree[r].lc].h)+1; tree[t].h=max(tree[tree[t].rc].h,tree[tree[t].lc].h)+1; return t; }
3、zigzag旋转:
当平衡树中插入的节点在第一个不平衡的节点的左子树的右子树中,我们就要对平衡树进行zigzag旋转
代码:
int zigzag(int r) { tree[r].rc=zig(tree[r].rc); return zag(r); }
4、zagzig旋转:
当平衡树中插入的节点在第一个不平衡的节点的右子树的左子树中,我
4000
们就要对平衡树进行zagzig旋转
代码:
int zagzig(int r) { tree[r].lc=zag(tree[r].lc); return zig(r); }
插入操作跟二叉查找树差不多,只不过要注意插入后的调整:
int insert(int x,int r) { if(r==0){ tree[++cnt].v=x; tree[cnt].h=1; return cnt; } if(x<tree[r].v){ tree[r].lc=insert(x,tree[r].lc); if(tree[tree[r].lc].h==tree[tree[r].rc].h+2){ if(x<tree[tree[r].lc].v) r=zig(r); else if(x>tree[tree[r].lc].v) r=zagzig(r); } } else if(x>tree[r].v){ tree[r].rc=insert(x,tree[r].rc); if(tree[tree[r].rc].h==tree[tree[r].lc].h+2){ if(x>tree[tree[r].rc].v) r=zag(r); else if(x<tree[tree[r].rc].v) r=zigzag(r); } } tree[r].h=max(tree[tree[r].lc].h,tree[tree[r].rc].h)+1; return r; }
删除操作的思路跟二叉查找树的思路是一样的,但是调整整棵树就比较麻烦了(因为一次旋转不能达到平衡),所以说就要写一个maintain()函数,在每次调用dele()的最后都要调用一下maintain()。
void maintain(int &r) { if(tree[tree[r].lc].h==tree[tree[r].rc].h+2){ int t=tree[r].lc; if(tree[tree[t].lc].h==tree[tree[r].rc].h+1) r=zig(r); else if(tree[tree[t].rc].h==tree[tree[r].rc].h+1){ tree[r].lc=zag(tree[r].lc); r=zig(r); } } else if(tree[tree[r].lc].h+2==tree[tree[r].rc].h){ int t=tree[r].rc; if(tree[tree[t].rc].h==tree[tree[r].lc].h+1) r=zag(r); else if(tree[tree[t].lc].h==tree[tree[r].lc].h+1){ tree[r].rc=zig(tree[r].rc); r=zag(r); } } tree[r].h=max(tree[tree[r].lc].h,tree[tree[r].rc].h)+1; } int dele(int &r,int x) { int tx; if(x==tree[r].v||(x<tree[r].v&&tree[r].lc==0)||(x>tree[r].v&&tree[r].rc==0)){ if(tree[r].lc==0||tree[r].rc==0){ tx=tree[r].v; r=tree[r].lc+tree[r].rc; return tx; } else tree[r].v=dele(tree[r].lc,x); } else{ if(x<tree[r].v) tx=dele(tree[r].lc,x); else tx=dele(tree[r].rc,x); } maintain(r); return tx; }
AVL仍然可以采用惰性删除,对于要删除的节点,只需要给它进行一个标记。平衡树的高度仍然是log(N),这样并不会降低效率,而删除操作却要快速的多。
如果遇到那些删除过后还要恢复的节点,则惰性删除更优,不需要额外占用空间,将原来的节点恢复即可。
如果遇到那种允许节点重复的AVL,惰性删除更可取,将删除标记设为整型变量,表示该节点出现的数量即可。
查询操作和二叉查找树也大致相同。以下代码是查询一个数在序列中与其他数的最小的差值(即最接近的数与它的差)。
int find(int root,int x) { if(root==0) return 1<<30; if(x==tree[root].v) return 0; if(x>tree[root].v) return min(x-tree[root].v,find(tree[root].rc,x)); else return min(tree[root].v-x,find(tree[root].lc,x)); }
三、例题
营业额统计
题目描述
Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况。Tiger拿出了公司的账本,账本上记录了公司成立以来每天的营业额。分析营业情况是一项相当复杂的工作。由于节假日,大减价或者是其他情况的时候,营业额会出现一定的波动,当然一定的波动是能够接受的,但是在某些时候营业额突变得很高或是很低,这就证明公司此时的经营状况出现了问题。经济管理学上定义了一种最小波动值来衡量这种情况:
该天的最小波动值 = min {该天以前某一天的营业额 - 该天营业额}
当最小波动值越大时,就说明营业情况越不稳定。
而分析整个公司的从成立到现在营业情况是否稳定,只需要把每一天的最小波动值加起来就可以了。你的任务就是编写一个程序帮助Tiger来计算这一个值。
第一天的最小波动值为第一天的营业额。
天数小于100000.
输入
第一行为正整数 ,表示该公司从成立一直到现在的天数接下来的n行每行有一个整数(一定有数据小于〇) ,表示第i天公司的营业额。
输出
输出文件仅有一个正整数,即每一天的最小波动值之和。答案保证在int范围内。
样例输入
6 5 1 2 5 4 6
样例输出
12
提示
结果说明:5+|1-5|+|2-1|+|5-5|+|4-5|+|6-5|=5+4+1+0+1+1=12四、分析
一道典型的平衡树题目。
思路:
(1)输入一个数a。如果a为第一个,则sum+=a,进行步骤(3)。
(2)查找最接近a的数b与a之差,即abs(a-b)。sum+=abs(a-b)。
(3)插入a。
代码:
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
struct node{
int lc,rc,h,v;
}tree[100005];
int cnt;
int zig(int r) { int t=tree[r].lc; tree[r].lc=tree[t].rc; tree[t].rc=r; tree[r].h=max(tree[tree[r].rc].h,tree[tree[r].lc].h)+1; tree[t].h=max(tree[tree[t].rc].h,tree[tree[t].lc].h)+1; return t; }
int zag(int r) { int t=tree[r].rc; tree[r].rc=tree[t].lc; tree[t].lc=r; tree[r].h=max(tree[tree[r].rc].h,tree[tree[r].lc].h)+1; tree[t].h=max(tree[tree[t].rc].h,tree[tree[t].lc].h)+1; return t; }
int zagzig(int r) { tree[r].lc=zag(tree[r].lc); return zig(r); }
int zigzag(int r) { tree[r].rc=zig(tree[r].rc); return zag(r); }
int find(int root,int x) { if(root==0) return 1<<30; if(x==tree[root].v) return 0; if(x>tree[root].v) return min(x-tree[root].v,find(tree[root].rc,x)); else return min(tree[root].v-x,find(tree[root].lc,x)); }
int insert(int x,int r) { if(r==0){ tree[++cnt].v=x; tree[cnt].h=1; return cnt; } if(x<tree[r].v){ tree[r].lc=insert(x,tree[r].lc); if(tree[tree[r].lc].h==tree[tree[r].rc].h+2){ if(x<tree[tree[r].lc].v) r=zig(r); else if(x>tree[tree[r].lc].v) r=zagzig(r); } } else if(x>tree[r].v){ tree[r].rc=insert(x,tree[r].rc); if(tree[tree[r].rc].h==tree[tree[r].lc].h+2){ if(x>tree[tree[r].rc].v) r=zag(r); else if(x<tree[tree[r].rc].v) r=zigzag(r); } } tree[r].h=max(tree[tree[r].lc].h,tree[tree[r].rc].h)+1; return r; }
int main()
{
int n,i,x,sum=0,root=0,s;
scanf("%d",&n);
for(i=1;i<=n;i++){
scanf("%d",&x);
if(i==1){sum+=x;root=insert(x,root);continue;}
s=find(root,x);
if(s!=0){
root=insert(x,root);
sum+=abs(s);
}
}
printf("%d",sum);
}
相关文章推荐
- python数据结构之列表基本操作[学习笔记]
- 数据结构 (C语言)顺序表的基本操作
- 数据结构 队列的基本操作
- 数据结构 - 顺序栈的基本操作(C语言)
- 数据结构 - 串的性质和基本操作(二)
- java数据结构----图的基本操作
- 数据结构_顺序表的基本操作(c)
- *第十五周*数据结构实践项目三【B-树的基本操作】
- 栈的简单基本操作(数据结构)
- AVL的构建(插入操作)---《数据结构》严蔚敏
- 【数据结构】串的基本操作
- 数据结构 顺序栈基本操作
- 数据结构--二叉树(链表)基本操作
- 数据结构:栈的定义与基本操作
- 数据结构——二叉排序树的基本操作(BST)
- 数据结构循环队列的基本操作(C语言)
- splay伸展树 指针型 平衡树基本操作 序列维护 详细讲解+总结
- 数据结构--栈的基本操作
- 数据结构---链栈的基本操作
- 数据结构 图的基本操作实现