树的几种操作
包括前序、中序、后序、层序遍历,已经树的重建!
题目出自《编程之美》(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;