對二叉樹進行先序、中序、后序遍歷都是從根結點開始,且在遍歷的過程中,經過的節點路線都是一樣的,只不過訪問的順序不同。
先序遍歷是深入時遇到結點就訪問,中序遍歷是深入時從左子樹返回時遇到結點就訪問,而后序遍歷是從右子樹反回時遇到根結點就訪問,在這一過程中,反回結點的順序與深入結點的順序相反,即先深入再反回,這不禁讓人聯想到棧,而想要實現二叉樹的非遞歸遍歷,就需要用棧的思想來實現
先序遍歷(DLR)
先序遍歷的遞歸過程為
(1)訪問根結點
(2)先序遍歷根結點的左子樹
(3)先序遍歷根結點的右子樹
而先序遍歷的非遞歸過程為
先將根結點進棧,當棧不為空時,出棧並訪問,然后依次將右左結點進棧(棧先進后出,所以先進棧右結點)
如圖:
void NRPreOrder(Btnode *b)
{
Btnode *p;
SqStack st;
InitStack(st);
if (b != NULL)
{
Push(st, b); //根結點入棧
while (!StackEmpty(st))
{
Pop(st, p); //根結點出棧
printf("%c",p->data); //在此處用輸出根接地那的值表示出棧
if (p->right != NULL) //當右子樹非空
Push(st, p->right); //右孩子入棧
if (p->left != NULL) //當左子樹非空
Push(st, p->left); //左孩子入棧
}
printf("\n");
}
DestroyStack(st);
}
還有一種前序遍歷的非遞歸方式:
沿左子樹深入時,入棧之前訪問該結點
繼續深入,為NULL時,則返回並彈出前面壓入的節點
從該結點的右子樹繼續深入
話不多說,來圖:
代碼如下:
void NRPreOrder(BiTree t) //非遞歸先序遍歷二叉樹
{
BiTree stack[maxsize],p;
int top; //棧頂
if(t==NULL) return; //樹為空則返回
top=0;
p=t; 這是一個用於存儲移動的指針的變量
while(!(p==NULL&&top==0)) //當根結點不為空且棧不為空時
{
while(p!=NULL) //先深入左子樹
{
printf("%2c",p->data); //在此處用打印表示訪問該結點
if(top<=maxsize-1)
{
stack[top]=p; //入棧
top++; //棧頂位置++
}
else //棧滿時
{
printf("棧溢出");
return;
}
p=p->lchild; //深入當前根結點的左子樹
}
if(top<=0)return;
else
{
top--; //棧頂--,
p=stack[top]; //彈出棧頂元素
p=p->rchild; //指向右子樹
}
}
中序遍歷(DLR)
而中序遍歷的方法和1先序遍歷極為相似,只是訪問的時機不停,在這里就不重復說明啦,當然,如果哪位有問題的話,歡迎私聊
void NRPreOrder(BiTree t) //非遞歸先序遍歷二叉樹
{
BiTree stack[maxsize],p;
int top; //棧頂
if(bt==NULL) return; //樹為空則返回
top=0;
p=bt; 這是一個用於存儲移動的指針的變量
while(!(p==NULL&&top==0)) //當根結點不為空且棧不為空時
{
while(p!=NULL) //先深入左子樹
{
if(top<=maxsize-1)
{
stack[top]=p; //入棧
top++; //棧頂位置++
}
else //棧滿時
{
printf("棧溢出");
return;
}
p=p->lchild; //深入當前根結點的左子樹
}
if(top<=0)return;
else
{
top--; //棧頂--,
p=stack[top]; //彈出棧頂元素
printf("%2c",&p->data); //在此處用打印表示訪問該結點
p=p->rchild; //指向右子樹
}
}
當然,除此之外,個人在bilibili上看到一個很好的演示過程,在這里推薦給米娜桑 https://www.bilibili.com/video/av40285886?from=search&seid=17491349128453302359
后序遍歷(DLR)
后序遍歷的特殊性在於,結點第一次出棧之后,還需要再次入棧,即一個結點出兩次入兩次棧,那么,是否該打印該結點的值成為了一個問題。這不禁讓我想到大一在做一個學生成績管理系統時遇到還未學習鏈表等結構時對於學生成績的刪除方式。當時在創建學生類時,為其添加了一個變量叫做“alive”,每一個類的對象的“alive”初始屬性都是1,當根據學號刪除該學生時,該學生類的“alive”屬性變為的0,這樣,在輸出、排序等操作所以學生的信息的時候,就不必再對“alive”屬性為0的學生進行操作
所以,這個方法也可以運用在次數
在創建棧時,我們可以為棧中元素添加一個“flag”屬性:
即:
typedef struct
{
BiTree link;
int flag;
}stacktype;
函數代碼:
void Nrpostorder(BiTree T)
{
stacktype stack[max];
BiTree p;
int top,sign;
if(T==NULL) return;
top=-1;
p=T;
while(!(p==NULL&&top==-1))
{
if(p!=NULL)
{
top++;
stack[top].link=p;
stack[top].flag=1;
p=p->lchild;
}
else
{
p=stack[top].link;
sign=stack[top].flag;
top--;
if(sign==1)
{
top++;
stack[top].link=p;
stack[top].flag=2;
p=p->rchild;
}
else
{
printf("%2c",p->data);
p=NULL;
}
}
}
}
最終代碼:
#include<stdio.h>
#include<stdlib.h>
#define MAX 20
typedef char TElemType;
typedef int Status;
typedef struct BiTNode
{
TElemType data;
struct BiTNode *lchild,*rchild; //左右孩子的指針
} BiTNode,*BiTree;
typedef struct
{
BiTree link;
int flag;
}stacktype;
//先序創建二叉樹
void CreateBiTree(BiTree *T)
{
char ch;
ch=getchar();
if(ch=='#')(*T)=NULL; //#代表空指針
else
{
(*T)=(BiTree)malloc(sizeof(BiTNode)); //申請節點
(*T)->data=ch; //生成跟節點
CreateBiTree(&(*T)->lchild);
CreateBiTree(&(*T)->rchild);
}
}
//先序輸出二叉樹
void PreOrder(BiTree T)
{
if(T)
{
printf("%2c",T->data); //訪問根節點,此處為輸出根節點的數據值
PreOrder(T->lchild); //先序遍歷左子樹
PreOrder(T->rchild); //先序遍歷右子樹
}
}
//中序輸出二叉樹
void InOrder(BiTree T)
{
if(T)
{
InOrder(T->lchild);
printf("%2c",T->data);
InOrder(T->rchild);
}
}
//后序輸出二叉樹
void PostOrder(BiTree T)
{
if(T)
{
PostOrder(T->lchild);
PostOrder(T->rchild);
printf("%2c",T->data);
}
}
//先序非遞歸先序
int NRPreOrder(BiTree T) //非遞歸先序遍歷二叉樹
{
BiTree stack[MAX],p;
int top; //棧頂
if(T==NULL) return 0; //樹為空則返回
top=0;
p=T; //這是一個用於存儲移動的指針的變量
while(!(p==NULL&&top==0)) //當根結點不為空且棧不為空時
{
while(p!=NULL) //先深入左子樹
{
printf("%2c",p->data); //在此處用打印表示訪問該結點
if(top<=MAX-1)
{
stack[top]=p; //入棧
top++; //棧頂位置++
}
else //棧滿時
{
printf("棧溢出");
return 0;
}
p=p->lchild; //深入當前根結點的左子樹
}
if(top<=0) return 0;
else
{
top--; //棧頂--,
p=stack[top]; //彈出棧頂元素
p=p->rchild; //指向右子樹
}
}
}
//中序非遞歸先序
int NRInOrder(BiTree T) //非遞歸先序遍歷二叉樹
{
BiTree stack[MAX],p;
int top; //棧頂
if(T==NULL) return 0; //樹為空則返回
top=0;
p=T; //這是一個用於存儲移動的指針的變量
while(!(p==NULL&&top==0)) //當根結點不為空且棧不為空時
{
while(p!=NULL) //先深入左子樹
{
if(top<=MAX-1)
{
stack[top]=p; //入棧
top++; //棧頂位置++
}
else //棧滿時
{
printf("棧溢出");
return 0;
}
p=p->lchild; //深入當前根結點的左子樹
}
if(top<=0) return 0;
else
{
top--; //棧頂--,
p=stack[top]; //彈出棧頂元素
printf("%2c",p->data); //在此處用打印表示訪問該結點
p=p->rchild; //指向右子樹
}
}
}
//層次遍歷二叉樹
void LevelOrder(BiTree T)
{
BiTree Queue[MAX],b; //用一維數組表示隊列,front和rear表示隊首和隊尾的指針
int front,rear;
front=rear=0;
if(T)
//若樹為空
{
Queue[rear++]=T; //根節點入隊列
while(front!=rear) //當隊列非空
{
b=Queue[front++]; //隊首元素出隊列,並訪問這個節點
printf("%2c",b->data);
if(b->lchild!=NULL) Queue[rear++]=b->lchild ; //若左子樹非空,則入隊列
if(b->rchild!=NULL) Queue[rear++]=b->rchild ; //若右子樹非空,則入隊列
}
}
}
//求樹的深度
int depth(BiTree T)
{
int dep1,dep2;
if(T==NULL) return 0;
else
{
dep1=depth(T->lchild);
dep2=depth(T->rchild);
return dep1>dep2?dep1+1:dep2+1;
}
}
//后序非遞歸
void Nrpostorder(BiTree T)
{
stacktype stack[MAX];
BiTree p;
int top,sign;
if(T==NULL) return;
top=-1;
p=T;
while(!(p==NULL&&top==-1))
{
if(p!=NULL)
{
top++;
stack[top].link=p;
stack[top].flag=1;
p=p->lchild;
}
else
{
p=stack[top].link;
sign=stack[top].flag;
top--;
if(sign==1)
{
top++;
stack[top].link=p;
stack[top].flag=2;
p=p->rchild;
}
else
{
printf("%2c",p->data);
p=NULL;
}
}
}
}
int main()
{
BiTree T=NULL;
printf("\n 創建一棵二叉樹: \n");
CreateBiTree(&T); //創建二叉樹
printf("\n先序遍歷的結果為:\n");
PreOrder(T); //先序遍歷
printf("\n先序非遞歸遍歷的結果為:\n");
NRPreOrder(T);
printf("\n中序遍歷的結果為:\n");
InOrder(T); //中序遍歷
printf("\n中序非遞歸遍歷的結果為:\n");
NRInOrder(T); //中序遍歷
printf("\n 后序遍歷的結果為: \n");
PostOrder(T);
printf("\n 后序遍歷非遞歸的結果為: \n");
Nrpostorder(T);
printf("\n 層次遍歷的結果為: \n");
LevelOrder(T); //層次遍歷
printf("\n 樹的深度為:%d\n",depth(T));
}
有一說一,這個博客寫了三天