欲谈树之遍历,先谈树之结构 :
看代码:
struct Node{
char data;
Node * lChild;
Node * rChild;
} *BiTree;
顾名思义,树是结点的集合。如上代码,我们定义了结点的结构。那么,问题来了,我们如何访问节点? 答案很简单,指针。
即Node * q q表示指向结点的指针 。 那么按照这个思路,我们如何定义树? 用Node ** Bitree=new Node *[size]? 事实上,不用那么麻烦,如上我们采用了结点表示法,即用树的根节点表示树,并通过lChild和rChild来联系结点与结点,其中如果结点指针指向NULL,则说明到了树的边界 即空节点。这样的话,相对于前一种表示法,显得更加简洁和灵活。
接着谈建树:
按照前序遍历的规则,根左右。看代码:
//example :A B C 0 0 D E 0 G 0 0 F 0 0 0
bool CreateBiTree(Node* &T)
{
char ch;
cin>>ch;
if(ch=='0')//空节点 用0来代替
T=NULL;
else
{
T=(Node*)malloc(sizeof(Node));//建树就是不断增加结点的过程
if(!T)
return false;
T->data=ch;
CreateBiTree(T->lChild);
CreateBiTree(T->rChild);
}
return true;
}
为了强化用根节点代表树的概念,我特地使用了Node* 参数,其中&是引用符 表示改变所给的树,这个和c++swap函数(指针,引用)实现的情况类似,如果没有这个引用符,将无法建树, 因为建立的树不会被保存到传递的参数上。
需要强调的一点是,因为树的边界是空节点,所以我们输入的时候会输入空节点,这里我用‘0’来代替。
最后谈遍历:
前序:根左右
中序:左根右
后序:左右根
根据以上规则很容易写出三者的递归版,下面仅以前序为例。
void PreOrderTraverse(Node * T)//递归方法
{
if(T==NULL)
{
//cout<<"pre"<<endl;
return;
}
cout<<T->data;
PreOrderTraverse(T->lChild);
PreOrderTraverse(T->rChild);
}
这里参数不需要添加&引用符的原因是它不改变传递进去的参数。
非递归版:
想要写非递归版的,就必须要了解三种遍历的实际情况。由二叉树遍历的定义可以知道,3种遍历算法的不同仅在于访问根节点和遍历左右子树的先后关系。如果在算法中暂且抹去和递归无关的visit语句(样例中体现为cout语句),则三种遍历算法完全相同。
现在看图:
如图可知,其实对于三种遍历的方法,他们实质上都是对树的每个节点访问三次,其中前序遍历在第一次访问节点时打印节点的信息,即如图三角形所示,中序遍历是在第二次访问节点时打印节点信息,如图圆形所示,后序遍历是在第三次访问节点时打印节点信息,如图正方形所示。
递归即是栈,下面构造栈来实现前序遍历与中序遍历:
typedef struct seqstack{
Node* data[SIZE];//类似一个树了 他要存储一组 Node指针
int top; //top:栈顶
}seqstack;
void push(seqstack &s,Node *t){
if(s.top == SIZE)
cout<<"the stack is full"<<endl;
else{
s.top++;
s.data[s.top]=t;
}
}
Node* pop(seqstack &s){
if(s.top == -1)
return NULL;
else{
s.top--;
return s.data[s.top+1];
}
}
然后就是实现遍历:
前序:
void PreOrderTraverse2(Node *t){//非递归版
seqstack s;
s.top = -1; //因为top在这里表示了数组中的位置,所以空为-1
if(t==NULL){
return;
}else{
while(t!=NULL || s.top != -1){//
while(t!=NULL){ //只要结点不为空就应该入栈保存,与其左右结点无关
cout<<t->data;//遍历根节点
push(s,t);
t= t->lChild;//遍历左孩子
}
t=pop(s);
t=t->rChild;//遍历右孩子
}
}
}
中序:
void InOrderTraverse2(Node *t){
seqstack s;
s.top = -1; //因为top在这里表示了数组中的位置,所以空为-1 //初始化栈顶
if(t==NULL){
return;
}else{
while(t!=NULL || s.top != -1){
while(t!=NULL){ //只要结点不为空就应该入栈保存,与其左右结点无关
push(s,t);
t= t->lChild;//左孩子入栈
}
t=pop(s);
cout<<t->data;//遍历根节点
t=t->rChild;//遍历右孩子
}
}
}
总体来说:三种遍历的非递归思路就是先遍历左子树,然后遍历右子树,差异无非是前序在遍历左子树之前打印节点信息,中序在遍历左子树之后遍历右子树之前打印节点信息,后序在遍历左子树,右子树之后再打印节点信息。后序的非递归代码,不同在于它得先判断右子树是否遍历过了才能选择是否输出节点信息,这个后续再加上。。。
整体代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#define SIZE 10000
using namespace std;
struct Node{
char data;
Node * lChild;
Node * rChild;
} *BiTree;
//example :A B C 0 0 D E 0 G 0 0 F 0 0 0
bool CreateBiTree(Node* &T)
{
char ch;
cin>>ch;
if(ch=='0')//空节点 用0来代替
T=NULL;
else
{
T=(Node*)malloc(sizeof(Node));//建树就是不断增加结点的过程
if(!T)
return false;
T->data=ch;
CreateBiTree(T->lChild);
CreateBiTree(T->rChild);
}
return true;
}
void PreOrderTraverse(Node * T)//递归方法
{
if(T==NULL)
{
//cout<<"pre"<<endl;
return;
}
cout<<T->data;
PreOrderTraverse(T->lChild);
PreOrderTraverse(T->rChild);
}
typedef struct seqstack{
Node* data[SIZE];//类似一个树了 他要存储一组 Node指针
int top; //top:栈顶
}seqstack;
void push(seqstack &s,Node *t){
if(s.top == SIZE)
cout<<"the stack is full"<<endl;
else{
s.top++;
s.data[s.top]=t;
}
}
Node* pop(seqstack &s){
if(s.top == -1)
return NULL;
else{
s.top--;
return s.data[s.top+1];
}
}
void PreOrderTraverse2(Node *t){//非递归版
seqstack s;
s.top = -1; //因为top在这里表示了数组中的位置,所以空为-1
if(t==NULL){
return;
}else{
while(t!=NULL || s.top != -1){
while(t!=NULL){ //只要结点不为空就应该入栈保存,与其左右结点无关
cout<<t->data;//遍历根节点
push(s,t);
t= t->lChild;//遍历左孩子
}
t=pop(s);
t=t->rChild;//遍历右孩子
}
}
}
void InOrderTraverse2(Node *t){
seqstack s;
s.top = -1; //因为top在这里表示了数组中的位置,所以空为-1 //初始化栈顶
if(t==NULL){
return;
}else{
while(t!=NULL || s.top != -1){
while(t!=NULL){ //只要结点不为空就应该入栈保存,与其左右结点无关
push(s,t);
t= t->lChild;//左孩子入栈
}
t=pop(s);
cout<<t->data;//遍历根节点
t=t->rChild;//遍历右孩子
}
}
}
int main()
{
CreateBiTree(BiTree);
PreOrderTraverse(BiTree);
cout<<endl;
PreOrderTraverse2(BiTree);
cout<<endl;
InOrderTraverse2(BiTree);
return 0;
}
example:A B C 0 0 D E 0 G 0 0 F 0 0 0
输出: