12.遍历二叉树与二叉树的建立

一、遍历二叉树

1.定义

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

2.前序遍历

(1)规则:若二叉树为空,则空操作返回。否则,先访问根结点,然后前序遍历左子树,再前序遍历右子树。

(2)实例

前序遍历结果为:A
BDGH CEIF

分析:当最先访问根结点后,然后前序遍历左子树。当访问根的左子树时,这里"前序遍历"即我们将B假设为左子树的根来遍历。

(3)算法

从二叉树定义可知,其是用递归的方式,所以,实现遍历算法也可以采用递归。

<span style="font-size:18px;">/*二叉树的前序遍历递归算法*/
void PreOrderTraverse(BiTree T)
{
    if(T==NULL)            //若树为空,返回为空
            return;
    printf("%c",T->data); //显示结点数据,可以更改为其他对结点操作
    PreOrderTraverse(T->lchild);    //再先遍历左子树
    PreOrderTraverse(T->rchild);    //最后遍历右子树
}</span>

实例分析:

如上图所示,当调用PreOrderTraverse(T)函数时,程序运行过程如下:

a)调用PreOrderTraverse(T),T根结点不为null,所以执行printf,打印字母A;

b)然后,调用PreOrderTraverse(T->lchild),访问A结点的左孩子,B结点不为null,执行printf打印出B;

c)此时再次递归调用PreOrderTraverse(T->lchild),访问了B结点的左孩子,执行printf打印字母D;

d)再次执行PreOrderTraverse(T->lchild),访问D结点的左孩子,执行printf打印字母G;

e)再次执行PreOrderTraverse(T->lchild),访问G结点的左孩子,由于G结点没有左孩子,则返回为空,所以T==null,返回此函数,此时调用PreOrderTraverse(T->rchild),访问D结点的右孩子,执行printf打印字母H;

f).......依次继续打印后面字母即可。

3.中序遍历

(1)规则:若树为空,则空操作返回。否则,从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。

(2)实例

中序遍历结果为:GDHB
A EICF

(3)算法

<span style="font-size:18px;">/*二叉树的中序遍历递归算法*/
void InOrderTraverse(BiTree T)
{
    if(T == NULL)
            return;
    InOrderTraverse(T->lchild);    //中序遍历左子树
    printf("%c",T->data);                //显示结点数据,可以更改为其他对结点操作
    InOrderTraverse(T->rchild);    //最后中序遍历右子树
}</span>

4.后序遍历

(1)规则:若树为空,则空操作返回。否则,从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。

(2)实例

后序遍历结果为:GHDB
IEFC A

(3)算法

<span style="font-size:18px;">/*二叉树的后序遍历递归算法*/
void PostOrderTraverse(BiTree T)
{
    if(T==NULL)
            return;
   PostOrderTraverse(T->lchild);//先后序遍历左子树
   PostOrderTraverse(T->rchild);//再后序遍历右子树
   printf("%c",T->data);                //显示结点数据,可以更改为其他对结点操作
}</span>

5.层序遍历

(1)规则:若树为空,则空操作返回。否则,从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。

(2)实例

层序遍历结果为:A
BC DEF GHI

二、推导遍历结果

二叉树遍历性质:

(1)已经前序遍历序列和中序遍历序列,可以唯一确定一颗二叉树;

(2)已经后序遍历序列和中序遍历序列,可以唯一确定一颗二叉树;

1.假设已经一颗二叉树的前序遍历序列为ABCDEF,中序遍历序列为CBAEDF,请问这颗二叉树的

后序遍历结果是多少?

分析:

2.假设一颗二叉树的中西序列是ABCDEFG,后序序列是BDCAFGE,求前序序列?

分析:

三、二叉树的建立

对于一颗普通的二叉树,我们需将二叉树中每个借点的空指针引出一个虚结点,其值为一特定值,比如"#"。我们称这种处理后的二叉树为原二叉树的扩展二叉树,扩展二叉树可以通过一个"前序"或"中序"或"后序"遍历序列确定一颗二叉树。

(1)扩展二叉树的前序遍序列为:AB#D##C##

(2)实现算法

<span style="font-size:18px;">/*按前序输入而二叉树中借点的值(一个字符)
  *    其中,#表示空树,构造二叉链表表示二叉树T
  */
