遍历二叉树(Traversing Binary Tree)

二叉树的遍历是指从根结点出发,按照某种次序访问二叉树中的所有结点,使得每个结点被访问一次且仅被访问一次。

实际应用中,查找树中符合条件的结点,或对结点进行的各种处理,这里简化为输出结点的数据。 线性结构:容易解决。

二叉树遍历操作的结果----非线性结构线性化

考虑二叉树的组成:

如果限定先左后右,则二叉树遍历方式有三种: 前序(根):DLR; 中序(根):LDR ;后序(根):LRD

注:“先、中、后”的意思是指访问的结点D是先于子树出现还是后于子树出现。

前序(根)遍历

若二叉树为空,则空操作返回;否则: ①访问根结点; ②前序遍历根结点的左子树; ③前序遍历根结点的右子树。

前序遍历序列:A B D G C E F

中序(根)遍历

若二叉树为空,则空操作返回;否则: ①中序遍历根结点的左子树; ②访问根结点; ③中序遍历根结点的右子树。

中序遍历序列:D G B A E C F

后序(根)遍历

若二叉树为空,则空操作返回;否则: ①后序遍历根结点的左子树; ②后序遍历根结点的右子树。 ③访问根结点;

后序遍历序列:G D B E F C A

遍历结果 “恢复”二叉树

                             

                              

已知一棵二叉树的前序序列和中序序列,构造该二叉树的过程如下:

1. 根据前序序列的第一个元素建立根结点;

2. 在中序序列中找到该元素,确定根结点的左右子树的中序序列;

3. 在前序序列中确定左右子树的前序序列;

4. 由左子树的前序序列和中序序列建立左子树;

5. 由右子树的前序序列和中序序列建立右子树。

已知一棵二叉树的后序序列和中序序列,分别是DECBHGFA 和BDCEAFHG 是否可以唯一确定这棵树?

①由后序遍历特征,根结点必在后序序列尾部(即A);

②由中序遍历特征,根结点必在其中间,而且其左部必全部是左子树子孙(即BDCE),其右部必全部是右子树子孙(即FHG);

③继而,根据后序中的DECB子树可确定B为A的左孩子,根据HGF子串可确定F为A的右孩子;以此类推。

遍历的算法实现:递归和非递归实现。 用递归形式格外简单!

二叉树结点的数据类型定义

