您的位置:首页 > 理论基础 > 数据结构算法

Java数据结构-二叉树及其遍历

2015-07-23 22:51 435 查看
二叉树的定义:n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互相不相交的、分别称为根结点的左子树和右子树的二叉树组成。

二叉树的特点

0<=度<=2;

左右子树是有顺序的,不能颠倒;

不论有几棵子树,也要区分它是左子树还是右子树。

二叉树的五种基本形态:

空二叉树;

只有一个根结点;

根结点只有左子树;

根结点只有右子树;

根结点既有左子树又有右子树。

举例3个结点的二叉树的形态有:



下面说一些特殊的二叉树。

斜树:所有的结点都只有左子树的二叉树称为左斜树;所有的结点都是只有右子树的二叉树称为右斜树。

满二叉树:所有分支结点都存在左子树和右子树,并且所有叶子结点都在同一层上,这样的二叉树称为满二叉树。

满二叉树的特点:

叶子结点都只能出现在最下一层;

非叶子结点的度一定是2;

在同样深度的二叉树中,满二叉树的结点个数最多,叶子树也最多。

完全二叉树:

对一个具有n个结点的二叉树按层序编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树的位置完全相同,则这棵二叉树称为完全二叉树。

完全二叉树与满二叉树的关系:满二叉树是完全二叉树的一种特例。

完全二叉树的特点:

叶子结点只能出现在最下两层;

最下层的叶子一定集中在左部连续位置;

倒数第二层,若有叶子结点,一定都在右部连续位置;

如果结点的度为1,则该结点只有左子树,即不存在只有右子树的情况;

同样结点树的二叉树,完全二叉树的深度最小(这里注意,可能很多朋友会说为什么不是满二叉树的深度最小呢,其实并不是随笔个数结点的二叉树就能构成满二叉树,所以称之为完全二叉树的深度最小;而且满二叉树其实也是完全二叉树的一种呢)。

二叉树的性质:

性质一:在二叉树的第i层上最多有“ 2的i-1次方 ”个结点。

性质二:深度为k的二叉树最多有“ 2的k次方-1 ”个结点。

性质三:对任何一棵二叉树T,如果其终端结点树为n0,度为2的结点数为n2,则n0=n2+1。(解释:分支线数=总的结点数-1=度为1的结点的分支数+度为2的结点的分支数;总的结点数=度为0的叶子结点数+度为1的结点数+度为2的结点数;根据以上两个等式,可以得出 “ 度为0的叶子结点数=度为2的结点数+1 ” 的结论。)

性质四:具有n个结点的完全二叉树的深度为“ 不大于log以2为底(n)的最大整数+1 ”,文字描述的不太好理解,即如下图:




关于这个性质的解释如下:



性质五:(这里比较长,而且公式不好描述,直接贴图吧)



二叉树的存储结构:

二叉树的顺序存储结构:一维数组

就是用一维数组存储二叉树中的结点,并且结点的位置和数组的下标对应;注意结点的位置采用完全二叉树编号的方法进行设置,这样如果为空结点的地方用^替代。不过这种方法会遇到极端情况,比如:



二叉树的链式存储结构:二叉链表

二叉树每个结点最多有两个子树,所以链表的结点可以设计为一个数据域和两个指针域。如下:




data是数据域;lchild和rchild为指针域,分别存放指向左右子树的指针。举例如下:




改进版:增加一个指针域用来指向双亲,称之为三叉链表。

二叉树的遍历:

原理:指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点有且仅被访问一次。

二叉树的遍历方法:(这里使用的是递归的思想)

前序遍历:

若二叉树为空,直接返回;否则先访问根结点,然后访问遍历左子树,最后访问遍历右子树。举例:




中序遍历:

若二叉树为空,直接返回;否则从根结点开始,先访问遍历左子树,然后访问根结点,最后访问遍历右子树。举例:




后序遍历:

若二叉树为空,直接返回;否则从根结点开始,先访问遍历左子树,然后访问遍历右子树,最后访问根结点。举例:



层序遍历:

若树为空,直接返回;否则从树的第一层的根结点开始访问,从上到下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。举例:



遍历算法:

下面是《大话数据结构》里面的二叉树的前序遍历的代码,其实中序遍历和后序遍历都是类似的只是位置的改变而已;




层次遍历的解释:

由层次遍历的定义可以推知,在进行层次遍历时,对一层结点访问完后,再按照它们的访问次序对各个结点的左孩子和右孩子顺序访问,这样一层一层进行,先遇到的结点先访问,这与队列的操作原则比较吻合。因此,在进行层次遍历时,可设置一个队列结构,遍历从二叉树的根结点开始,首先将根结点指针入队列,然后从对头取出一个元素,每取一个元素,执行下面两个操作:

(1)访问该元素所指结点;

(2)若该元素所指结点的左、右孩子结点非空,则将该元素所指结点的左孩子指针和右孩子指针顺序入队。

以上分析可以清楚层次遍历的大致实现了,具体就不进行讲下去了。

推到遍历:

注意分析题意,最好画出分析图示。

推到遍历的两个性质:

已知前序遍历序列和中序遍历序列,可以唯一确定一棵二叉树。

已知后序遍历序列和中序遍历序列,可以唯一确定一棵二叉树。

注意已知前序遍历序列和后序遍历序列是不能确定一棵二叉树的。

二叉树的建立:

将二叉树中每个结点的空指针引出一个虚结点,设定其值为“#”,这种处理后的二叉树称为原二叉树的扩展二叉树。

得出扩展二叉树后可以根据前中后序遍历的任意一种方法来建立二叉树。(这里同样是用到了递归的思想)

线索二叉树:

利用二叉树的现有二叉链表存储结构中的空指针域来指示前驱和后继,用来指向直接前驱结点和直接后继结点的指针被称为线索(thread),加了线索的二叉树称为线索二叉树。

解释线索二叉树的结构:

一个具有n个结点的二叉树若采用二叉链表存储结构,必有n+1个指针域存放的都是NULL。

若某结点的左孩子指针域(lchild)为空,利用它来指出该结点在某种遍历序列中的直接前驱结点的存储地址,若某结点的右孩子指针(rchild)为空,利用它来指出该结点在某种遍历序列中的直接后继结点的存储地址;非空的指针域,仍存放指向该结点左、右孩子的指针。这样,就得到了一棵线索二叉树。

图示如下:







下面这里举一个上课的时候老师举得例子:










由以上分析可知:

如果所用的二叉树需要经常遍历或者查找结点时需要某种遍历序列中的前驱和后继关系,那么采用线索二叉链表的存储的结构就是非常好的选择。

树的遍历:

从根结点出发,按照某种次序依次访问树中所有结点,使得每个结点被访问一次且仅被访问一次。

树通常有前序(根)遍历、后序(根)遍历和层序(次)遍历三种方式。

(1) 前序(根)遍历

若树为空,则空操作返回;否则

a. 访问根结点;

b. 按照从左到右的顺序以前序遍历方式遍历根结点的每一棵子树。

(2) 后序(根)遍历

若树为空,则空操作返回;否则

a. 按照从左到右的顺序以后序遍历方式遍历根结点的每一棵子树;

b.访问根结点。

(3) 层序(次)遍历

从树的第一层(即根结点)开始,自上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。

树转换为二叉树:

⑴加线:树中所有相邻兄弟之间加一条连线。

⑵去线:对树中的每个结点,只保留它与第

一个孩子结点之间的连线,删去它与其它孩

子结点之间的连线。

⑶层次调整:以根结点为轴心,将树顺时针

转动45度,使之层次分明。

举例如下:



森林转换为二叉树:

⑴ 将森林中的每棵树依上述方法转换成二叉树,此时的二叉树都没有右子树;

⑵ 从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树根结点的右孩子来处理,当所有二叉树依上法连起来后所得到的那一棵二叉树就是由森林转换得到的二叉树。

森林的遍历方式有两种:

(1) 前序遍历:若森林不空,则访问森林中第一棵树的根结点;前序遍历森林中第一棵树的子树森林;前序遍历森林中(除第一棵树之外)其余树构成的森林。即依次从左至右对森林中的每一棵树进行前序遍历。

(2) 中序遍历:若森林不空,则中序遍历森林中第一棵树的子树森林;访问森林中第一棵树的根结点;中序遍历森林中(除第一棵树之外)其余树构成的森林。即依次从左至右对森林中的每一棵树进行后序遍历。

二叉树转换为树或森林

判断一棵二叉树能够转换成树还是森林的标准:二叉树的根是否含有右子树,有则是森林,没有则是树。

⑴ 加线:若某结点x是其双亲y的左孩子,则把结点x的右孩子、右孩子的右孩子、……,都与结点y用线连起来;

⑵ 去线:删去原二叉树中所有的双亲结点与右孩子结点的连线;

⑶ 层次调整:整理由⑴、⑵两步所得到的树或森林,以根结点为轴心,将树逆时针转动45度,使之层次分明。

二叉树转换为树举例:




二叉树转换为森林举例:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: