学习随记三十五——递归实现伸展树

递归实现伸展树


前言


在我完成非递归实现AVL树后,触类旁通后面相似的树的操作我也很快就实现,zhuoci。


一、伸展树是什么?

我的感觉伸展树是一种结合了二叉查找树与AVl树特点的树。伸展树的插入删除与二叉查找树无异,伸展树的旋转又有点借鉴了AVL树旋转的意味,不同的是伸展树不需要高度所以也不需要考虑平衡,伸展树的调整是发生在查找时,将每次查找的元素所在的结点旋转至根结点,旋转共有两种类型,之字形与AVl的双旋转。

二、程序剖析

  1. 结点声明:
    数据域,左儿子,右儿子指针
typedef struct Treenode{
	ElementType data=0;
	struct Treenode* Left=nullptr;
	struct Treenode* Right=nullptr;
}Treenode;
typedef Treenode* Splaytree;
  1. 插入函数:
    与二叉查找树的递归插入一毛一样
Splaytree Insert(Splaytree root,ElementType x){
	if(root==nullptr){
		root=new Treenode;
		root->data=x;
	}else if(root->data>x)root->Left=Insert(root->Left,x);
	else root->Right=Insert(root->Right,x);
	return root;
}
  1. 删除函数:
    函数参数:根结点,待删除数据
    函数实现:如果待删除元素比结点元素小则向左子树递归删除,比结点元素大则向右子树递归删除;若相等则有三种情况,待删除结点有两个儿子,一个儿子,树叶,先判断左右指针是否为空,若都非空则说明待删除结点有两个儿子,则在其右子树中找到最小的结点,用其数据覆盖待删除结点,然后再递归删除其右子树中最小的那个结点;反之则说明待删除结点只有一个儿子或为树叶,使用一个中间指针保存这个结点地址,若其左儿子非空则移动指针指向其左儿子,反之指向它的右儿子,再删除中间指针保存的结点,最后返回指针指向的结点地址。