typedef struct Tnode *BiTree;
typedef struct Tnode {int data;BiTree  left_child, right_child;
} Tnode;
//结点数据类型自定义
typedef struct Tnode{int data; struct Tnode   *lchild,*rchild;
} Tnode, *BiTree;
//先序遍历算法
DLR(Tnode *root ){if (root){ //非空二叉树printf(“%d”,root->data); //访问DDLR(root->lchild); //递归遍历左子树DLR(root->rchild); //递归遍历右子树} return(0);
}
//中序遍历算法
LDR(Tnode *root){if(root){LDR(root->lchild);printf(“%d”,root->data);LDR(root->rchild); } return(0);
}
//后序遍历算法
LRD (Tnode *root){if(root){ LRD(root->lchild);LRD(root->rchild);printf(“%d”,root->data); } return(0);
}

对遍历的分析

从前面的三种遍历算法可以知道:如果将printf语句抹去,从递归的角度看,这三种算法是完全相同的,或者说这三种遍历算法的访问路径是相同的,只是访问结点的时机不同。

从虚线的出发点到终点的路径 上,每个结点经过3次。

第1次经过时访问=先序遍历

第2次经过时访问=中序遍历

第3次经过时访问=后序遍历

二叉树遍历的时间效率和空间效率

时间效率:O(n) //每个结点只访问一次

空间效率:O(n) //栈占用的最大辅助空间

递归遍历的应用1:计算二叉树中叶子结点的数目

思路:输出叶子结点比较简单,用任何一种遍历算法,凡是左右指针均空者,则为叶子,将其统计并打印出来。

DLR(Tnode *root)     //采用先序遍历的递归算法
{ if ( root!=NULL ) //非空二叉树条件,还可写成if(root){      if(!root->lchild&&!root->rchild)  //是叶子结点{sum++; printf("%d\n",root->data);}DLR(root->lchild); //递归遍历左子树,直到叶子处;DLR(root->rchild); }//递归遍历右子树,直到叶子处;} return(0);
}

递归遍历的应用2:二叉树的建立

遍历是二叉树各种操作的基础,可以在遍历的过程中进行各种操作,例如建立一棵二叉树。

为了建立一棵二叉树,将二叉树中每个结点的空指针引出一个虚结点,其值为一特定值如“#”,以标识其为空,把这样处理后的二叉树称为原二叉树的扩展二叉树。

                       

扩展二叉树的前序遍历序列:A B # D # # C # #

设二叉树中的结点均为一个字符。假设扩展二叉树的前序遍历序列由键盘输入,root为指向根结点的指针,二叉链表的建立过程是: 首先输入根结点,若输入的是一个“#”字符,则表明该二叉树为空树,即root=NULL;否则输入的字符应该赋给root->data,,之后依次递归建立它的左子树和右子树 。

思路:利用前序遍历来建树(结点值陆续从键盘输入)

Status createBTpre(BiTree T ){ scanf(“%c”,&ch);if(ch==’# ’)T=NULL;
else{T=( Bintree )malloc(sizeof(Tnode));T->data=ch;createBTpre(T->lchild);createBTpre(T->rchild); }return OK;
}

递归遍历的应用3:求二叉树深度

求二叉树深度,或从x结点开始的子树深度。

算法思路:只查各结点后继链表指针,若左(右) 孩子的左(右)指针非空,则层次数加1;否则函数返回。

当T= NULL时,深度为0; 否则, T的深度= MAX{左子树深度,右子树深度}+1;

int  Depth(BiTree T)
{if (T= =NULL) return 0;else {hl= Depth(T->lchild);hr= Depth(T ->rchild);return max(hl, hr)+1;}
}

1. 求二叉树结点数。

int  num(BiTree T)
{if (T= =NULL) return 0;else {left= num (T->lchild);right= num(T ->rchild);return left+right+1;}
}

2. 按层次输出二叉树中所有结点。

算法思路:既然要求从上到下,从左到右,则利用队列存放各子树结点的指针是个好办法,而不必拘泥于递归算法。

技巧:初始时把根节点入队,当根结点出队后,令其左、右孩子结点入队(空不入队),而左孩子出队时又令它的左右孩子结点入队,……由此便可产生按层次输出的效果。

遍历序列:A B C D E F G

3.判别给定二叉树是否为完全二叉树(即顺序二叉树)。

算法思路:完全二叉树的特点是:没有左子树空而右子树单独存在的情况(前k-1层都是满的,且第k层左边也满)。

技巧:按层序遍历方式,先把所有结点(不管当前结点是否有左右孩子)都入队列.若为完全二叉树,则层序遍历时得到的肯定是一个连续的不包含空指针的序列.如果序列中出现了空指针,则说明不是完全二叉树。

非递归算法——前序遍历

二叉树前序遍历的非递归算法的关键:在前序遍历过某结点的整个左子树后,如何找到该结点的右子树的根指针。

解决办法:在访问完该结点后,将该结点的指针保存在栈中,以便以后能通过它找到该结点的右子树。

在前序遍历中,设要遍历二叉树的根指针为p,则有两种可能:

⑴ 若p!=NULL,Visit(p->data);Push(S,p);    p= p->lchild

⑵ 若p=NULL,Pop( S,p);    p = p->rchild;

前序遍历——非递归算法(伪代码)

1.栈s初始化;

2.循环直到p为空且栈s为空

2.1 当p不空时循环 2.1.1 输出p->data;

2.1.2 将指针p的值保存到栈中;

2.1.3 继续遍历p的左子树

2.2 当p为空,栈s不空,则

2.2.1 将栈顶元素弹出至p;

2.2.2 准备遍历p的右子树;

Status preTraverse(BiTree T,Status( *Visit)(TElemType  e))
{  //前序遍历的非递归算法InitStack( S ); p=T;while ( p || !StackEmpty(S) ) {// 栈不空或树不空if ( p ) {if ( !Visit( p->data) )   return ERROR;访问根结点,Push(S,p);    p= p->lchild; }  // 根指针进栈,遍历左子树else  {    // 左子树访问完了,根指针退栈, 遍历右子树Pop( S,p); p = p->rchild;}  // else}   // whilereturn   OK;}  //  InorderTraverse

用二叉链表法(l_child, r_child)存储包含n个结点的二叉树,结点的指针区域中会有多少个空指针?

分析:用二叉链表存储包含n个结点的二叉树,结点必有2n个链域(见二叉链表数据类型说明)。     除根结点外,二叉树中每一个结点有且仅有一个双亲(直接前驱),所以只会有n-1个结点的链域存放指针,指向非空子女结点(即直接后继)。所以, 空指针数目=2n-(n-1)=n+1个。

二叉链表空间效率这么低,能否利用这些空闲区存放有用的信息或线索? ——我们可以用它来存放当前结点的直接前驱和后继等线索,以加快查找速度。 ——线索二叉树

线索二叉树(Threaded Binary Tree)

1. 定义

普通二叉树只能找到结点的左右孩子信息,而该结点的直接前驱和直接后继只能在遍历过程中获得。

若将遍历后对应的有关前驱和后继预存起来,则从第一个结点开始就能很快“顺藤摸瓜”而遍历整个树了。

例如中序遍历结果:B D C E A F H G,实际上已将二叉树转为线性排列,显然具有唯一前驱和唯一后继。

预存信息两种方法:增加两个域:fwd(存放前驱指针)和bwd(存放后继指针);利用空链域(n+1个空链域)

规定:

1)若结点有左子树,则lchild指向其左孩子;        否则, lchild指向其直接前驱(即线索);

2)若结点有右子树,则rchild指向其右孩子;       否则, rchild指向其直接后继(即线索) 。

为区别两种不同情况,特增加两个标志域(各1bit)

约定:当Tag域为0时,表示正常情况;当Tag域为1时,表示线索情况.

附:有关线索二叉树的几个术语:

线索链表:用含Tag的结点样式所构成的二叉链表。

线   索:指向结点前驱和后继的指针。

线索二叉树:加上线索的二叉树。

线    索   化:对二叉树以某种次序遍历使其变为线索二叉树的过程。

增加了前驱和后继等线索能方便找出当前结点的前驱和后继,不用堆栈也能遍历整个树。

2. 线索二叉树的生成

线索化过程就是在遍历过程中修改空指针的过程:

将空的lchild改为结点的直接前驱;

将空的rchild改为结点的直接后继。

非空指针仍然指向孩子结点(称为“正常情况”)

线索二叉树的生成算法

目的:在依某种顺序遍历二叉树时修改空指针,添加前驱或后继。

注解:为方便添加结点的前驱或后继,需要设置两个指针:p指针→当前结点之指针;  pre指针→前驱结点之指针。

技巧:当结点p的左/右域为空时,只改写它的左域(装入前驱pre),而其右域(后继)留给下一结点来填写。 或者说,当前结点的指针p应当送到前驱结点的空右域中。

若p->lchild=NULL,则{p->Ltag=1;p->lchild=pre;}                //p的前驱结点指针pre存入左空域

若pre->rchild=NULL, 则{pre->Rtag=1;pre->rchild=p;}       //p存入其前驱结点pre的右空域

Status InorderThreading(BiThrTree  & Thrt, BiThrTree  T)
{ //中序遍历二叉树T,并将其中序线索化, Thrt 指向头结点.if ( ! (Thrt = (BiThrTree) malloc ( sizeof (BiThrnode) ) )exit ( OVERFLOW ) ;Thrt ->LTag = Link;   Thrt ->RTag = Thead;   // 建头结点Thrt ->rchild = Thrt ;                                       //右指针回指if ( !T ) Thrt ->lchild = Thrt ;    // 若二叉树空,则左指针回指else {Thrt ->lchild = T;     pre = Thrt; //将头结点与树相连InThreading(T);          // 中序遍历进行中序线索化pre ->rchild = Thrt;   pre ->RTag = Thread;     //最后一个结点线索化Thrt ->rchild = pre;}return OK;} // InOrderThreading
void InThreading (BiThrTree p){ if (p){   InThreading( p->lchild );  // 左子树线索化if ( !p->lchild ) { p->LTag=Thread; p->lchild=pre; } // 前驱线索if ( !pre->rchild ){ pre->RTag=Thread; pre->rchild=p; } //后继线索pre = p;                         // 保持pre指向p的前驱InThreading(p->rchild);      //右子树线索化}} // InThreading

3. 线索二叉树的遍历

理论上,只要找到序列中的第一个结点,然后依次访问结点的后继直到后继为空时结束。但是,在线索化二叉树中,并不是每个结点都能直接找到其后继的,当标志为0时,R_child=右孩子地址指针,并非后继!需要通过一定运算才能找到它的后继。

以中序线索二叉树为例: 对叶子结点(RTag=1),直接后继指针就在其rchild域内; 对其他结点(RTag=0),直接后继是其右子树最左下的结点;

(因为中序遍历规则是LDR,先左再根再右 该结点访问完后,下一个要访问的是其右子树 上第一个要访问的结点)

线索二叉树的中序遍历算法

//程序注解  (非递归,且不用栈):
P=T->lchild;   //从头结点进入到根结点;
while( p!=T)
{  while(p->LTag==link)p=p->lchild;  //先找到中序遍历起点if(!visit(p->data)) return ERROR;  //若起点值为空则出错告警while(p->RTag==Thread ……){ p=p->rchild; Visit(p->data);}//若有后继标志,则直接提取p->rchild中线索并访问后继结点;p=p->rchild;  //当前结点右域不空或已经找好了后继,则一律从结点的右子树开始重复{  }的全部过程。
}
Return OK;

算法流程

代码实现:二叉树的应用https://blog.csdn.net/qq1457346557/article/details/107122992

数据结构入门----遍历二叉树和线索二叉树相关推荐

  1. 8.遍历二叉树、线索二叉树、森林

    思考 一.什么遍历二叉树.线索二叉树.森林?(What) 0.二叉树遍历一共(4种) 1.二叉树先序遍历 2.二叉树中序遍历 3.二叉树后序遍历 4.遍历分析 5.遍历二叉树 6.二叉树层次遍历 7. ...

  2. 重拾算法(3)——用458329个测试用例全面测试二叉树和线索二叉树的遍历算法

    重拾算法(3)--用458329个测试用例全面测试二叉树和线索二叉树的遍历算法 在"上一篇"和"上上一篇"中,我给出了二叉树和线索二叉树的遍历算法.给出算法容易 ...

  3. 二叉树的遍历 中序线索二叉树

    文章目录 前言 一.中序遍历的特点:投影 二.中序线索二叉树 三.代码思路 三.代码 前言 在N个节点的二叉树中,每个节点有2个指针,所以一共有2N个指针,除了根节点以外,每一个节点都有一个指针从它的 ...

  4. C语言实现二叉树的中序线索化及遍历中序线索二叉树

    C语言实现二叉树的线索化以及如何遍历线索二叉树! 文章目录 线索二叉树的结构及数据类型定义 根据输入结点初始化二叉树 中序遍历二叉树并线索化 遍历中序线索二叉树 项目完整代码 项目完整代码(改进版) ...

  5. 深入学习二叉树(二) 线索二叉树

    深入学习二叉树(二) 线索二叉树 1 前言 在上一篇简单二叉树的学习中,初步介绍了二叉树的一些基础知识,本篇文章将重点介绍二叉树的一种变形--线索二叉树. 2 线索二叉树 2.1 产生背景 现有一棵结 ...

  6. 线索二叉树,画图教你秒懂线索二叉树(线索二叉树的建立和简单操作)逻辑代码分析

    数据结构专升本学习,线索二叉树 前言 前面我们学习树和二叉树的一些基本操作,今天我们学习一个新的知识,学习一下线索二叉树,线索二叉树是由二叉链存储结构变化而来的(我们先得有个二叉链树,再做处理),就是 ...

  7. 【Java数据结构与算法】第十一章 顺序存储二叉树、线索二叉树和堆

    第十一章 顺序存储二叉树.线索化二叉树.大顶堆.小顶堆和堆排序 文章目录 第十一章 顺序存储二叉树.线索化二叉树.大顶堆.小顶堆和堆排序 一.顺序存储二叉树 1.介绍 2.代码实现 二.线索二叉树 1 ...

  8. c语言线索二叉树作用,线索二叉树(C语言)

    实现下面这棵树: 先序遍历: A B C D E F 中序遍历: C B D A E F 代码 #include #include #include #include typedef enum {li ...

  9. 【数据结构-树】2.二叉树遍历与线索二叉树(图解+代码)

    一.二叉树的定义及其主要特征 1.1 二叉树的概念 二叉树是另一种树形结构,其特点是每个结点最多含两棵子树(也就是说,二叉树的度≤2). 二叉树是一种有序树,若将其左.右子树颠倒,则成为另一颗不同的二 ...

最新文章

  1. HDU 2830 Matrix Swapping II
  2. 简单介绍Kubernetes
  3. axios post传递对象_axios的post传参时,将参数转为form表单格式
  4. 去杠杆高歌猛进,借呗会倒闭吗?
  5. 安装12G内存读出内存条为3.45G的处理方法
  6. VS.NET版本与VC版本对应关系
  7. JerseyTest
  8. android Toast五种特效
  9. Android菜鸟的成长笔记(23)——获取网络和SIM卡信息
  10. Cannot load module file xxx.iml Intellij
  11. 谷歌浏览器离线更换皮肤-安装谷歌浏览器插件与问题解决
  12. linux驱动开发:mma7660 sensor的配置
  13. Briss-最好用的pdf裁边工具
  14. 自动化测试练习项目环境搭建
  15. 深度图+灰度图 可视化判断灰度图区域是否有深度
  16. CentOS关闭火狐浏览器Flash过期提示
  17. JavaScript入门指南(翻译自 The JS Handbook)
  18. 高效查询快递物流信息
  19. 记录-Selection.addRange() 已弃用,该如何解决
  20. ZigBee的无线通信与网络组建

热门文章

  1. matlab三维图像分割,Matlab 沿三维任意方向切割CT图的仿真计算
  2. 服务器运维用macos,MacOS和Linux区别_网站服务器运行维护,linux,macos
  3. 系统集成项目管理工程师笔记_备考常见英文词汇汇总
  4. Linux命令使用笔记
  5. 朝花夕拾:Java中实现对EXCEL文件的读取
  6. CCF论文列表(2022拟定)大更新!NAACL升B!ICLR继续陪跑...MICCAI空降B!PRCV空降C!
  7. 15.JavaScript——34——JavaScript高级
  8. 华清远见Qt作业网络聊天室1014
  9. css 字体图标更改颜色_在CSS中更改字体
  10. 联想z400成功带起外置显卡gtx1050