前面讲解了插入结点的操作过程,本文就分析一下程序是如何删除一个结点呢?
伸展树的删除图解:
假如伸展树结构如下图,而我们要删除结点6(根结点),操作的过程是怎么样的呢?(这里的图有一点错误:tail的父亲应该是:10,而不是1)
第一步:首先把结点6旋转到根结点(select操作)这样删除结点6的操作就变成了删除根结点的操作了。(这里的图有一点错误:tail的父亲应该是:10,而不是1)
第二步:如果根结点没有左孩子,那么直接把根结点的右孩子作为新的根结点,完成删除操作;类似的,如果根结点没有右孩子,那么把根结点的左孩子作为新的根结点,完成删除操作。
如果根结点有两个孩子,先把根结点左子树中最大的节点旋转到左孩子的位置(这样做保证了左孩子的右孩子为空)然后呢把根结点的右孩子挂接到左孩子的右孩子位置。
本图中结点5是整棵子树中最大的节点,所以不用旋转了。
第三步:把根结点6的右孩子8作为左孩子5的右孩子!(有点难懂!!还是看图吧!)然后把结点5设定为根结点。(这里的图有一点错误:tail的父亲应该是:10,而不是1)
这样就完成了一个结点的删除操作!这样很细致的一分析就发现思路清晰了,挺容易理解了!
刚开始我还有一个疑问,如果就这样删除了那么各结点的size怎么调整呢?后来发现我多虑了!在结点splay的过程中已经调整好了,也就是说,删除是不影响其它结点的size大小的,最后加一句:update(root);就搞定一切了! 这整个过程一定要对左旋(zag)和右旋(zig)理解清楚!!
最后把删除的代码贴出来吧!
- #include <iostream>
- #define INF ~0u>>1
- #define MN 200005
- #define NIL SPLAY
- using namespace std;
- struct SplayTree{
- struct Node{
- int key,size;
- int minv,add;
- bool rev,ishead;
- Node *left,*right,*father;
- Node(){}
- Node(int _key):key(_key){//开始我以为代码里并没有对key进行赋值,后来才发现“:key(_key)”这句代码已经对其赋值了
- minv=_key,size=1,add=0,rev=false;ishead=false;
- }
- }SPLAY[MN],*SP,*root,*head,*tail;
- //
- // //对伸展树进行初始化,也就是把head和tail加入到树中,
- void init(){
- SP=NIL;
- NIL->key=NIL->minv=INF;
- NIL->size=0;
- NIL->rev=false;NIL->add=0;//用于懒操作的
- NIL->left=NIL->right=NIL->father=NIL;
- head=new(++SP)Node(INF);
- head->ishead=true;
- head->left=head->right=head->father=NIL;
- tail=new(++SP)Node(INF);
- tail->left=tail->right=tail->father=NIL;
- head->right=tail;tail->father=head;
- head->size++;
- root=head;
- }
- //更新结点的值
- void update(Node *&t){
- t->size=t->left->size+t->right->size+1;
- t->minv=min(t->key,min(t->left->minv,t->right->minv));
- }
- //结点t往右旋转
- void zig(Node *&t){//表示t为f的左孩子
- Node *f=t->father,*r=t->right;//因为t的左结点不会变,而右结点要接到t->father的左边
- t->father=f->father;//这句很好理解:因为t要旋转到t->father的位置
- if(f==root) root=t;
- else{
- if(f->father->left==f) f->father->left=t;
- else f->father->right=t;
- }
- t->right=f,r->father=f,f->father=t,f->left=r;
- update(f);update(t);
- }
- //结点t往左旋转
- void zag(Node *&t){
- Node *f=t->father,*ll=t->left;//因为t的右结点不会变,而左结点要接到t->father的右边
- t->father=f->father;//这句很好理解:因为t要旋转到t->father的位置
- if(f==root) root=t;
- else{
- if(f->father->left==f) f->father->left=t;
- else f->father->right=t;
- }
- t->left=f,ll->father=f,f->father=t,f->right=ll;
- update(f);update(t);
- }
- //暂时只维护区间,不作伸展操作
- void splay(Node *&root,Node *&t){
- while(root!=t){
- if(t->father==root){
- if(t->father->left==t) zig(t);
- else zag(t);
- }
- else{
- if(t->father->father->left==t->father){
- if(t->father->left==t) zig(t->father),zig(t);
- else zag(t),zig(t);
- }else{
- if(t->father->left==t) zig(t),zag(t);
- else zag(t->father),zag(t);
- }
- }
- }
- }
- //往第pos位后插入key
- void insert(int key,int pos){
- Node *t;t=new(++SP)Node(key);
- t->left=t->right=t->father=NIL;
- Node *r,*p;r=root;
- bool flag=false;//默认朝左边走
- while(r!=NIL){
- p=r;r->size++;
- if(r->left->size+1>pos)r=r->left,flag=false;
- else pos-=r->left->size+1,r=r->right,flag=true;
- }
- if(flag)p->right=t;
- else p->left=t;
- t->father=p;
- splay(root,t);
- }
- //把pos位置的结点旋转到根结点
- void select(Node *&root,int pos){
- Node *r=root;
- while(r->left->size+1!=pos){
- if(r->left->size+1>pos) r=r->left;
- else pos-=r->left->size+1,r=r->right;
- }
- splay(root,r);
- }
- //删除结点
- void remove(int pos){
- select(root,pos);
- if(root->left==NIL) root=root->right;
- else if(root->right==NIL) root=root->left;
- else{
- select(root->left,root->left->size); //这一句保证了root的左孩子的右孩子为空
- root->left->right=root->right;
- root->right->father=root->left;
- root=root->left;
- }
- root->father=NIL;
- update(root);
- }
- void inorder(Node *t){
- if(t->left!=NIL)
- inorder(t->left);
- printf("%d ",t->key);
- if(t->right!=NIL)
- inorder(t->right);
- }
- }tree;
- //这里需要注意的是,SplayTree一定要定义成全局变量,不然会因为栈溢出而报内存错误
- int main(){
- int a[10]={0,1,2,3,4,5};//a[0]位置的数据不用
- int i;
- //初始化
- tree.init();
- //初始化序列数据
- for(i=1;i<=3;i++){
- tree.insert(a[i],i);
- }
- // tree.inorder(tree.root);cout<<endl;
- tree.remove(3);//删除结点2 因为我们根结点的左边多了一个head结点,所以加1
- //按下标的顺序输出
- tree.inorder(tree.root);
- return 0;
- }