Splaytree Delete(Splaytree root,ElementType x{
	Splaytree tempcell=nullptr;
	if(root==nullptr){
		cout<<"Not have this element."<<endl;
		return 0;
	}
	else if(root->data>x) root->Left=Delete(root->Left,x);
	else if(root->data<x) root->Right=Delete(root->Right,x);
	else if(root->Left&&root->Right){		//two children
				tempcell=FindMin(root->Right);
				root->data=tempcell->data;
				root->Right=Delete(root->Right,root->data);
	}else{													//one or zero child
				tempcell=root;
				if(root->Left==nullptr) root=root->Right;
				else root=root->Left;
				delete(tempcell);
			}
	return root;
}
  1. 查找函数:
    这个程序的重点功能函数
    函数参数:结点地址,待查找元素
    函数实现:因为查找时要实现树的调整将每次查找的元素所在的结点移动到根结点,所以调整要放在定位到这个结点之后,自下而上递归调整。先递归定位到待查找结点返回待查找结点的地址,又因为要记录移动路径,且因为函数是递归的,变量的作用域与生存期不同,因此在函数外声明了一个全局变量指针栈,每次移动时将结点地址压入栈中,当函数经过递归调用返回待查找结点地址时,判断栈是否为空,如果栈非空则取栈顶指针传给父结点,然后出栈,如果栈还是非空则取栈顶指针传给爷爷结点,如果栈空则使爷爷结点等于父结点(说明待查找元素的父结点为根结点,使用这个作为判断条件),如果父指针指向的结点为待查找元素所在结点的父结点,且爷爷指针指向的结点为父指针指向的结点的父结点或爷爷结点等于父结点(待查找元素所在结点的父结点为根结点)则进行旋转,否则若爷爷指针不等于父指针(防止最后进行到根结点时使根结点指向待查找元素所在结点形成闭合的树)则根据爷爷指针指向的结点成为待查找元素所在结点的父结点,最后若结点元素等于待查找元素则返回这个结点,否则返回递归传回来的待查找元素所在结点的地址。
Splaytree Find(Splaytree root,ElementType x){
	Splaytree father=nullptr,grandfather=nullptr,temp=nullptr;
	if(root==nullptr){
		cout<<"Not found."<<endl;
		return nullptr;
	}else{                             
		if(root->data!=x){
			str.push(root);
			if(root->data>x) temp=Find(root->Left,x);
			else if(root->data<x) temp=Find(root->Right,x);
			if(!str.empty()){
				father=str.top();
				str.pop();
				if(!str.empty())grandfather=str.top();
				else grandfather=father; 
				if((father->Left==temp||father->Right==temp)&&((grandfather->Left 
==father||grandfather->Right==father)||grandfather==father)){
				 Rotate(temp,father,grandfather);
				 }
				 else{
				 	if(grandfather!=father){
				   if(grandfather->data>temp->data)grandfather->Left=temp;
				   else grandfather->Right=temp;
				 }
				}
			}
	 }else return root;
	}
	return temp;
}
  1. 旋转函数:
    函数参数:待查找元素所在的结点,它的父结点与它的爷爷结点
    函数实现:查找函数的关键所在,有三种情况对应三种旋转方式,一种是一字形,旋转条件类似于AVL树中的左——左,右——右,即爷爷结点与父结点之间的关系与父结点与待查找元素结点之间的关系相同,只不过旋转方式不同,一字形旋转使的爷爷结点成为父结点的子结点同时父结点被代替的子结点成为爷爷结点的子结点,然后父结点成为待查找元素结点的子结点,待查找元素结点被代替的子结点成为父结点的子结点;二是之字形,对应于AVL树中的左——右、右——左,即爷爷结点与父结点以及父结点与待查找元素结点的关系相反,旋转方式为AVl树中的标准双旋转方式,先是父结点的另外一个子结点成为爷爷结点的子结点,然后使爷爷结点成为那个被代替的子结点,同理,待查找元素的另外一个子结点成为父结点的子结点,然年父结点成为待查找元素结点的子结点;三是父结点与爷爷结点是一个结点说明待查找元素结点的父结点为根结点,使待查找元素结点的一个子结点代替待查找元素成为父结点的子结点,然后使父结点成为待查找元素的子结点。
void Rotate(Splaytree root,Splaytree father,Splaytree grandfather){
	if(father->data<grandfather->data){
		if(root->data<father->data){			//左一字形旋转
		 grandfather->Left=father->Right;
		 father->Right=grandfather;
		 father->Left=root->Right;
		 root->Right=father;
		}else{														//左双旋转
		 father->Right=root->Left;
		 root->Left=father;
		 grandfather->Left=root->Right;
		 root->Right=grandfather; 
		}
	}else if(father->data>grandfather->data){
		if(root->data>father->data){			//右一字形旋转
		 grandfather->Right=father->Left;
		 father->Left=grandfather;
		 father->Right=root->Left;
		 root->Left=father;
		}else{														//右双旋转
		 father->Left=root->Right;
		 root->Right=father;
		 grandfather->Right=root->Left;
		 root->Left=grandfather; 
		}
	}else{		//父结点与祖父结点相等,说明为根结点
		if(root->data<father->data){	
			father->Left=root->Right;
			root->Right=father;
		}else{
			father->Right=root->Left;
			root->Left=father;
		}
	}		
}

2.整体代码

代码如下(示例):

#include<iostream>
#include<stack>
typedef int ElementType;
typedef struct Treenode{
	ElementType data=0;
	struct Treenode* Left=nullptr;
	struct Treenode* Right=nullptr;
}Treenode;
typedef Treenode* Splaytree;
using namespace std;
stack<Splaytree>str;														//查找时用来记录路径 
Splaytree Insert(Splaytree,ElementType);				//插入函数 
Splaytree Find(Splaytree,ElementType);					//查找函数 
Splaytree FindMin(Splaytree);										//找最小结点 
Splaytree Delete(Splaytree,ElementType);				//删除函数 
void Rotate(Splaytree,Splaytree,Splaytree);			//旋转函数 
void PrintTree(Splaytree);											//打印函数 
int main(void){
	ElementType x;
	Splaytree root=nullptr;
	cout<<"Please input number you want to insert."<<endl;
	while(cin>>x) root=Insert(root,x);
	cin.clear();
	cin.ignore();
	cout<<"Please input number you want to find."<<endl;
	while(cin>>x){
		root=Find(root,x);
		Splaytree p=root;
		cout<<endl;
	//root=Delete(root,x);
	  PrintTree(root);
	}
	return 0;
}
Splaytree FindMin(Splaytree root){
	if(root==nullptr)return nullptr;
	else if(root->Left==nullptr)return root;
	else return FindMin(root->Left);
}
void Rotate(Splaytree root,Splaytree father,Splaytree grandfather){
	if(father->data<grandfather->data){
		if(root->data<father->data){			//左一字形旋转
		 grandfather->Left=father->Right;
		 father->Right=grandfather;
		 father->Left=root->Right;
		 root->Right=father;
		}else{														//左双旋转
		 father->Right=root->Left;
		 root->Left=father;
		 grandfather->Left=root->Right;
		 root->Right=grandfather; 
		}
	}else if(father->data>grandfather->data){
		if(root->data>father->data){			//右一字形旋转
		 grandfather->Right=father->Left;
		 father->Left=grandfather;
		 father->Right=root->Left;
		 root->Left=father;
		}else{														//右双旋转
		 father->Left=root->Right;
		 root->Right=father;
		 grandfather->Right=root->Left;
		 root->Left=grandfather; 
		}
	}else{		//父结点与祖父结点相等,说明为根结点
		if(root->data<father->data){	
			father->Left=root->Right;
			root->Right=father;
		}else{
			father->Right=root->Left;
			root->Left=father;
		}
	}		
}
Splaytree Find(Splaytree root,ElementType x){
	Splaytree father=nullptr,grandfather=nullptr,temp=nullptr;
	if(root==nullptr){
		cout<<"Not found."<<endl;
		return nullptr;
	}else{                             
		if(root->data!=x){
			str.push(root);
			if(root->data>x) temp=Find(root->Left,x);
			else if(root->data<x) temp=Find(root->Right,x);
			if(!str.empty()){
				father=str.top();
				str.pop();
				if(!str.empty())grandfather=str.top();
				else grandfather=father; 
				if((father->Left==temp||father->Right==temp)&&((grandfather->Left 
==father||grandfather->Right==father)||grandfather==father)){
				 Rotate(temp,father,grandfather);
				}else{
				 	if(grandfather!=father){
				   if(grandfather->data>temp->data)grandfather->Left=temp;
				   else grandfather->Right=temp;
				 }
				}
			}
	 }else return root;
	}
	return temp;
}
Splaytree Insert(Splaytree root,ElementType x){
	if(root==nullptr){
		root=new Treenode;
		root->data=x;
	}else if(root->data>x)root->Left=Insert(root->Left,x);
	else root->Right=Insert(root->Right,x);
	return root;
}
Splaytree Delete(Splaytree root,ElementType x){
	Splaytree tempcell=nullptr;
	if(root==nullptr){
		cout<<"Not have this element."<<endl;
		return 0;
	}
	else if(root->data>x) root->Left=Delete(root->Left,x);
	else if(root->data<x) root->Right=Delete(root->Right,x);
	else if(root->Left&&root->Right){		//two children
				tempcell=FindMin(root->Right);
				root->data=tempcell->data;
				root->Right=Delete(root->Right,root->data);
	}else{													//one or zero child
				tempcell=root;
				if(root->Left==nullptr) root=root->Right;
				else root=root->Left;
				delete(tempcell);
			}
	return root;
}
void PrintTree(Splaytree root){
	if(root!=nullptr){
		PrintTree(root->Left);
		cout<<root->data<<endl;
		PrintTree(root->Right);
	}
}

总结

伸展树与二叉查找树与AVl树之间有着很强的联系,它们具有不同的使用场景,可以根据它们的特性选择不同的使用场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值