void CreateBitree(Bitree *T)
{
    TElemType ch;
    scanf("%c",&ch);        //输入结点数据字符
    if(ch=='#')
            *T=NULL;
    else
     {
            *T=(BiTree)malloc(sizeof(BiTNode));    //为数据为字符的结点在内存中分配空间
            if(!*T)                                                    //如果分配未成功则异常结束(内存溢出)
                    exit(OVERFLOW);
            (*T)->data = ch;                                    //生成根结点
            CreateBiTree(&(*T)->lchild);                //构造左子树
            CreateBiiTree(&(*T)->rchild);                //构造右子树
    }
}</span>

总结:实际上,建立二叉树也是利用了递归的远离,只不过在原来应该是打印结点的地方改成了生成结点、给结点赋值操作而已。另外,我们也可以通过中序或后序遍历的方式实现二叉树的建立,只不过代码里生成的结点和构造左右字子树的代码顺序交换一下即可。

四、线索二叉树

对于一个有n个结点的二叉链表,每个结点有指向左右孩子的两个指针域,所以一共是2n个指针域。而n个结点的二叉树一共有n-1条分支线(根结点无前驱),也就是说,其实存在2n-(n-1)=n+1个空指针域。由于这些空间不存储任何事物,这样会导致内存资源的浪费。另外,在二叉链表上,我们只能知道每个结点指向其左右孩子结点的地址,而不知道某个结点的前驱是谁,后继是谁。想要知道,就必须遍历一次链表,以后每次需要知道时,都必须先遍历一次。为了提供内存空间的利用率和节省操作时间,我们可以考虑在创建就明确结点的前驱和后继。

1.线索二叉树

如果将指向前驱和后驱的指针称为线索,那么加上线索的二叉链表则称为线索链表;加上线索的二叉树就称之为线索二叉树(Threaded
Binary Tree),对二叉树以某种次序遍历使其变为线索二叉树的过程称作是线索化。通过线索二叉树,我们对它进行遍历就等于操作一个双向链表结构,从而大大提高了访问速度。

如上图二叉树按中序遍历后:HDIBJE
A FCG,空指针指(结点rchild指针或lchild指针)向的后继或前驱。

2.线索二叉树结点结构与实现

(1)结点结构

由于无法知道某一结点的lchild是指向它的左孩子还是指向前驱,rchild是指向右孩子还是指向后继。因此,我们在每个结点再增设两个标志域ltag和rtag,需要注意的是ltag和rtag只是存放0或1数字的布尔型变量,其占用的内存空间要小于像lchild和rchild的指针变量。结点结构如下:

(2)线索二叉树结构实现

<span style="font-size:18px;">/*二叉树的二叉线索存储结构定义
*    Link==0表示左右孩子指针
*    Thread==1表示指向前驱或后驱的线索
*/
typedef eum {Link,Thread} PointerTag;
typedef struct BiThrNode    /*二叉线索存储结点结构*/
{
    TElemType  data;                            //数据域:结点数据
    struct BiThrNode *lchild,*rchild;    //指针域:左右孩子指针
    PointerTag   LTag;
    PointerTag   RTag;                         //左右标志
}BiThrNode,*BiThrTree;</span>

3.中序遍历线索化的递归函数(难点)

线索化的实质就是将二叉链表中的空指针改为指向前驱或后继的线索。由于前驱和后继的信息只有在遍历该二叉树时才能得到,所以线索化的过程就是在遍历的过程中修改空指针的过程。

中序遍历线索化的递归函数代码如下:

BiThrTree pre;    //全局变量,始终指向刚刚访问过的结点

/*中序遍历进行中序线索化*/

void InThreading(BitThrTree p)

{

if(p)

{

InThreading(p->lchild);    //递归左子树线索化

if(!p->lchild)                    //结点无左孩子

{

p->LTag=Thread;        //前驱线索:将结点左指针标志置1,说明左指针指向该结点的前驱

p->lchild=pre;                //左孩子指针指向前驱

}

if(!pre->rchild)                //前驱没有右孩子

{

pre->RTag=Thread;    //后继线索

pre-rchild=p;                //前驱右孩子指针指向后继(当前结点p)

}

pre=p;                            //保持pre指向p的前驱

InThreading(p->rchild);    //递归右子树线索化

}

}

源码分析:

(1)结点前驱线索化

if(!p->lchild)表示如果某结点的左指针域为空,因为其前驱节点刚刚访问过,赋值给了pre,所以可以将pre赋值给p->lchild,并修改p->LTag=Thread(也就是定义为1)以完成前驱结点的线索化。

