树(tree)

树(tree)

树(tree)型结构是一类重要的非线性结构。树型结构反映了数据元素之间的层次关系和分支关系,很是相似于天然界中的树。java

一、概念

树是由一个集合以及在该集合上定义的一种关系构成的。集合中的元素称为树的结点,所定义的关系称为父子关系。父子关系在树的结点之间创建了一个层次结构。node

1.递归定义

树是N(N>0)个结点的有限集合。算法

集合中存在惟一的一个结点,称为树根(root),该结点没有前趋结点,除根结点之外其他结点分为M(M≥0)个互不相交的集合,其中每个集合都是一颗树,并称其为根的子树(sub tree)。数组

以下图,为T={R,A,B,C,D,E,F}的树型表示法数据结构

采用子树概念递归定义数为:树是由根节点和若干棵子树构成的。post

2.基本术语

  • 一个结点的子树个数称为该结点的度(degree)
  • 一棵树中结点度的最大值称为该树的度
  • 度为零的结点称为叶子(leaf)或者终端结点
  • 度不为零的结点称为分支结点或者非终端结点
  • 除根结点以外的分支结点统称为内部结点
  • 树中结点的后继结点称为儿子(child)或者儿子结点
  • 结点的前趋结点称为儿子结点的父亲(parents)或者父结点
  • 同一个父亲的儿子称为兄弟(sibling)
  • 结点的层次(level)从根开始定义,层次数为0的结点是根结点,其子树的根的层次数为1。若结点在L层,其子树的根就在L+1层。
  • 树中结点的最大层次数称为树的深度(depth)高度
  • 在树中k+1个结点经过k条边链接构成的序列{(v0,v1),(v1,v2), … ,(vk-1,vk)| k ≥0},称为长度为k的路径(path)
  • 从根到该结点路径上的全部结点都是该结点的祖先(Ancestor)
  • 以某结点为根的树中的任一结点都称为该结点的子孙(Descendant)
  • 父亲在同一层次的结点互为堂兄弟
  • 若是将树中结点的各子树当作是从左至右是有次序的,则称该树为有序树(Ordered Tree);若不考虑子树的顺序则称为无序树(Unordered Tree)
  • 树中全部结点最大度数为m的有序树称为m叉树
  • 森林(forest)是m(m ≥0 )棵互不相交的树的集合。对树中每一个结点而言,其子树的集合即为森林。树和森林的概念相近。删去一棵树的根,就获得一个森林;反之,加上一个结点做树根,森林就变为一棵树。

树中结点之间所存在的父子关系能够用于描述树型结构的逻辑特征:编码

  • 树中任一个结点均可以有领个或者多个后继结点,但最多只能有一个前趋结点。
  • 树中只有根结点无前趋,叶子结点无后继。

树型结构是非线性结构。.net

祖先与子孙的关系则是对父子关系的延伸,其定义了树中结点的纵向次序。3d

有序树的兄弟关系的延伸,定义了树中结点的横向次序。指针

3.元祖定义

ADT表示为:TREE=(D,R)

其中D是具备相同特性的数据元素的集合;R是元素集合D上的关系集合。若是D中只含有一个数据元素,则R为空集。

4.ADT定义

下面是数的抽象数据类型的定义:

ADT Tree{
数据对象D:D是具备相同性质的数据元素的集合。
数据关系R:若D=Φ则R=Φ;若D≠Φ,则R={H},H是以下二元关系:
      ①在D中存在一个惟一的称为根的元素root,它在H下无前驱;
      ②除root之外,D中每一个结点在H下都有且仅有一个前驱。
基本操做:
getSzie();				//返回树的结点数。
getRoot();				//返回根结点。
getParent(x);			//返回结点x的父结点。
getFirstChild(x);			//返回结点x的第一个儿子。
getNextSibiling(x);		//返回结点x的下一个兄弟结点。
getHeight(x);			//返回以x为根的树的高度。
insertChild(x,child);		//将结点child为根的子树插入树中,做为x的子树
deleteChile(x,i);			//删除x的第i棵子树。
proOrder(x);			//先序遍历
postOrder(x);			//后续遍历
levelOrder(x);			//按层遍历
}ADT Tree

