[数据结构]二叉树创建与遍历

实验报告:二叉树创建与遍历

一、问题描述

二叉树是一种实用范围很广的非线性结构,一棵非空二叉树有也只有一个根结点,每个结点最多有两个子树,我们称为左子树与右子树,当一个结点的左、右子树都是空的时,沃恩称此结点为叶子结点。

二叉树有一些很好的性质,这里不再赘述。考虑如何存储一棵树,本实验选择使用链式存储结构——二叉链表;如果事先知道需要存储的二叉树是满二叉树或者完全二叉树,则可以考虑使用顺序存储,否则将浪费大量的存储空间。

对于一棵既成的二叉树,有三种遍历方式——先序、中序与后序。可以证明,一棵形态固定的二叉树与先序遍历、后序遍历生成的序列是1-1对应的,基于这一点,我们可以从先序序列或后序序列出发得到唯一与之对应的二叉树。这是本实验创建二叉树的理论基础。

值得注意的是,一个中序遍历序列与二叉树并不是1-1对应的关系,也就是说一个中序序列对应的二叉树的形态并不固定,即使像本实验中这样添加了虚空结点也不是1-1对应的,所以中序列并不能够生成一棵二叉树。

本实验主要给出先序创建二叉树,以及中序遍历该树。

二、数据结构——二叉链表

二叉链表是二叉树的链式存储结构,其结构与二叉树的性质有天然的契合。

我们知道,二叉树的结点需要保存的信息有:当前结点的信息、左子树、右子树。基于如此的结构,我们在一个结点处分为数据域和指针域:数据域保存该结点自身的信息;指针域分为左、右指针域,分别指向该结点的左、右子树;有必要时可以加上一个链域指向该结点的父亲结点(双亲结点),当然这样就成了三叉链表。三叉链表与二叉链表没有太大的差别,只是三叉链表在具体操作涉及到寻找结点的祖先时会有优势,比如寻找最近公共祖先(lca),这与本实验无关,不再赘述。

值得注意的是,当结点的某个子树不存在时,指向该结点的指针域应该是NULL的。有个小技巧是,可以人为定义一个“空指针域”,应该指向NULL的时候都指向这个所谓的“空结点”,可以一定程度上避免指针访问越界的错误。

三、算法的设计和实现

这个算法的思想非常简单。本实验可以分为两部分——创建二叉树与遍历二叉树。

1、创建二叉树(以先序序列为例)

树的序列生成是一个递归的过程,所以以遍历序列创建树的时候也需要递归生成这棵树。

以先序序列为例,如果当前标号非空,那么我们得到的就是当前结点的数据,保存该数据后,下一个数据——无论是虚空结点还是真实的数据——是左子树的数据,所以我们将递归到左子树中;如果当前标号是空的,即虚空结点,表示此处不存在结点,返回NULL即可;当前结点的左子树递归返回后进入右子树递归;返回该结点的地址,结束。

可以看出这就是一个模拟生成先序序列的过程,着眼于一个结点来说,就是先访问当前子树的根节点,然后左子树,再右子树,最后返回。

后序序列也可以类似地生成,具体细节将在下面提到。

2、遍历二叉树(以中序遍历为例)

与树的创建相似,二叉树的遍历同样是一个递归的过程。

以中序序遍历为例,如果左子树非空,则递归进入左子树;输出当前结点的标号;如果右子树非空,则递归进入右子树;结束。如果不是需要输出序列,而是想得到这个序列,那么很自然地可以想到栈,将输出标号的操作改为将标号压栈即可,别的操作不改动。

遍历二叉树的意义重大,比如中序遍历一棵排序二叉树,得到的就是保存的有序的序列。

四、预期结果和实验中的问题

1、预期结果:

程序能够根据输入的先序序列(带有虚空结点),正确地生成对应的二叉树,并正确地输出中序遍历序列。下图是一个生成三序的例子。

2、实验中的问题及一些说明:

(1)如果没有虚空结点,能否通过三序得到唯一对应的二叉树?

答案是肯定的。下面将简单地说明如何利用先序序列和中序序列得到对应的二叉树。

A)分析:

对于一个先序序列来说,第一个结点一定是根结点,它的右边是左子树的先序序列,然后是右子树的先序序列。这里的问题在于,无法找到左、右子树序列的分界点,换句话说,仅靠一个先序序列我们无法得到对应的二叉树。

这时我们再对中序序列的构成分析,我们可以在中序序列中找到根结点(根结点的标号已经在先序序列中得到了),它的左边是左子树的中序序列,右边是右子树的中序序列。

这时候我们发现,这里出现了一个子结构。因为一棵树(子树)的三序序列长度是相等的,所以我们可以得到左、右子树的先序序列,从而对于任一子树来说,我们得到了它的先序序列和中序序列,问题转换成了相似的子问题。

B)实现:

a)先序序列第一个结点找到根结点;

b)在中序序列中找到该结点(此处可以顺序查找,找到结束即可;当数据量比较大时推荐使用二分法查找),得到左、右子树的结点数,从而分别得到左、右子树对应的先序序列和中序序列;

c)分别递归进入左、右子树,建树。递归的边界是叶子结点,它的先序和中序都是长度为1的自己的标号。

(2)包含虚空结点的后序序列如何生成对应的二叉树?

有一个简单的办法,从后往前扫描后序序列,相当于是顺序为“根结点-右子树-左子树”生成的序列。这个与先序序列生成对应二叉树是一个镜面的过程,不再赘述。

(3)中序序列能够生成唯一对应的二叉树吗?

答案是否定的。中序序列与二叉树并不是1-1对应的,下图是一个例子:

这两个都是合法的二叉树,两者的中序序列都是CBA,即使是加上了虚空结点,得到的中序序列也都是$C$B$A$$表示空格,也就是虚空结点),所以中序序列与二叉树不是1-1对应的,当然不能通过中序序列生成一棵唯一的二叉树。

(2)能否不使用递归操作?

这个问题可以归结为研究递归操作的本质。

递归操作实际上是一个对栈的操作,以先序遍历二叉树为例:将当前结点压栈;如果左子树非空,将左子树压栈;如果右子树非空,将右子树压栈;如果左、右子树都是空的,即当前结点为叶子结点,弹栈;当结点的左右子树都完成了操作,弹栈。

于是我们得到了一个有意思的结果:所有的递归算法是可以有非递归调用的实现方式的(这里的非递归调用实现方式指的是递归调用函数,其本质应该还是递归)。当然,也不排除一些递归解决的问题同样可以用递推解决,比如汉诺塔问题,这里不赘述。

附:c++源代码:

 

 1 /*
 2 项目:创建二叉树,三序遍历
 3 作者:张译尹 
 4 */ 
 5 #include <iostream>
 6 #include <cstdio>
 7 
 8 using namespace std;
 9 
10 template <class T> class BiTree
11 {
12 private:
13     T data; //根结点的标志
14     BiTree *lch, *rch; //左右儿子
15 public:
16     void InitBiTree() //初始化二叉链表
17     {
18         data = 0;
19         lch = rch = NULL;
20     }
21     BiTree* CreatBiTree() //先序(带虚空结点)递归建立二叉链表 
22     {
23         BiTree <char> *rt = new BiTree;
24         rt -> InitBiTree();
25         char ch;
26         scanf("%c", &ch);
27         if(ch != ' ') //非空指针,递归建树
28         {
29             rt -> data = ch;
30             rt -> lch = CreatBiTree();
31             rt -> rch = CreatBiTree();
32         } 
33         else
34         {
35             delete rt;
36             rt = NULL;
37         }
38         return rt;
39     } 
40     void PreOrderTraverse() //递归先序遍历 
41     {
42         printf("%c", data);
43         if(lch)
44             lch -> PreOrderTraverse();
45         if(rch)
46             rch -> PreOrderTraverse();
47     }
48     void InOrderTraverse() //递归中序遍历
49     {
50         if(lch)
51             lch -> InOrderTraverse();
52         printf("%c", data);
53         if(rch)
54             rch -> InOrderTraverse();
55     } 
56     void PostOrderTraverse() //递归后序遍历
57     {
58         if(lch)
59             lch -> PostOrderTraverse();
60         if(rch)
61             rch -> PostOrderTraverse();
62         printf("%c", data);
63     } 
64 };
65 
66 int main()
67 {
68     BiTree <char> *Tree;
69     //Tree -> InitBiTree();
70     
71     printf("请输入二叉树的先序列,结点用字母表示,空域用空格表示。\n");
72     Tree = Tree -> CreatBiTree();
73     
74     printf("先序序列:\n");
75     Tree -> PreOrderTraverse();
76     printf("\n");
77     
78     printf("中序序列:\n");
79     Tree -> InOrderTraverse();
80     printf("\n");
81     
82     printf("后序序列:\n");
83     Tree -> PostOrderTraverse();
84     printf("\n");
85     
86     return 0;
87 }
View Code

 

posted @ 2015-12-06 18:28  zyy是一只超级大沙茶  阅读(2700)  评论(0编辑  收藏  举报