以孩子链表法兄弟链表为存储结构,求树的深度,其中返回的兄弟子树高度为什么不用加1

之前学的链表、队列、栈都是線性表,因为其中每个数据元素只有一个前驱和一个后继是一对一的关系。

假如是一对多的关系呢这种数据结构就是今天要学的

樹是由有限个结点(假设为n)构成的集合n = 0说明这是棵空树。一棵树中有且只有一个根结点,按照习惯在位于树的顶端根结点可以理解为始祖一般的存在,他们有若干个孩子链表法但是他本身没有双亲。如图1中结点A就是根结点

假设把树从中截断(并没有),可以得箌若干个互不相交(没有交集)的集合每一个集合本身又是一棵树,称为根的子树以后就直接叫“子树”。比如假想我断开了A-B, A-C的连接结点B和C没有双亲成为了根结点,产生了两棵互不相交的子树如下。

什么叫互不相交呢下面粗线连接部分如左图D和E,他们到底属于哪棵树可以认为构成子树的集合之间有交集,造成了原来的子树T1和子树T2相交这样相交的树,不叫子树因为这不符合树的定义。

上面的圖中每一个圆圈代表的就是一个结点结点之间的连线表示了结点之间的关系。结点拥有的子树数目称为该结点的也可以简单理解为該结点拥有的孩子链表法个数。如上面的图中A结点的度为2度为0的结点称为叶子结点——也就是没孩子链表法。度不为0的结点称为非叶子結点或非终端结点树的度为树中各个结点度的最大值,如图1中结点D拥有的孩子链表法有3个最多,所以树的度就是3

树中各个结点之间囿什么关系呢?某结点它的子树的根称做该节点的孩子链表法(Child)该结点称为这些孩子链表法的双亲(Parent),或者直接叫父结点同一个父结点的孩子链表法之间互称为兄弟(Sibling)。举例来说图1中结点C的孩子链表法结点有E和F,E和F的父结点为C而E和F之间是兄弟关系。

树的深度僦是指树的层数根结点处为第一层,其孩子链表法结点为第二层以此类推。易知图1树的深度为4

这种结构的思想比较简单:除了根结點没有父结点外,其余每个结点都有一个唯一的父结点将所有结点存到一个数组中。每个结点都有一个数据域data和一个数值parent指示其双亲在數组中存放的位置根结点由于没有父结点,parent用-1表示

// 树的容量,能容纳的最大结点数 // 存放树的所有结点 // 以指定树大小初始化树 // 以默认的樹大小初始化树 // 新的结点放入数组中第一个空闲位置 // 向上继续查找父结点知道根结点 // 按照以下定义,生成树

setRoot方法必须首先被调用可以看到根结点始终被放置在数组中第一个位置(下标为0),之后才能调用addChild方法createTree将创建树的过程简化了,我们只需输入一组数据datas和这组数據对应的parents传给createTree就行,注意datas的第一个数据是根结点信息在代码中默认使用-1表示其parent,所以它在parents中没有对应的parent值也就是说datas的第二个值才和parents的苐一个值对应,以此类推树创建完成后,若想再添加结点到树调用addChild就行。

childrenFromNode方法获取某个结点的所有孩子链表法结点由代码看出它需偠遍历所有结点,复杂度为O(n)parentTo方法获取某结点的父结点,复杂度O(1)

另外求树的度的时候,也是遍历了所有结点从中选出最大的度作为树嘚度,复杂度为O(n)求树的深度也类似,遍历了所有结点从下往上,一直追溯到根结点用currentDepth记录了当前结点的深度,从所有结点中选择最夶深度值作为树的深度

换种思路,既然双亲表示法获取某结点的所有孩子链表法有点麻烦我们索性让每个结点记住他所有的孩子链表法。但是由于一个结点拥有的孩子链表法个数是一个不确定的值虽然最多只有树的度那么多,但是大多数结点的孩子链表法个数并没有那么多如果用数组来存放所有孩子链表法,对于大多数结点来说太浪费空间了自然我们容易想到用一个可变容量的表来存,选用Java内置嘚LinkedList是个不错的选择先用一个数组存放所有的结点信息,该链表只需存储结点在数组中的下标就行了

// 树的容量,能容纳的最大结点数 // 存放树的所有结点 // 新的结点放入数组中第一个空闲位置 // 父结点添加其孩子链表法 // 根据给定的结点查找再数组中的位置 // 求以node为根结点的子树的罙度 // max是某个结点所有孩子链表法中的最大深度 // 即使没有孩子链表法返回1也是正确的 // 这里需要+1因为depth -> max是当前结点的孩子链表法的深度, +1才是当湔结点的深度

有些方法的实现和双亲表示法一样,有些方法的实现改变了

createTree方法中可以接受一个二维int数组,存放着每个结点的children传参的时候注意一点,如果某个结点没有孩子链表法那么也应该填入空值。就像下面这样否则会引发空指针。

addChild参数列表没变实现变为新添加嘚结点在数组中的下标(其实就是数组的第一个空闲位置)add进父结点的孩子链表法链表中。createTree可以按照定义一次性生成树只需传入结点信息的表和对应的孩子链表法链表就行,复杂度也是O(n)childrenFromNode获得某个结点的所有孩子链表法,这就比双亲表示法好点了它没有遍历所有结点也無需进行if判断,而仅仅将该结点的孩子链表法链表中的内容(整型值)转换成Node对象返回而已不过该实现要获取某个结点的父结点就没有雙亲法好了,孩子链表法表示法必须遍历所有结点复杂度为O(n)。获取父结点方法中遍历所有结点,如果有某个结点的孩子链表法链表中包含了所求结点则返回该结点。

求树的深度方法也变成了递归实现在双亲法实现中由于存在parent域,所以从下至上查找比较方便;而在孩孓链表法表示法中获取孩子链表法结点比较方便,所以从根结点开始从上至下查找这里使用到了递归的思想。由于返回的是max + 1(为什么昰这个值后面会解释)所以需要对树空的情况进行正确的处理。若是叶子结点循环不会执行,应该返回max + 1 = 1正确;其他情况,该结点有駭子链表法进入循环开始递归,递归直到遇到叶子结点停止开始返回,叶子结点返回1回到父结点的nodeDepth函数,max被赋值为1现在说说这个max箌底是什么意思,代码中for (int i: node.children)遍历当前结点的所有孩子链表法,它们共享同一个max所以max的意义就是某结点所有孩子链表法结点的深度的最大徝。于是max + 1就是当前结点的深度接着说,函数一直返回每次返回实际就是往上一层,到所求结点的孩子链表法结点处其孩子链表法结點中的最大深度赋值给max,那么最后返回的max + 1就是所求结点作为根结点时的子树深度

再说获取某结点父结点的方法,从代码看出它遍历了所囿结点如果要改进,可以将双亲表示法融合进去增加一个parent域就行。也就是说Node类改成如下就行,这种实现可以称为双亲孩子链表法表礻法

这样获取父结点的复杂度就变成了O(1),就懒得实现了稍微改改代码就好了。

还有一种表示法关注某结点的孩子链表法结点之间的關系,他们互为兄弟一个结点可能有孩子链表法,也有可能有兄弟也可能两者都有,或者两者都没基于这种思想,可以用具有两个指针域(一个指向当前结点的孩子链表法一个指向其兄弟)的链表实现,这种链表又称为二叉链表特别注意的是,双亲表示法和孩子鏈表法结点表示法都使用了数组存放每一个结点的信息,若稍加分析使用数组是有必要的。但在这种结构中我们摒弃了数组,根结點可以作为头指针以此开始可以遍历到树的全部结点——根结点肯定是没有兄弟的(根结点如果有兄弟这棵树就有两个根结点了),如果它没有孩子链表法则这棵树只有根结点;若有孩子链表法,就如下图它的nextChild的指针域就不为空,现在看这个左孩子链表法有兄弟(實际就是根结点的第二个孩子链表法)还有孩子链表法,则左孩子链表法的两个指针域都不为空再看这个左孩子链表法的nextSib,他有个孩子鏈表法...一直这样下去对吧,能够访问到树的全部结点的

整个结构就是一条有两个走向的错综复杂的链表,垂直走向是深入到结点的子孓孙孙;水平走向就是查找它的兄弟姐妹这种结构也能直观反映树的结构的,上图其实就是下面这棵树

说了这么多,反正把它当链表僦行了就是多了一个指针域而已。(和双向链表区别开双向链表是a.next =b,必然有b.prev = a;但是这里二叉链表却没有这个限制它指向任意一个结点嘟可以)。

// 存放所有结点每次新增一个结点就add进来 // 以指定的根结点初始化树 // 如果该parent是叶子结点,没有孩子链表法 // parent有孩子链表法了只能放在n其第一个孩子链表法的最后一个兄弟之后 // 从parent的第一个孩子链表法开始,追溯到最后一个兄弟

由于有的方法需要遍历树的所有结点所鉯自建一个表List<Node<Item>> nodes来存放,具体来说就是每次添加结点的同时将这个结点加入到该表中

addChild方法很重要,如果需要依附的父结点还没有孩子链表法(if分支)那需要添加的结点成为它的第一个孩子链表法;如果父结点有孩子链表法了(else分支),那么就从父结点的第一个孩子链表法開始一直到它最后一个兄弟之后,新添加的结点位于此处

获取某结点的所有孩子链表法childrenFromNode方法,就是从所求结点的第一个孩子链表法开始不断找到其兄弟,第一个孩子链表法与其所有兄弟全部就是所求结点的所有孩子链表法

求度,求深度的算法和孩子链表法表示法差鈈多就不再赘述。来看clear清空树的方法需要把每个结点得信息都置为空,就必须有个方法能遍历这棵树这里用了后序遍历的方法,这の后还没完存放结点的表也应该清空才是。

若是想让获取父结点变得方便些也可以多设置一个parent域,见孩子链表法表示法的优化

孩子鏈表法兄弟表示法有一个优点,可以将一棵普通树转化成二叉树由于二叉树有诸多特征,使得处理起来变得简单孩子链表法兄弟表示法上面的那个链表,稍微拉伸下改变下结构就能变成一棵二叉树,如下


  • 1.树(Tree): 树是 n(n>=0) 个结点的有限集。当 n=0 时称为空树在任意一颗非空树中:有且仅有一...

  • 前面讲到的顺序表、栈和队列都是一对一的线性结构,这节讲一对多的线性结构——树「一对多」就是指一个元素只能有┅个前...

  • B树的定义 一棵m阶的B树满足下列条件: 树中每个结点至多有m个孩子链表法。 除根结点和叶子结点外其它每个结点至少有m...

  • 数据结构和算法--二叉树的实现 几种二叉树 1、二叉树 和普通的树相比,二叉树有如下特点: 每个结点最多只有两棵子...

  • 1.树的定义 树是n(n>=0)个结点的有限集.n=0时称為空树.在任意一颗非空树种:(1)有且仅有一个特定的称为...

描述:对以孩子链表法-兄弟链表表示的树编写计算树的深度的算法

//层次遍历构建二叉树

求深度采用递归调用如果树为空,则返回0如果左孩子链表法为空,则返回1
定義一个最大值变量用于保留当前的最大值,如果后面兄弟结点的dep大于max则将max置位dep
层层下降,最后返回max+1

我要回帖

更多关于 孩子链表法 的文章

 

随机推荐