(2)结点后驱线索化

由于该节点还没有访问到,因此只能对它的前驱结点pre的右指针rchild做判断,if(!pre->rchild)表示如果为空,则p就是pre的后继,于是pre->rchild=p,并且设置pre->RTag=Thread,完成后继结点的线索化。

(3)  pre=p语句的作用是完成前驱和后继的判断后,将当前的结点p赋值给pre,以便下一次使用。

4.中序遍历线索二叉树T的非递归算法

二叉树的二叉线索存储表示(以中序为例):在线索链表上添加一个头结点,并令其lchild域的指针指向二叉树的根结点,其rchild域的指针指向中序遍历时访问的最后一个结点。令二叉树中序串行中的第一个结点的lchild域指针和最后一个结点的rchild域的指针均指向头结点,这样就创建了一个双向线索链表。这样定义的好处是既可以从第一个结点起顺后继进行遍历,也可以从最后一个结点起顺前驱进行遍历。

/*T指向头结点,头结点左键lchild指向根结点,头结点右链rchild指向中序遍历的最后一个结点

*    中序遍历二叉线索链表表示的二叉树T,时间复杂度为O(n)*/

Status InOrderTraverse_Thr(BiThTree T)

{

BiThrTree p;

p=T->lchild;    //p指向根结点

while(p != T)    //空树或遍历结束时,p==T

{

while(p->LTag==Link)    //当LTag==0时循环到中序序列第一个结点

p=p->lchild;

printf("%c",p->data);        //显示结点数据,可以更改为其他对结点操作

while(p->RTag == Thread && p->rchild !=T)

{

p=p->rchild;

printf("%c",p->data);

}

p=p->rchild;        //p进至其右子树根

}

}

源码分析:

(1) p=T->lchild;让p指向根结点开始遍历,如上图编号①所示;

(2)while(p!=T):即循环直到图中的④的出现,此时意味着p指向了头结点,于是与T相等(T是指向头结点的指针),结束循环,否则一直循环下去去进行遍历操作;

(3)while(p->LTag==Link) 循环,就是由A->B->D->H,此时H结点的LTag不是Link(就是不等于0),所以结束此循环并打印H;

(4)while(p->RTag == Thread && p->rchild !=T),由于结点H的RTag==Thread(就是等于1),且不是指向头结点。因此打印H的后继D,之后因为D的RTag是Link,因此退出循环;

(5)p=p->rchild,即p指向了结点D的右孩子。

.....................不断循环,直到打印出HDIBJEAFCG结束遍历操作。

总结:二叉树的线索化有利于节省空间和时间,在实际问题中,如果所用的二叉树需经常遍历或查找结点时需要某种遍历序列中的前驱和后继,那么采用线索二叉链表的存储结构是一个非常不错的选择。

时间: 2024-10-12 20:03:20

12.遍历二叉树与二叉树的建立的相关文章

研磨数据结构与算法-12遍历二叉树