5.表示方式

树的经常使用表示方式:树型表示法、文氏图表示法、凹入图表示法以及广义表表示法。 以下图:

二、二叉树(binary tree)

1.定义

每一个结点的度均不超过2的有序树,称为二叉树(binary tree)。

二叉树的递归定义以下:

二叉树或者是一棵空树,或者是一棵由一个根结点和两棵互不相交的分别称为根的左子树和右子树的子树所组成的非空树。

二叉树中每一个结点的孩子数只能是0、1或2个,而且每一个孩子都有左右之分。

位于左边的孩子称为左孩子,位于右边的孩子称为右孩子;以左孩子为根的子树称为左子树,以右孩子为根的子树称为右子树。

二叉树结构与通常树结构的区别:

  • 第一:二叉树能够是空树,即不包含任何结点;通常树至少应该有一个结点。
  • 第二:二叉树区别于度数为2的有序树,在二叉树中容许某些结点只有右子树而没有左子树;有序树中,一个结点若是没有第一子树就不可能有第二子树的存在。

所以二叉树并不是是树的特殊情形,他们是两种不一样的数据结构。

2.分类

每层结点都达到最大数的二叉树称为满二叉树。

能够对满二叉树的结点进行编号,约定编号从根结点起,层间自上而下,层内自左而右,逐层由1到n进行标号。

若一棵二叉树最多只有最下面的两层结点的度数能够小于2,而且最下一层上的结点都集中在该层最左边的若干位置上,则此二叉树称为彻底二叉树。

3.性质

性质1

二叉树第i(i≥1)层上的结点树最多为2^(i-1)。

性质2

高度为k的二叉树最多有2^k-1个结点。

性质3

对任何二叉树T,设n0、n一、n2分别表示度数为0、一、2的结点树,则n0=n2+1。

由性质3能够推导出:

当n2=0时,n0=1。即该二叉树有一个起始结点和一个终端结点,其余各结点有一个父亲和儿子,二叉树退化为单链表。

性质4

有n个结点的彻底二叉树的高度为⎣log n⎦。

性质5

满二叉树原理 非空满二叉树的叶节点数等于其分支结点数加1。

性质6

一棵非空二叉树空子树的数目等于其结点数目加1.

4.存储结构

二叉树的存储结构:顺序存储结构和连接存储结构。

1>顺序存储结构

二叉树的顺序存储结构是把二叉树的全部结点按照必定的次序顺序存储到一组包含n个存储单元的额空间中。

在二叉树的顺序存储结构中只存储结点的值(数据域),不存储结点之间的逻辑关系,结点之间的逻辑关系由数组中下标的顺序来体现。

二叉树顺序存储的原则是:无论给定的二叉树是否是彻底二叉树,都看做彻底二叉树,即按照彻底二叉树的层次次序把各个结点依次存储数组中。

如图为二叉树的顺序存储结构:

在顺序存储结构中,由某个结点的存储单元地址能够推出其父亲、左儿子、右儿子及其兄弟的地址,假设给定结点的地址为I,则:

  • (1)若I=1,则该结点是根结点,无父亲。
  • (2)若I≠1,则该结点的父亲结点地址为I/2的整数部分。
  • (3)若2×I≤n,则该结点的左二指结点地址为2×I,不然该结点无左儿子。
  • (4)若2×I+1≤n,则该结点的右儿子结点地址为2×I+1,不然该结点无右儿子。
  • (5)若I为奇数(不为1),则该结点的左兄弟为I-1。
  • (6)若I为偶数(不为n),则该结点的右兄弟为I+1。

顺序存储结构对彻底二叉树而言,既简单又节省存储空间。

通常二叉树为了能用结点在数组中的相对位置表示结点之间的逻辑关系,也必须按彻底二叉树的形式来存储树中的结点,这必然形成存储空间的浪费。

2>连接存储结构

通常二叉树使用顺序存储结构会形成大量存储空间的浪费,因此通常二叉树的存储结构更多的采用连接的方式。

二叉树的连接存储结构中每一个结点由数据域和指针域两部分组成。

二叉树每一个结点的指针域有两个,一个指向左儿子,一个指向右儿子。使用此结构的二叉树的连接存储结构称为二叉链表。

能够在上述结点的结构中增长一个指向结点的父结点的指针域,采用此结点结构获得的二叉树存储结构称为三叉链表。

5.二叉树的遍历

树的遍历(traversal)是按照某种次序访问树中的全部结点,且每一个结点刚好访问一次。

二叉树的遍历是以递归的方式进行,依递归的调用顺序的不一样,可分为3种不一样的遍历方式:前序遍历方式、中序遍历方式、后序遍历方式。

1>前序遍历

前序遍历(Preorder Traversal)是先遍历根结点,再遍历左子树,最后才遍历右子树。

操做步骤以下:

  • (1)访问根结点
  • (2)前序遍历左子树
  • (3)前序遍历右子树

2>中序遍历

中序遍历(Inorder Traversal)是先遍历左子树,再遍历根结点,最后才遍历右子树。

操做步骤以下:

  • (1)中序遍历左子树
  • (2)访问根结点
  • (3)中序遍历右子树

3>后序遍历

后续遍历(Postorder Traversal)是先遍历左子树,再遍历右子树,最后才遍历根结点。

操做步骤以下:

  • (1)后续遍历左子树
  • (2)后续遍历右子树
  • (3)访问根结点

4>层次遍历

层次遍历是指从二叉树的第一层开始,从上至下逐层遍历,在同一层中,则按从左到右的顺序对结点逐个访问。

操做步骤以下:

  • (1)初始化一个队列
  • (2)二叉树的根结点放入队列
  • (3)重复步骤(4)-(7)直至队列为空
  • (4)从队列中驱虎一个结点x
  • (5)访问结点x
  • (6)若是x存在左子节点,将左子节点放入队列
  • (7)若是x存在右子节点,将右子节点放入队列

三、线索二叉树

1.定义

利用空指针域存放结点的前趋结点和后继几点的指针信息,这种附加的指针称为线索。

增长了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded Binary Tree)

为了区分一个结点的指针是指向去儿子仍是指向其前趋后继,为每一个结点增长了两个线索标志域ltag和rtag,这样线索链表的结点结构以下图:

标志域标志的是左右指针域的功能,以下:

  • ltag=0:lchild是指向结点的左儿子的指针。
  • ltag=1:lchild是指向结点的前趋结点的左线索。
  • rtag=0:rchild是指向结点的右儿子的指针。
  • rtag=1:rchild是指向结点的后继结点的右线索。

每一个标志位只占1bit。

2.遍历

一棵二叉树以某种方式遍历并使其变成线索二叉树的过程称为二叉树的线索化。 以下图:三种遍历下的线索二叉树

为了讨论算法方便起见,一般在二叉树中增长一个与树中结点相同类型的头结点,令头结点的信息域为空,其lchild域指向二叉树的根结点,当二叉树为空时,lchild域值为空;其rchild域指向以某种方式遍历二叉树时最后访问的结点,当二叉树为空时,rchild域指向该结点自己,同时令原来指向二叉树根结点的头指针指向该头结点,以某种方式遍历二叉树时,第一个被访问结点的左指针域和最后一个被访问结点的右指针域的值若是是线索,也指向该头结点。

如图:

3.运算

1>建立中序线索二叉树

