C++实现的完整伸展树程序

这两天学了伸展树,然后参考老师的一些源代码自己尝试写了下完整的实现过程。不过发现程序有些错误,于是自己开始调试,通过仔细地调试、一步步跟踪最终解决了自己发现的问题,在这个过程中发现伸展树的伸展过程挺复杂的,不过自己也从中提高了自己调试程序的能力,也加深了自己对伸展树程序执行过程的理解。如果大家发现以下程序的问题的话欢迎一起交流学习。


#include <iostream>
using namespace std;

struct Node
{
	Node* lc,*rc,*par;
	int val,weight; //weight表示该值出现的次数
}*root;


//右旋的操作(以x为轴心)
void rRotate(Node* x)
{
	//先处理x的右孩子
	Node* y=x->par;
	y->lc=x->rc;
	//x的右孩子不为空时
	if(x->rc) x->rc->par=y;
	
	//处理x
	x->par=y->par;
	//当y有双亲时
	if(y->par)
	{
		if(y==y->par->lc)
			y->par->lc=x;
		else
			y->par->rc=x;
	}
	//处理y
	x->rc=y;
	y->par=x;
}


//左旋的操作(以x为轴心)
void lRotate(Node* x)
{
	//先处理x的右孩子
	Node* y=x->par;
	y->rc=x->lc;
	if(x->lc) x->lc->par=y;
	
	//处理x
	x->par=y->par;
	//当y有双亲时
	if(y->par)
	{
		if(y==y->par->lc)
			y->par->lc=x;
		else
			y->par->rc=x;
	}
	//处理y
	x->lc=y;
	y->par=x;
}

//将结点x调整到以y为双亲的位置
void Splay(Node* x,Node* y)
{
	while(x->par!=y)
	{
		//快结束时
		if(x->par->par==y)
		{
			//判断x是双亲的左孩子还是右孩子
			(x->par->lc==x)? rRotate(x):lRotate(x);
		}
		else
		{
			//如果x双亲是它双亲的左孩子时
			if(x->par->par->lc==x->par)
			{
				//左左结构
				if(x->par->lc==x)
				{
					//两次右旋
					rRotate(x->par);
					rRotate(x);
				}
				//左右结构
				else
				{
					//先左旋再右旋
					lRotate(x);
					rRotate(x);
				}
			}
			else
			{
				//右右结构
				if(x->par->rc==x)
				{
					//两次左旋
					lRotate(x->par);
					lRotate(x);
				}
				//右左结构
				else
				{
					//先右旋再左旋
					rRotate(x);
					lRotate(x);
				}
			}
		}
	}
	//如果y为空,则将x置为根
	if(0==y) root=x;
}


//查找结点的函数
bool find(Node* x,int key)
{
	if(!x) return false;

	//找到时
	if(x->val==key)
	{
		Splay(x,0); //将x伸展到树根
		return true;
	}
	else
	{
		//递归调用继续查找
		if(x->val>key)
			return find(x->lc,key);
		else
			return find(x->rc,key);
	}
}

//插入操作
void insert(int key)
{
	Node *ptr=root,*y=0;

	int lrChose=0; //判断新的结点应该为y的左孩子还是右孩子
	while(true)
	{
		//找到插入位置时
		if(!ptr)
		{
			ptr=new Node;
			ptr->lc=ptr->rc=0;
			ptr->val=key;
			ptr->weight=1;
			ptr->par=y;
			if(y!=0)
			{
				if(0==lrChose) y->lc=ptr;
				else y->rc=ptr;
			}

			Splay(ptr,0); //做伸展
			break;
		}

		y=ptr;
		if(key==ptr->val)
		{
			++ptr->weight;
			Splay(ptr,0);
			break;
		}
		else
			if(key<ptr->val)
			{
				ptr=ptr->lc;
				lrChose=0;//表示新的结点在y的左孩子位置插入
			}
			else
			{
				ptr=ptr->rc;
				lrChose=1; //表示新的结点在y的右孩子位置插入
			}
	}
}

void preOrder(Node* r)
{
	if(r!=0)
	{
		for(int i=0;i<r->weight;++i)
			cout<<r->val<<" ";

		preOrder(r->lc);
		preOrder(r->rc);
	}
}


//合并两棵树的函数
Node* join(Node* n1,Node* n2)
{
	//将n1和n2置为各自树的树根
	if(n1) n1->par=0;
	if(n2) n2->par=0;

	//当其中一棵树为空时直接返回另一棵
	if(!n1) return n2;
	if(!n2) return n1;

	n1->par=n2->par=0;

	Node* temp=n1;
	//找出n1树最大的结点(在最右边)
	while(temp->rc) temp=temp->rc; 

	Splay(temp,0); //将temp伸展到树根(此时temp所在树的树根temp无右子树)

	temp->rc=n2;
	n2->par=temp;

	return temp; //返回合并后的树根
}

//删除结点的函数
void remove(Node* x)
{
	Splay(x,0); //先将x伸展到树根
	root=join(x->lc,x->rc); //合并左右子树
	delete x; //最后释放x的空间
}

//删除最小值
void delMin(int& min,int& cnt)
{
	Node* x=root;
	//最小值在最左边
	while(x->lc) x=x->lc;
	//通过引用参数带回改结点的一些信息
	min=x->val;
	cnt=x->weight;

	remove(x);
}

//删除最大值
void delMax(int& max,int& cnt)
{
	Node* x=root;
	//最大值在最右边
	while(x->rc) x=x->rc;
	//通过引用参数带回改结点的一些信息
	max=x->val;
	cnt=x->weight;

	remove(x);
}


int main()
{
	//test
	int val=0;

	while(cin>>val)
		insert(val);

	preOrder(root); //前序遍历
	cout<<endl;

	cin.clear(); //恢复输入流


	int findNum=0;
	cout<<"Enter the integer you want find:";
	cin>>findNum;
	if(find(root,findNum))
		cout<<"Has found!"<<endl;
	else
		cout<<"Has not found!"<<endl;


	int max=0,min=0,cnt=0;
	delMin(min,cnt);
	cout<<"min:"<<min<<",num:"<<cnt<<endl;

	delMax(max,cnt);
	cout<<"max:"<<max<<",num:"<<cnt<<endl;
}

通过伸展树,自己也获得了些启发,排序二叉树当输入的数据比较特殊时,所生成的树会像一条链表那样,这样会降低程序的效率,并且由于一般的排序二叉树一旦形成,结构很难再改变,因此这种结构会持续影响效率;而伸展树虽然刚建立起来时也有可能遇到这种情况,但随着操作越多,它的结构会越优化,算法效率会越高,因为它每执行一样操作几乎都会做一次伸展,使得树形结构趋于合理。而这正是我们做项目或者做系统需要考虑的,如何让自己的系统越来越优化而不是越来越糟糕使我们每个做项目的人都要去考虑的问题。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值