节点: /* * 二叉树节点 */ public class Node { //数据项 public long data; //数据项 public String sData; //左子节点 public Node leftChild; //右子节点 public Node rightChild; /** * 构造方法 * @param data */ public Node(long data,String sData) { this.data = data; this.sData = sDa

72 中序遍历和后序遍历树构造二叉树

原题网址:https://www.lintcode.com/problem/construct-binary-tree-from-inorder-and-postorder-traversal/description 描述 根据中序遍历和后序遍历树构造二叉树 你可以假设树中不存在相同数值的节点 您在真实的面试中是否遇到过这个题?  是 样例 给出树的中序遍历: [1,2,3] 和后序遍历: [1,3,2] 返回如下的树: 2 /  \ 1    3 标签 二叉树 思路:要建立二叉树,首先要建立根

Leetcode 106.从中序与后序遍历序列构造二叉树

从中序与后序遍历序列构造二叉树 根据一棵树的中序遍历与后序遍历构造二叉树. 注意:你可以假设树中没有重复的元素. 例如,给出 中序遍历 inorder = [9,3,15,20,7] 后序遍历 postorder = [9,15,7,20,3] 返回如下的二叉树: 3 / 9 20 / 15 7 解题思路: 已知中序遍历和后序遍历IN和Post,求还原二叉树. 后序遍历的最后一个数post[len-1]就是root节点. 搜索IN,如果IN[I]=post[len-1],那么 IN[I+1]-I

jS生成二叉树,二叉树的遍历,查找以及插入

js递归,二叉树的操作 //递归算法n次幂 function foo(n) { if (n == 1) { return 1; } else { return n * foo(n - 1); } } //console.log(foo(3));var nodes = { name: 'root', childs: [ { name: 'a1' }, { name: 'a2' }, { name: 'a3' }, { name: 'b1' }, { name: 'b2' }, { name: 'b

LeetCode 145 Binary Tree Postorder Traversal(二叉树的后续遍历)+(二叉树、迭代)

翻译 给定一个二叉树,返回其后续遍历的节点的值. 例如: 给定二叉树为 {1, #, 2, 3} 1 2 / 3 返回 [3, 2, 1] 备注:用递归是微不足道的,你可以用迭代来完成它吗? 原文 Given a binary tree, return the postorder traversal of its nodes' values. For example: Given binary tree {1,#,2,3}, 1 2 / 3 return [3,2,1]. Note: Recur

重建二叉树与二叉树的层次遍历

数据结构实验之求二叉树后序遍历和层次遍历 Time Limit: 1000ms   Memory limit: 65536K  有疑问?点这里^_^ 题目描写叙述 已知一棵二叉树的前序遍历和中序遍历,求二叉树的后序遍历. 输入 输入数据有多组,第一行是一个整数t (t<1000).代表有t组測试数据.每组包含两个长度小于50 的字符串,第一个字符串表示二叉树的先序遍历序列,第二个字符串表示二叉树的中序遍历序列. 输出 每组第一行输出二叉树的后序遍历序列,第二行输出二叉树的层次遍历序列 演示样例输

数据结构之 二叉树---求二叉树后序遍历和层次遍历(先建树,再遍历)

数据结构实验之求二叉树后序遍历和层次遍历 Time Limit: 1000MS Memory limit: 65536K 题目描述 已知一棵二叉树的前序遍历和中序遍历,求二叉树的后序遍历. 输入 输入数据有多组,第一行是一个整数t (t<1000),代表有t组测试数据.每组包括两个长度小于50 的字符串,第一个字符串表示二叉树的先序遍历序列,第二个字符串表示二叉树的中序遍历序列. 输出 每组第一行输出二叉树的后序遍历序列,第二行输出二叉树的层次遍历序列 示例输入 2 abdegcf dbgeaf

nyoj重建二叉树(不真的建立)

  感觉c++很陌生啊 题目很简单,给你一棵二叉树的后序和中序序列,求出它的前序序列(So easy!). 输入 输入有多组数据(少于100组),以文件结尾结束.每组数据仅一行,包括两个字符串,中间用空格隔开,分别表示二叉树的后序和中序序列(字符串长度小于26,输入数据保证合法). 输出 每组输出数据单独占一行,输出对应得先序序列. 样例输入 ACBFGED ABCDEFG CDAB CBAD 样例输出 DBACEGFBCAD #include<iostream> #include<me

通过二叉树的中序和后序遍历序列构造二叉树(非递归)

题目:通过二叉树的中序和后序遍历序列构造二叉树 同样,使用分治法来实现是完全可以的,可是在LeetCode中运行这种方法的代码,总是会报错: Memory Limit Exceeded ,所以这里还是用栈来实现二叉树的构建. 与用先序和后序遍历构造二叉树的方法类似,但还是要做一些改变: 如果从后往前处理中序和后序的序列,则处理就为如下所示的情况: Reverse_Post: 根-右子树-左子树 Reverse_In: 右子树-根-左子树 这样处理方式和先序-中序就差不多了,只是将添加左孩子的情况

【数据结构之二叉树】二叉树的创建、遍历等操作

二叉树的基本操作: 1.创建二叉树 2.销毁二叉树 3.遍历二叉树:1)前序遍历 2)中序遍历 3)后序遍历 4)层次遍历 4.搜索二叉树 5.删除子叶 6.插入子叶 7.获取左/右子叶的值 8.获取树深度 9.获取叶子结点数 1.创建二叉树 这里创建的是链式存储结构的二叉树,包含数据域,左右两结点的指针域:在读取创建树时,以#代替空格,输入格式的规范为:以前序遍历的顺序输入,如果该结点的左子叶为空,则输入#,以此类推: e.g: -  +         \ a    *    e    f