# 二叉树的遍历
遍历:按照某种次序把所有结点都访问一遍
层次遍历:基于树的层次特性确定的次序规则
二叉树的递归特性:
- 要么是个空二叉树
- 要么就是由 "根节点 + 左子树 + 右子树" 组成的二叉树
# 先序遍历
先序遍历:根左右
先序遍历 -> 前缀表达式
先序遍历 —— 第一次路过时访问的节点
先序遍历操作过程如下:
- 若二叉树为空,则什么也不做
- 若二叉树非空
- 访问根节点
- 先序遍历左子树
- 先序遍历右子树
typedef struct BiTNode{
ElemType data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
//先序遍历
void PreOrder(BiTree T){
if(T!=NULL){
visit(T); //访问根节点
PreOrder(T->lchild); //递归访问左子树
PreOrder(T->rchild); //递归访问右子树
}
}
# 中序遍历
中序遍历:左根右
中序遍历 -> 中缀表达式 (需要加界限符)
中序遍历 —— 第二次路过时访问的节点
中序遍历操作过程如下:
- 若二叉树为空,则什么也不做
- 若二叉树非空
- 中序遍历左子树
- 访问根节点
- 中序遍历右子树
typedef struct BiTNode{
ElemType data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
//中序遍历
void InOrder(BiTree T){
if(T!=NULL){
InOrder(T->lchild); //递归访问左子树
visit(T); //访问根节点
InOrder(T->rchild); //递归访问右子树
}
}
# 后续遍历
后续遍历:左右根
后序遍历 -> 后缀表达式
后序遍历 —— 第三次路过时访问的节点
后序遍历操作过程如下:
- 若二叉树为空,则什么也不做
- 若二叉树非空
- 后序遍历左子树
- 后序遍历右子树
- 访问根节点
typedef struct BiTNode{
ElemType data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
//后序遍历
void PostOrder(BiTree T){
if(T!=NULL){
PostOrder(T->lchild); //递归访问左子树
PostOrder(T->rchild); //递归访问右子树
visit(T); //访问根节点
}
}
# 二叉树的层次遍历
算法思想:
- 初始化一个辅助队列
- 根节点入队
- 若队列非空,则队头结点出队,访问该结点,并将其左、右孩子插入队尾 (如果有的话)
void LevelOrder(BiTree T){
LinkQueue Q; //初始化辅助队列
InitQueue(Q);
BiTree p;
EnQueue(Q,T); //根节点入队
while(!IsEmpty(Q)){ //队不空则循环
DeQueue(Q,p); //队头节点出队
visit(p); //访问出队节点
if(p->lchild!=NULL)
EnQueue(Q,p->lchild); //左孩子入队
if(p->rchild!=NULL)
EnQueue(Q,p->rchild); //右孩子入队
}
}
# 由遍历序列构造二叉树
# 不同二叉树的中序遍历序列
结论:若只给出一颗二叉树的前 / 中 / 后 / 层序遍历序列中的一种,不能唯一确定一颗二叉树
中序遍历序列:左子树的中序遍历序列 + 根节点 + 右子树的前序遍历序列
# 前序 + 中序遍历序列
前序遍历序列:根节点 + 左子树的前序遍历序列 + 右子树的前序遍历序列
# 后续 + 中序遍历序列
后续遍历序列:左子树的后续遍历序列 + 右子树的后续遍历序列 + 根节点
# 层序 + 中序遍历序列
层序遍历序列:根节点 + 左子树的根 + 右子树的根
Key:找到树的根节点,并根据中序序列分为左右子树,再找到左右子树的根节点
结论:前序、后续、层序序列的两两组合无法唯一确定一颗二叉树
# 线索二叉树
# 线索二叉树的作用
思路:
从根节点出发,重新进行一次中序遍历,指针 q 记录当前访问的结点,指针 pre 记录上一个被访问的节点
如何找到指定节点 p 在中序遍历序列中的前驱
- 当 p==q 时,pre 为前驱
如何找到 p 的中序后继
- 当 pre==p 时,q 为后继
缺点:找前驱、后继不是很方便:遍历操作必须从根开始
中序线索二叉树:
n 个结点的二叉树来说,有 n+1 个空链域,可用来记录前驱、后继的信息
指向前驱、后继的指针称为 "线索"
# 线索二叉树的存储结构
//二叉树的结点(链式存储)
typedef struct BiTNode{
ElemType data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
//线索二叉树结点
typedef struct ThreadNode{
ElemType data;
struct ThreadNode *lchild,*rchild;
int ltag,rtag; //左、右线索标志
//tag==0,表示指针指向孩子
//tag==1,表示指针是线索
}ThreadNode,*ThreadTree;
# 三种线索二叉树
先序线索二叉树:按照先序遍历进行线索化
中序线索二叉树:按照中序遍历进行线索化
后序线索二叉树:按照后序遍历进行线索化
先序线索二叉树 —— 线索指向先序前驱、先序后继
中序线索二叉树 —— 线索指向中序前驱、中序后继
后序线索二叉树 —— 线索指向后序前驱、后序后继
# 二叉树的线索化
土办法找中序前驱:
//中序遍历
void InOrder(BiTree T){
if(T!=NULL){
InOrder(T->lchild); //递归遍历左子树
visit(T); //访问根节点
InOrder(T->rchild); //递归遍历右子树
}
}
//访问节点q
void visit(BiTNode *q){
if(q==p) //当前访问节点刚好是p
final=pre; //找到p的前驱
else
pre=q; //pre指向当前访问节点
}
//辅助全局变量,用于查找结点p的前驱
BiTNode *p; //p指向目标节点
BiTNode *pre=NULL; //指向当前访问节点的前驱
BiTNode *final=NULL; //用于记录最终结果
中序线索化:
//全局变量pre,指向当前访问结点的前驱
ThreadNode *pre=NULL;
//中序线索化二叉树
void CreateInThread(ThreadTree T){
pre=NULL; //pre初始化为NULL
if(T!=NULL){ //非空二叉树才能线索化
InThread(T); //中序线索化二叉树
if(pre->rchild==NULL)
pre->rtag=1; //处理遍历的最后一个节点
}
}
//线索二叉树结点
typedef struct ThreadNode{
ElemType data;
struct ThreadNode *lchild,*rchild;
int ltag,rtag; //左、右线索标志
//tag==0,表示指针指向孩子
//tag==1,表示指针是线索
}ThreadNode,*ThreadTree;
void InThread(ThreadTree T){
if(T!=NULL){
InThread(T->lchild); //中序遍历左子树
visit(T); //访问根节点
InThread(T->rchild); //中序遍历右子树
}
}
void visit(ThreadNode *q){
if(q->lchild==NULL){ //左子树为空
q->lchild=pre;
q->ltag=1;
}
if(pre!=NULL && pre->rchild==NULL){
pre->rchild=q; //建立前驱节点的后继线索
pre->rtag=1;
}
pre=q;
}
最后要检查 pre 的 rchild 是否为 NULL,如果是,则令 rtag=1
先序线索化:
//全局变量pre,指向当前访问结点的前驱
ThreadNode *pre=NULL;
void CreatePreThread(ThreadTree T){
pre=NULL; //pre初始化为NULL
if(T!=NULL){ //非空二叉树才能线索化
PreThread(T); //先序线索化二叉树
if(pre->rchild==NULL)
pre->rtag=1; //处理遍历的最后一个节点
}
}
void PreThread(ThreadTree T){
if(T!=NULL){
visit(T); //先处理根节点
if(T->ltag==0)
PreThread(T->lchild);
PreThread(T->rchild);
}
}
void visit(ThreadNode *q){
if(q->lchild==NULL){ //左子树为空,建立前驱线索
q->lchild=pre;
q->ltag=1;
}
if(pre!=NULL && pre->rchild==NULL){
pre->rchild=q; //建立前驱节点的后继线索
pre->rtag=1;
}
pre=q;
}
后续线索化:
//全局变量pre,指向当前访问结点的前驱
ThreadNode *pre=NULL;
void CreatePostThread(ThreadTree T){
pre=NULL; //pre初始化为NULL
if(T!=NULL){ //非空二叉树才能线索化
PreThread(T); //中序线索化二叉树
if(pre->rchild==NULL)
pre->rtag=1; //处理遍历的最后一个节点
}
}
void PostThread(ThreadTree T){
if(T!=NULL){
PostThread(T->lchild);
PostThread(T->rchild);
visit(T);
}
}
void visit(ThreadNode *q){
if(q->lchild==NULL){ //左子树为空,建立前驱线索
q->lchild=pre;
q->ltag=1;
}
if(pre!=NULL && pre->rchild==NULL){
pre->rchild=q; //建立前驱节点的后继线索
pre->rtag=1;
}
pre=q;
}
# 在线索二叉树中找前驱后继
中序二叉树找中序后继:
寻找指定节点 * p 的后继
若 p->rtag==1,则 next=p->rchild
若 p->rtag==0,则 next=p 的右子树最左下节点
寻找指定节点 * p 的前驱
- 若 p->ltag==1,则 pre=p->lchild
- 若 p->ltag==0,则 pre=p 的左子树最右下节点
//找到以p为根的子树中,第一个被中序遍历的节点
ThreadNode *FirstNode(ThreadNode *p){
//循环找最左下节点(不一定是叶节点)
while(p->ltag==0)
p=p->lchild;
return p;
}
//找到以p为根的子树中,最后一个中序遍历的节点
ThreadNode *LastNode(ThreadNode *p){
//循环找最右下节点(不一定是叶节点)
while(p->rtag==0)
p=p->rchild;
return p;
}
//在中序线索二叉树中找到结点p的后继节点
ThreadNode *NextNode(ThreadNode *p){
//右子树最左下节点
if(p->rtag==0)
return FirstNode(p->rchild);
else //rtag==1
return p->rchild;
}
//在中序线索二叉树中找到结点p的前驱节点
ThreadNode *PreNode(ThreadNode *p){
//右子树最左下节点
if(p->ltag==0)
return LastNode(p->lchild);
else //rtag==1
return p->rchild;
}
//对中序线索二叉树进行中序遍历(利用线索实现非递归算法)
void InOrder(ThreadNode *T){
for(ThreadNode *p=FirstNode(T);p!=NULL;p=NextNode(p))
visit(p);
}
//对中序线索二叉树进行逆向中序遍历(利用线索实现非递归算法)
void RevOrder(ThreadNode *T){
for(ThreadNode *p=LastNode(T);p!=NULL;p=PreNode(p))
visit(p);
}
先序线索二叉树找先序前驱:
寻找指定节点 * p 的前驱
- 若 p->ltag==1,则 next=p->lchild
- 若 p->ltag==0
- 如果能找到 p 的父节点,且 p 是左孩子,p 的父节点为其前驱
- 如果能找到 p 的父节点,且 p 是右孩子,其左兄弟为空,p 的父节点为其前驱
- 如果能找到 p 的父节点,且 p 是右孩子,其左兄弟非空,p 的前驱为左兄弟子树最后一个被先序遍历的节点
- 如果 p 是根节点,则 p 没有前驱
后续线索二叉树找后续前驱:
寻找指定节点 * p 的前驱
- 若 p->ltag==1,则 pre=p->lchild
- 若 p->ltag==0
- 如果能找到 p 的父节点,且 p 是右孩子,p 的父节点为其前驱
- 如果能找到 p 的父节点,且 p 是左孩子,其右兄弟为空,p 的父节点为其前驱
- 如果能找到 p 的父节点,且 p 是左孩子,其右兄弟非空,p 的前驱为右兄弟子树第一个被后序遍历的节点
- 如果 p 是根节点,则 p 没有后续后继