二叉树的非递归遍历与重建

树的几种操作

包括前序、中序、后序、层序遍历,已经树的重建!


题目出自《编程之美》(3.9重建二叉树,3.10分层遍历二叉树)


树重建:

给出树的前序遍历和中序遍历序列,重建树的结构(也可给出中序遍历和后序遍历),很简单(《编程之美》为数不多的能够,自己独立完成的!!!!Qrn)!

void Rebuild(int* pPreOrder,int* pInOrder,int n,NODE* &root)
{
	if(n==0)
		return;
	//只有一个节点
	if(n==1)
	{
		root=new NODE(*pPreOrder);
		return;
	}
	root=new NODE(*pPreOrder);
	//假设数的元素没有重复
	//查找根节点在中序遍历的位置,前序遍历自然第一个节点就是根
	int k;
	int* tmp;
	for(k=0,tmp=pInOrder;k<n;k++)
		if(*tmp==*pPreOrder)
			break;
		else
			tmp++;
	//建立根的左子树和右子树
	NODE* pL=NULL;
	NODE* pR=NULL;
	Rebuild(pPreOrder+1,pInOrder,k,pL);
	Rebuild(pPreOrder+k+1,pInOrder+k+1,n-k-1,pR);
	root->pLeft=pL;
	root->pRight=pR;
}

树的四种非递归遍历


下午上了会网,发现树的前序,中序,层序,后序遍历是针对二叉树的!!!!!!!

对于一般的树,遍历主要分为两类,深度优先遍历,广度优先遍历~~~!

所以,前序,中序,后序遍历应该是一种深度遍历的实现,而层序遍历是广度优先遍历的实现,前者可以用一个栈来模拟,后者可以用一个队列来模拟!!


先说明树的层序遍历,这里我的理解是:层序遍历可以把树抽象层一个队列,按照深度的大小来出队,入队

直接上代码

void PrintLevel(NODE* root)
{
	NODE *CurNode=root;
	vector<NODE*> tmp;
<span style="white-space:pre">	</span>//其实下面两个参数是为了,将每一层分开,否则完全不需要,只要一个简单的对列就行了
	int cur=0;//当前访问的节点
	int last=1;//当前这层最后一个节点后面的一个位置
	if(CurNode==NULL)
		return;
	else
		tmp.push_back(CurNode);
	while(true)
	{
		while(cur<last)//第k层还未打印完成
		{
			CurNode=tmp[cur++];
			cout<<CurNode->Value;
			//将第k+1层节点入队
			//实际上是一层一层入队的,及打印一层,入队下一层
			if(CurNode->pLeft!=NULL)
				tmp.push_back(CurNode->pLeft);
			if(CurNode->pRight!=NULL)
				tmp.push_back(CurNode->pRight);
		}
		//第k层打印好了准备打印k+1
		cout<<endl;
		last=tmp.size();
		if(cur==last)//没有下一层了
			break;
	}
	tmp.clear();
}

《编程之美》里还有一个递归的方案,打印树的第k层

void PrintNodeAtLevel(NODE* root,int level)
{
	//访问二叉树的某一层
	if(root==NULL||level<0)
		return;
	if(level==0)
	{
		cout<<root->Value;
	}
	else
	{
		PrintNodeAtLevel(root->pLeft,level-1);
		PrintNodeAtLevel(root->pRight,level-1);
	}
	
}
附加题,倒着层序遍历,我的做法一次将层序遍历完,然后倒着输出,层与层间用NULL隔开,如果队列中出现连续两个NULL表示已经遍历完成

void PrintLevelR(NODE* root)
{
	//一次将所有节点入栈,层与层用NULL分开
	vector<NODE*> tmp;
	int cur=-1;//当前访问的节点
	int last=1;//当前这层最后一个节点后面的一个位置
	NODE* tmpNODE;
	if(root==NULL)
		return;
	else
		tmp.push_back(root);
	while(true)
	{
		last=tmp.size();
		tmp.push_back(NULL);//NULL作为分隔符
		cur++;//越过分隔的NULL
		while(cur<last)//第k层
		{
			//将第k+1层节点入栈
			if(tmp[cur]->pLeft!=NULL)
				tmp.push_back(tmp[cur]->pLeft);
			if(tmp[cur]->pRight!=NULL)
				tmp.push_back(tmp[cur]->pRight);
			cur++;
		}
		//连续两个NULL表示已经结束
		if(tmp[last-1]==NULL)
			break;
	}
	//倒序打印
	for(int i=tmp.size()-3;i>=0;i--)
	{
		if(tmp[i]!=NULL)
			cout<<tmp[i]->Value;
		else
			cout<<endl;
	}
}

其他三种遍历的非递归实现


前序遍历可以和层遍历形成鲜明对比,前序遍历是栈的形式,而层序遍历是队列的形式

void PrintPre(NODE* root)
{
	if(!root)
		return;
	stack<NODE*> s;//建立一个栈模拟递归
	NODE* tmpNode;
	s.push(root);
	while(!s.empty())
	{
		tmpNode=s.top();
		cout<<tmpNode->Value;
		s.pop();
		if(tmpNode->pRight!=NULL)
			s.push(tmpNode->pRight);
		if(tmpNode->pLeft!=NULL)
			s.push(tmpNode->pLeft);
	}
}


下面两个比较难


中序遍历,对于某一个节点p,如果p有左儿子,则将它入栈。对于出栈的p=s.top(),是已经考虑过左子的,可以直接打印,接着令p=p->pRight


void PrintIn(NODE* root)
{
	if(!root)
		return;
	stack<NODE*> s;//建立一个栈模拟递归
	NODE* tmpNode=root->pLeft;//这里的初值很关键
	s.push(root);
	while(!s.empty()||tmpNode!=NULL)//tmpNode!=NULL的限制是考虑节点,只有右儿子的情况,
	{
		while(tmpNode!=NULL)
		{
			s.push(tmpNode);
			tmpNode=tmpNode->pLeft;
		}
		tmpNode=s.top();
		s.pop();
		cout<<tmpNode->Value;//已经没有左儿子了,打印自己
		tmpNode=tmpNode->pRight;//只要右儿子不空入栈,右儿子也空,打印完毕
	}
}


后序遍历:

主要利用后序遍历的特点,如果节点p有右儿子,那么遍历p时,上一个遍历的节点一定是p的右儿子。

1。先将p的左儿子入栈,p=p->pL;

2。p==NULL,出栈一个p

void PrintPost(NODE* root)
{
	if(root==NULL)
		return;
	stack<NODE*> s;
	s.push(root);
	NODE *p=root->pLeft;
	NODE *last=NULL;
	while(!s.empty()||p!=NULL)
	{
		while(p!=NULL)
		{
			s.push(p);
			p=p->pLeft;
		}
		p=s.top();
		if(p->pRight==NULL)
		{
			cout<<p->Value;
			s.pop();
			last=p;
			p=NULL;
		}else
		{
			if(last==p->pRight)
			{
				cout<<p->Value;
				s.pop();
				last=p;
				p=NULL;
			}else
				p=p->pRight;
		}
		
	}
}

3。对于2。中的p,如果p没有右子直接打印,p=NULL

如果p有右儿子,last!=p的右儿子,p=p->pR; 如果last==p的右儿子,打印,p==NULL;






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值