建立线索二叉树实质上就是遍历一棵二叉树,在遍历的过程当中,检查当前结点的指针域是否为空,若是为空改成指向前趋结点或者后继结点的线索。

在堆一个二叉树加线索时,必须申请一个头结点,创建头结点与二叉树的根结点的线索,对二叉树线索化后,还需创建最后一个结点与头结点之间的线索。

总结起来就两个步骤:

  • 第一:遍历修改空指针域;
  • 第二:申请头结点,并创建头结点与根结点和最后一个结点的线索。

2>查找前趋结点

若是该结点的左标志位为1,那么其左指针域所指向的结点即是它的前趋结点。

若是该结点的左标志位为0,代表该结点有左儿子,根据中序遍历的定义,它的前趋结点是以该结点的左儿子为根的子树的最右结点。

3>查找后继结点

若是该结点的右标志位为1,那么其右指针域所指向的结点就是它的后继结点。

若是该结点的右标志位为0,代表该结点有右儿子,根据中序遍历的定义,它的后继结点就是以该结点右儿子为根的子树的最左结点。

4>查找固定值

顺序遍历比对数据便可。

5>插入结点

插入结点可分为两种状况考虑:

  • 其一:将新结点插入到二叉树中做为某结点的左儿子;
  • 其二:将新结点插入到二叉树中做为某结点的右儿子。

每种状况又分别分为两种:

  • 插入当前结点的左右子节点为空,为空直接插入,修改当前结点的指针域,以及插入结点的指针域便可。
  • 插入当前结点的左右子节点不为空,断开原有的父子关系,从新创建上下三层的父子关系。

四、树之间的转换

1.树转换为二叉树

将一颗树转换为二叉树的步骤以下:

  • (1)树中全部相邻兄弟之间加一条线;
  • (2)对树中的每一个结点,只保留它的第一个儿子结点之间的连线,删去与其余儿子结点之间的连线;
  • (3)以树的根结点为轴心,将整棵树顺时针转动必定的角度,使之结构井井有条。

转换过程以下图:

在转换事后的二叉树中,左分支上的各结点在原来树中是父子关系,右分支上的各结点在原来的树中是兄弟关系。

2.二叉树还原为树

树转换为二叉树这一转换过程是可逆的,能够依据二叉树的根结点有无右儿子结点,将一颗二叉树还原为树。

步骤以下:

  • (1)若某结点是其双亲的左儿子,则把该结点的右儿子、右儿子的右儿子……都与该结点的双亲结点用线链接起来;
  • (2)删除原二叉树中全部的双亲结点与右儿子结点的连线;
  • (3)整理由(1)、(2)两步所获得的树,使之结构井井有条。

以下图即为还原过程:

3.森林转换为二叉树

森林是若干棵树的集合,森林亦可用二叉树表示。

转换步骤以下:

  • (1)将森林中的每棵树都转换成相应的二叉树
  • (2)第一棵二叉树不动,从第二棵二叉树开始,依次将后一棵二叉树的根结点做为前一棵二叉树根结点的右孩子,当全部的二叉树连在一块儿后,这样所获得的二叉树就是由森林转换获得的二叉树。

过程如图:

五、树的遍历

树的遍历分为两种方式:先根遍历和后根遍历。

1.先根遍历

先根遍历定义为:

  • (1)访问根结点;
  • (2)按照从左到右的顺序先根遍历根结点的每一棵子树。

2.后根遍历

后跟遍历的定义为:

  • (1)按照从左到右的顺序后跟遍历根结点的每一棵子树;
  • (2)访问根结点。

3.总结

根据树与二叉树的转换关系以及树与二叉树的遍历定义能够推出:

  • 树的先根遍历与其转换的相应二叉树的前序遍历结果序列相同
  • 树的后根遍历与其转换的相应二叉树的中序遍历结果序列相同

所以,树的遍历算法也可采用相应的二叉树的遍历算法实现。

六、森林的遍历

