数据结构15————二叉树的遍历和建立
文章目录
一.内容
- 二叉树遍历的概念
- 二叉树的遍历 使用递归实现
- 二叉树的遍历 使用栈实现
- 二叉树的建立
- 二叉树遍历的应用
所有代码只是核心函数,完整代码见末尾链接
注意本篇博客都是基于二叉链表的实现
二.二叉树遍历的概念
如果我们要求次序的不重复的,遍历一颗树,并且限制从左到右习惯方式,一共有4种不同遍历方法。
1.层次遍历
按照二叉树的层,一层一层的访问。
例:对于二叉树T而言,它的层次遍历序列为ABCDEFG
2.前序遍历
对于一颗二叉树
- 先访问根节点
- 前序遍历左子树(按照前序遍历规则访问左子树,即先访问左子树的根,然后继续遍历它的左右子树,下同)
- 前序遍历右子树
例:对于二叉树T而言,它的前序遍历序列为ABDGCEF
3.中序遍历
对于一颗二叉树
- 中序遍历它的左子树
- 访问它的根节点
- 中序遍历它的右子树
例:对于二叉树T而言,它的中序遍历序列为DGBAECF
4.后序遍历
对于一颗二叉树
- 后序遍历它的左子树
- 后序遍历它的右子树
- 访问根节点
例:对于二叉树T而言,它的后序遍历序列为GDBEFCA
三.二叉树的遍历 递归实现
其实根据上面的前中后序遍历规则,基本来说就可以确定递归的写法了,不过要增加一个递归出口。当前指针为NULL时,结束。
1.前序遍历
void PreOrder(BiTree root){
int static count;
if(root==NULL)
return;
printf("%c",root->data);
PreOrder(root->lChild);
PreOrder(root->rChild);
}
2.中序遍历
void InOrder(BiTree root){
if(root==NULL)
return;
InOrder(root->lChild);
printf("%c",root->data);
InOrder(root->rChild);
}
3.后序遍历
void PostOrder(BiTree root){
if(root==NULL)
return;
PostOrder(root->lChild);
PostOrder(root->rChild);
printf("%c",root->data);
}
4.层次遍历
对于层次遍历而言,不能使用递归来进行遍历。要使用队来进行。(类似于图的广度优先遍历)
以树T为例
先将A入队 队内元素A 遍历序列为空
将队首A出队,访问,入队A的左右子树,队内元素BC,遍历序列A
将队首B出队,访问,入队B的左右子树,队内元素CD,遍历序列AB
将队首C出队,访问,入队C的左右子树,队内元素DEF,遍历序列ABC
将队首D出队,访问,入队D的左右子树, 队内元素EFG,遍历序列ABCD
将队首E出队,访问,入队E的左右子树, 队内元素FG,遍历序列ABCDE
将队首F出队,访问,入队F的左右子树, 队内元素G,遍历序列ABCDE
将队首G出队,访问,入队G的左右子树, 队内元素空,遍历序列ABCDEFG
队空,结束
void levelOrder(BiTree root){
BiTree p=root;
CSeQeue *S;
S=InitSeQueue();//创建队
InSeQueue(S,p);//入队
while(!EmptySeQueue(S)){
QutSeQueue(S,&p);//出队
printf("%c",p->data);
if(p->lChild!=NULL)
InSeQueue(S,p->lChild);//入队
if(p->rChild!=NULL)
InSeQueue(S,p->rChild);//入队
}
}
三.二叉树的遍历 栈实现
1.前序遍历
对于前序遍历而言,使用栈来实现,和递归很像
规则,
- 先访问根节点,然后将根节点入栈
- 遍历左子树。遍历完之后,
- 出栈,获得右子树指针,遍历它的右子树
以树T为例:(可以自己根据代码走一遍)
访问A节点,入栈,栈内元素A,遍历序列A
访问A节点的左子树B,B入栈,栈内元素AB,遍历序列AB
访问B节点的左子树D,D入栈,栈内元素ABD,遍历序列ABD
访问D节点的左子树,左子树为空,左子树的访问完毕,出栈栈顶D,栈内元素AB,遍历序列为ABD
访问D节点的右子树G,G入栈,栈内元素ABG,遍历序列ABDG
访问G节点的左子树,左子树为空,出栈栈顶G,栈内元素AB,遍历序列ABDG
访问G节点的右子树,右子树为空,出栈栈顶B,栈顶元素A,遍历序列为ABDG
访问B节点的右子树,右子树为空,出栈栈顶A,栈内元素空,遍历序列ABDG
访问A节点的右子树C,C入栈,栈内元素C,遍历序列ABDGC
……
最后当需要出栈是,栈为空,则表示遍历完成
void PreOrder(BiTree root){
BiTree p;
SeqStack *S;
S = InitStack();
p = root;
while(p!=NULL||StackEmpty(S)==0){
while(p!=NULL){
printf("%c",p->data);
Push(S,p); //入栈
p=p->lChild; //遍历左子树
}
if(StackEmpty(S)==0){
Pop(S,&p);//出栈
p=p->rChild; //遍历右子树
}
}
}
2.中序遍历
和前序遍历类似。
- 先入栈,遍历它的左子树 左子树遍历完之后
- 出栈,访问根节点,
- 遍历右子树
void InOrder(BiTree root){
BiTree p;
SeqStack *S;
S = InitStack();
p = root;
while(p!=NULL||StackEmpty(S)==0){
while(p!=NULL){
Push(S,p); //入栈
p=p->lChild; //遍历左子树
}
if(StackEmpty(S)==0){
Pop(S,&p);//出栈
printf("%c",p->data);
p=p->rChild; //遍历右子树
}
}
}
3.后序遍历
和前序中序遍历不同,后序遍历稍微麻烦些,因为涉及到两次访问栈顶
- 先入栈,,遍历左子树,左子树遍历完之后
- 访问栈顶(不出栈) 得到右子树指针,遍历右子树,右子树遍历完之后
- 出栈栈顶元素,访问根节点
void PostOrder(BiTree root){
BiTree p,q;
SeqStack *S;
S = InitStack();
q = NULL;
p = root;
while(p!=NULL||StackEmpty(S)==0){
while(p!=NULL){ //出循环时p左子树为空
Push(S,p); //入栈
p=p->lChild; //遍历左子树
}
if(StackEmpty(S)==0){
GetTop(S,&p);
if(p->rChild==NULL||p->rChild==q){ //如果栈顶元素p右子树为空,或者上一步输出的是他的右子树
Pop(S,&p);
printf("%c",p->data);
q=p;
p=NULL;
}else{ //如果上一步访问是栈顶元素的左子树
p=p->rChild;
}
}
}
}
四.二叉树的建立
单独一个遍历序列是无法唯一确定一颗二叉树的,想要根据遍历序列确定一颗二叉树,只有两种方法,一种是根据扩展的遍历序列,一种是根据中序遍历序号和前序(中序)遍历序列。所以我们二叉树的创建就是根据这两种思路来进行的。 我们以扩展的先序遍历序列和先序+中序遍历序列为例创建二叉树
1.根据扩展的先序序列创建二叉树.
a. 扩展的先序序列
扩展的先序序列,就是将二叉树节点的左右空子树也用特殊符号表示出来。
比如树T的扩展序列为ABDG^CEF^
b. 代码
BiTree CreatBiTree(BiTree root){ //二叉树的建立(由扩展的先序序列建立的二叉树)
static int count;
char ch=str[count];
count++;
if(ch=='#')
return NULL;
root = (BiTNode *)malloc(sizeof(BiTNode));
root->data = ch;
root->lChild = CreatBiTree(root->lChild);//以当前节点的左指针为下一级二叉树的跟
root->rChild = CreatBiTree(root->rChild);//以当前节点的右指针为下一级二叉树的跟
return root;
}
2.根据先序和中序序列创建二叉树
思路,根据先序序列确定根,根据中序序列确定根节点的左右子树。然后根据先序序列确定左右子树的根节点,根据中序序列确定左右子树的左右子树…
BiTree CreatBiTree(char *frontArray,char *centreArray,int n){
BiTree root;
char lfArray[N],rfArray[N];
char lcArray[N],rcArray[N];
int ln,rn,i,j;
char ch;
if(n==0)
return NULL;
ch = frontArray[0];
root = (BiTNode *)malloc(sizeof(BiTNode));
root->data = ch;
for(i=0;centreArray[i]!=ch;i++){ //左子树的后序
lcArray[i] = centreArray[i];
}
ln=i;
i++;
for(rn=0;i<n;rn++,i++){ //右子树的后序
rcArray[rn] = centreArray[i];
}
for(i=0;i<ln;i++){ //左子树的先序
lfArray[i] = frontArray[i+1];
}
for(j=0;j<rn;j++,i++){ //右子树的先序
rfArray[j]=frontArray[i+1];
}
root->lChild = CreatBiTree(lfArray,lcArray,ln);//以当前节点的左指针为下一级二叉树的跟
root->rChild = CreatBiTree(rfArray,rcArray,rn);//以当前节点的右指针为下一级二叉树的跟
return root;
}
五.二叉树的遍历的应用
1. 节点及所在层次
void PreOrder(BiTree root){ //先序遍历输出加层次
int static count;
if(root==NULL)
return;
count++;
printf("(%c,%d)",root->data,count);
PreOrder(root->lChild);
PreOrder(root->rChild);
count--;
}
2. 某层叶子节点个数
int f1(BiTree root,int k){
int static countlevel;
int static countleaf;
if(root==NULL)
return;
countlevel++;
if(countlevel == k&&root->lChild==NULL&&root->rChild==NULL)
countleaf++;
f1(root->lChild,k);
f1(root->rChild,k);
countlevel--;
return countleaf;
}
3. 交换左右子树
void exchange(BiTree root){ //交换各节点的左右子树
BiTree t;
if(root==NULL)
return;
t=root->lChild;
root->lChild=root->rChild;
root->rChild=t;
exchange(root->lChild);
exchange(root->rChild);
}
4. 根节点到叶子节点的路径
void PreOrder(BiTree root){
int static count;
int i;
if(root==NULL)
return;
count++;
if(root->lChild==NULL&&root->rChild==NULL){
printf("%c:",root->data);
for(i=1;i<count;i++){
printf("%c",array[i]);
}
printf("\n");
}else{
array[count]=root->data;
}
PreOrder(root->lChild);
PreOrder(root->rChild);
count--;
}
5. 两个节点的共同祖先
int count;
int flag; //标记是否找到
void PreOrder(BiTree root,char ch,char *array){
if(root==NULL)
return ;
count++;
if(root->data==ch){
flag = 1;
array[count]=0;
return ;
}else if(flag ==0&&root!=NULL){
array[count]=root->data;
}
if(flag==0){
PreOrder(root->lChild,ch,array);
PreOrder(root->rChild,ch,array);
}
count--;
return;
}