遍历:按一定的规律对二叉树中的每个结点进行访问且仅访问一次。
访问:输出结点信息、打印树、统计数目、各种计算等操作。
目的:将非线性结构变成线性化的访问序列。
二叉树的遍历
二叉树的遍历方式
DLR
DRL
LDR
LRD
RDL
RLD
规定先左后右的顺序的三种方案
根据对根访问的先后命名遍历方案
DLR 先序遍历
LDR 中序遍历
LRD 后序遍历
三种遍历的递归定义
(1) 先序遍历(DLR)操作过程
若二叉树为空,则为空操作,否则依次执行如下三个操作:
① 访问根结点;
② 按先序遍历左子树;
③ 按先序遍历右子树。
先序遍历:ABDFGCEH
(2) 中序遍历(LDR)操作过程
若二叉树为空,则为空操作,否则依次执行如下三个操作:
① 按中序遍历左子树;
② 访问根结点;
③ 按中序遍历右子树。
先序遍历:BFDGACEH
(3) 后序遍历(LRD)操作过程
若二叉树为空,则为空操作,否则依次执行如下三个操作:
① 按后序遍历左子树;
② 按后序遍历右子树;
③ 访问根结点。
先序遍历:FGDBHECA
最早应用:存储在计算中的表达式求值
存储方式:先序
例如:(a+bc)-d/e
前缀:-+abc/de
中缀:a+bc-d/e
后缀:abc+de/-
逆波兰式易于求值
二叉树的遍历算法
(1) 先序遍历二叉树
void PreOrder(BiTree root) {
if(NULL!=root) {
Visit(root->data);
PreOrder(root->LChild);
PreOrder(root->RChild);
}
}
(2) 中序遍历二叉树
void InOrder(BiTree root) {
if(NULL!=root) {
InOrder(root->LChild);
Visit(root->data);
InOrder(root->RChild);
}
}
(3) 后序遍历二叉树
void PostOrder(BiTree root) {
if(NULL!=root) {
PostOrder(root->LChild);
PostOrder(root->RChild);
Visit(root->data);
}
}
遍历算法应用
输出二叉树中的结点
(1) 前序遍历输出结点
void PreOrder(BiTree root) {
if(root!=NULL) {
Visit(root->data); /* 先序访问 */
PreOrder(root->LChild);
PreOrder(root->RChild);
}
}
void PreOrder(BiTree root) {
if(root!=NULL) {
Printf(root->data); /* 先序输出*/
PreOrder(root->LChild);
PreOrder(root->RChild);
}
}
(2) 中序遍历输出结点
void InOrder(BiTree root) {
if(root!=NULL) {
InOrder(root->LChild);
Visit(root->data); /* 中序访问 */
InOrder(root->RChild);
}
}
void InOrder(BiTree root) {
if(root!=NULL) {
InOrder(root->LChild);
Printf(root->data); /* 输出结点 */
InOrder(root->RChild);
}
}
(3) 后序遍历输出结点
void PostOrder(BiTree root) {
if(root!=NULL) {
PostOrder(root->LChild);
PostOrder(root->RChild);
Visit(root->data); /* 中序访问 */
}
}
void PostOrder(BiTree root) {
if(root!=NULL) {
PostOrder(root->LChild);
PostOrder(root->RChild);
Printf(root->data); /* 输出结点 */
}
}
输出二叉树中的叶子结点
(1) 前序遍历输出叶子结点
void PreOrder(BiTree root) {
if(root!=NULL) {
Visit(root->data); /* 先序访问 */
PreOrder(root->LChild);
PreOrder(root->RChild);
}
}
(2) 中序遍历输出叶子结点
void InOrder(BiTree root) {
if(root!=NULL) {
InOrder(root->LChild);
Visit(root->data); /* 中序访问 */
InOrder(root->RChild);
}
}
(3) 后序遍历输出叶子结点
void PostOrder(BiTree root) {
if(root!=NULL) {
PostOrder(root->LChild);
PostOrder(root->RChild);
Visit(root->data); /* 中序访问 */
}
}
统计二叉树中的叶子结点数目
(1) 前序统计叶子结点数目
void PreOrder(BiTree root) {
if(root!=NULL) {
Visit(root->data); /* 先序访问 */
PreOrder(root->LChild);
PreOrder(root->RChild);
}
}
(2) 中序统计叶子结点数目
void InOrder(BiTree root) {
if(root!=NULL) {
InOrder(root->LChild);
Visit(root->data); /* 中序访问 */
InOrder(root->RChild);
}
}
(3) 后序统计叶子结点数目
void PostOrder(BiTree root) {
if(root!=NULL) {
PostOrder(root->LChild);
PostOrder(root->RChild);
Visit(root->data); /* 中序访问 */
}
}
(4) 递归算法统计叶子结点数目
空树:0 仅有根结点的树:1
其他:左子树叶子数+右子树叶子数
int Leaf(BiTree root) {
if(root==NULL)
return 0;
else if(root->LChild==NULL&&root->RChild==NULL)
return 1;
else
return Leaf (root->LChild)+Leaf(root->RChild);
}
创建二叉树
(1) 扩展的遍历序列
在遍历序列中,不忽略空子树,而用特定的元素表示空子树。
先序遍历序列:
ABCD
扩展先序遍历序列:
AB…CD…
(2) 用扩展的先序遍历序列创建二叉树
void CreateBiTree(BiTree *bt) {
char ch;
ch=getchar();
if(ch=='.') /* 创建空树 */
*bt=NULL;
else { /* 创建树 */
*bt=(BiTree)malloc(sizeof(BiTNode));
(*bt)->data=ch;
CreateBiTree(&((*bt)->LChild));
CreateBiTree(&((*bt)->RChild));
}
}
深度
(1) 后序遍历求深度
空树:0
非空树:max(L的深度,R的深度)+1
int PostTreeDepth(BiTree bt) {
int hl,hr,max;
if(bt!=NULL) {
hl=PostTreeDepth(bt->LChild); /* 左子树深度 */
hr=PostTreeDepth(bt->RChild); /* 右子树深度 */
max=hl>hr?hl:hr;
return max+1; /* 树深度 */
} else
return 0; /* 空树深度 */
}
(2) 前序遍历求深度
深度=结点的最大层次
孩子结点层次:双亲层次+1
int depth=0; /* 初始深度为0 */
void PreTreeDepth(BiTree bt,int h) { /* 层次h初值为1 */
if(bt!=NULL) {
if(h>depth) depth=h; /* 前序新深度 */
PreTreeDepth(bt->LChild,h+1);
PreTreeDepth(bt->RChild,h+1);
}
}
二叉树的横向打印
(1) 输出结果:是竖向树的90度旋转
逆中序遍历RDL:CFEADB。
每行输出一个结点。
结点在行中的位置与所在的层深有关。
void PrintTree(BiTree bt,int nLayer) { /* 层号决定空格数 */
int i;
if(bt!=NULL) {
PrintTree(bt->RChild,nLayer+1); /* 先右 */
Visit(bt->data);
PrintTree(bt->LChild,nLayer+1); /* 后左 */
}
}
基于栈的递归消除
递归消除的基本思想:
在遍历过程中,用栈来记录未访问的结点的指针。
中序遍历的非递归算法
[算法思想1] 从根结点开始,只要当前结点存在,或者栈不空,则重复下面操作:
① 从当前结点开始,进栈并遍历左子树,直到左子树为空;(循环)
② 退栈并访问;
③ 遍历右子树。
[算法思想2] 从根结点开始,只要当前结点存在,或者栈不空,则重复下面操作:
① 如果当前结点存在,则进栈并遍历左子树;
② 否则退栈并访问,然后遍历右子树。
(1) 直接实现栈操作
定义存储结点地址的顺序栈空间
#define m 50
BiTree s[m+1];
直接按下标操作栈
(2) 调用栈操作的函数
IniStatic(&S)
IsEmpty(S)
Push(&S,p)
Pop(&S,&p)
后序遍历的非递归算法
[算法思想] 从根结点开始,只要当前结点存在,或者栈不空,则重复下面操作:
① 从当前结点开始,进栈并遍历左子树,直到左子树为空;(循环)
② 如果栈顶结点的右子树为空,或者栈顶结点的右孩子为刚访问过的结点,则退栈并访问;
③ 否则,遍历右子树。
前序遍历的非递归算法
[算法思想] 从根结点开始,只要当前结点存在,或者栈不空,则重复下面操作:
① 如果当前结点存在,则访问结点、右子树入栈、遍历左子树;
② 否则退栈遍历右子树。
线索二叉树
问题:n个结点的二叉树有多少个空链域?
基本概念
(1) 概念
线索:充分利用二叉树的空链域,用于记录遍历结果的前驱或后继信息。
线索化:就是加线索的过程。相应的二叉树称为线索二叉树。
(2) 线索二叉树结点的结构
Ltag=0,LChild指向左孩子
Ltag=1,LChild指向前驱结点
Rtag=0,RChild指向右孩子
Rtag=1,RChild指向后继结点
(3) 线索二叉树结点的定义
typedef struct Node {
DataType data;
int Ltag;
int Rtag;
struct Node *LChild;
struct Node *RChild;
} BiTNode,*BiTree;
举例:中序线索二叉树的存储结构
中序遍历:DGBAEHCF
特殊结点的处理:加头结点
叶子结点有何特点?
二叉树的线索化
首先,创建二叉链表
void CreateBiTree(BiTree *bt) {
char ch;
ch=getchar();
if(ch=='.')
*bt=NULL;
else {
*bt=(BiTree)malloc(sizeof(BiTNode));
(*bt)->data=ch;
(*bt)->Ltag=0; (*bt)->Rtag=0; /* 线索先都设为0 */
CreateBiTree(&((*bt)->LChild));
CreateBiTree(&((*bt)->RChild));
}
}
其次,中序加线索
BiTree pre=NULL; /* 初始前驱为空 */
void Inthread(BiTree root) {
if(root!=NULL) {
Inthread(root->LChild); /* 遍历左子树 */
if(root->LChild==NULL) { /* 置前驱线索 */
root->Ltag=1; root->LChild=pre;
}
if(pre!=NULL&&pre->RChild==NULL) {/* 置后继线索 */
pre->Rtag=1; pre->RChild=root;
}
pre=root; /* 记录下次遍历前驱结点 */
Inthread(root->RChild); /* 遍历右子树 */
}
}
在线索二叉树中找前驱、后继结点
(1) 结点的前驱结点
线索记录的前驱结点
或左子树“最右下端”的结点
BiTNode *InPre(BiTNode *p) {
BiTNode *q,*pre;
if(p->Ltag==1)
pre=p->LChild;
else {
for(q=p->LChild; q->Rtag==0; q=q->RChild)
;
pre=q;
}
return pre;
}
(2) 结点的后继结点
线索记录的后继结点
或右子树“最左下端”的结点
注:中序最后一个结点没有右子树。
BiTNode *InNext(BiTNode *p) {
BiTNode *q,*Next;
if(p->Rtag==1) /* 利用线索的后继 */
Next=p->RChild;
else if(p->RChild==NULL) /* p是末结点 */
Next=NULL;
else { /* 右子树“最左下端”结点 */
for(q=p->RChild; q->Ltag==0; q=q->LChild) ;
Next=q;
}
return Next;
}
遍历中序线索二叉树
(1) 先找到中序遍历的第一个结点
遍历左子树,直到没有左孩子的结点
BiTNode * InFirst(BiTree root) {
BiTNode *p=root;
if(!p) return NULL;
while(p->Ltag==0) p=p->LChild;
return p;
}
(2) 依次遍历下一个结点,直至结点为空
void TInOrder(BiTree root) {
BiTNode *p;
p=InFirst(root);
while(p) {
Visit(p->data);
p=InNext(p);
}
}
中序线索二叉树插入右结点
(1) 结点的右孩子为空时的插入
(2) 末结点
(3) 结点的右孩子为非空时的插入
由遍历序列确定二叉树
已知中序遍历:B D C E A F H G
已知后序遍历:D E C B H G F A
欢迎大家加我微信交流讨论(请备注csdn上添加)