森林的遍历有三种方式:前序遍历、中序遍历和后续遍历。

1.前序遍历

前序遍历的定义为: 若森林非空,则:

  • (1)访问森林中第一棵树的根结点
  • (2)前序遍历第一棵树的根结点的子树森林
  • (3)前序遍历剩余的其余子森林

2.中序遍历

中序遍历的定义为: 若森林非空,则:

  • (1)中序遍历第一棵树的根结点的子树森林
  • (2)访问森林中第一棵树的根结点
  • (3)中序遍历剩余的其余子森林。

3.后序遍历

后续遍历的定义为: 若森林非空,则:

  • (1)后序遍历第一棵树中根结点的子树森林;
  • (2)后序遍历除去第一棵树后剩余的树构成的森林;
  • (3)访问森林中第一棵树的根结点。

4.总结

根据森林与二叉树的转换关系以及森林和二叉树的遍历定义能够推出:

  • 森林前序遍历、中序遍历和后序遍历分别与所转换的二叉树的前序遍历、中序遍历和后序遍历的结果序列相同。

七、树的存储结构

1.双亲链表表示法

以一组连续的存储单元来存放树中的结点,每一个结点有两个域:一个是数据域,用来存放结点的信息,另外一个是双亲域,用来存放双亲的位置。 结构如图所示:

2.孩子链表表示法

将一个结点全部孩子连接成一个单链表形,而树中有若干个结点,则由若干个单链表,每一个单链表有一个表头结点,全部表头结点用一个数组来描述。 结构如图:

3.双亲孩子链表表示法

如图所示:

4.孩子兄弟链表表示法

相似二叉链表,但第一根链指向第一个孩子,第二根链指向下一个兄弟。 结构如图:

八、哈夫曼树

哈夫曼(Huffman)树又称最优树,能够用来构造最优编码,用于信息传输、数据压缩等方面,是一类有着普遍应用的二叉树。

1.概念

  • 在一棵树中,从一个结点往下能够达到的孩子或子孙结点之间的通路称为路径。
  • 通路中分支的数据称为路径长度。
  • 若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。
  • 结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。
  • 树的带权路径长度规定为全部叶子结点的带权路径长度之和,记为WPL。
  • 按必定规则构造一棵二叉树,使带权路径长度达到最小,称这样的二叉树为最优二叉树,也称哈夫曼树。

2.编码问题应用

在数据通讯中,常常须要将传送的文字转换成由二进制字符0、1组成的二进制串,咱们称之为二进制编码。 在发送端,须要将电文中的字符转换成二进制的0、1序列,而在接受端则要将受到的0、1序列转换成对应的字符序列。

哈弗曼树可用于构造使电文的编码总长最短的编码方案:

规定哈弗曼树中的左分支表明0,右分支表明1,则从根结点到每一个叶节点所通过的路径分支组成的0或1序列便为该结点对应字符的编码,称为哈夫曼编码。

在哈夫曼编码树中,树的带权路径长度的含义是各个字符的码长与其出现次数的乘积和,也就是电文的代码总长。

由于哈夫曼算法构造的是带权路径长度最小的二叉树,因此采用哈夫曼树构造的编码是一种能使电文代码总长最短的不等长编码。

实现哈夫曼树的算法可分为两大部分,构造哈夫曼树和在哈夫曼树上求叶结点的编码。

在构造哈夫曼树时,能够设置一个结构数组huff_node保存哈夫曼树中各结点的信息,根据二叉树的性质可知,huff_node数组的大小可设置为2n-1。 数组元素的结构以下:

  • weight域:保存结点的权值。
  • lchild、rchild域:分别保存该结点的左右孩子结点在数组中的序号,从而创建关系。
  • flag:标志域。当flag=0时,表示该结点未加入树中;当flag=1时,表示该结点已加入树中。

上一篇:数组和广义表

下一篇:图(graph)

相关文章
相关标签/搜索