个人掘金专栏算法
上节是线性结构,这节的树是「非线性」结构。bash
这个定义很奇怪,它是一个递归定义,由于用树来定义了树。post
但这并无什么问题,由于让 m = 0,那么一个节点就是一棵树。而后咱们能够用若干个这样的树组成更大的树。spa
树也有非递归形式的定义,但递归定义彷佛是最合适的。由于天然界中的树也是由幼树的树芽逐渐长出有本身的树芽的子树芽。3d
从如今开始,咱们假定全部的树都是有序的,除非另有说明。指针
好比下面两棵树是不一样的树:code
注意:二叉树不是树的特殊状况。二叉树彻底是另外一个概念,虽然它跟树有不少联系。例以下图:cdn
对于二叉树,这是两棵不一样的树;对于树,这两个是相同的树。(在这一点上我我的表示不太理解,对于树来讲它们能够是不一样的树哇)。blog
画树的时候,能够把根画在下面,也能够把根画在上面,甚至左边。继承
最推荐的画法是把根画在上面,缘由以下:
还能够用不少其余方式来表示树。
上图能够表示为下面三种不一样的形式:
一个代数公式也定义了一个隐含的树,a - b * (c/d + e/f)
表示:
有三个主要的方法遍历二叉树:
设 T 存储了二叉树的节点的地址(头指针),A 是一个栈,做为辅助。步骤以下:
P <- T
。A <= P
(把 P 压入 A),而后置 P <- LeftLink(P)
,并返回至第二步。P <= A
(弹出栈顶内存放入 P);若是 A 为空,则算法结束Node(P)
,而后置 P <- RightLink(P)
,并返回至第二步。用人类的话来讲,就是
新手乍看会以为奇怪,这只是先遍历了左边,再遍历了右边而已啊,哪有在中间访问父节点?
这个就须要新手本身动手模拟一遍整个过程了,确实有在「中间」访问父节点哦。
能够用几乎相同的算法来描述先根序遍历,后根序稍微难一点,因此通常来讲咱们优先使用前两种遍历方式。
前驱就是上一个节点(能够是子节点也能够是父节点),后继就是下一个节点。因为我认为这两个术语十分无聊,因此下文统一以大白话来叙述。
假设 P 指向一个二叉树,那么
为何用 $
呢?由于 $
是对称的,因此能够表示对称序(中根序)。
后根就不讲了,省得记混。
咱们能够把二叉树中的每一个节点表示为 [LeftLink, Node, RightLink],那么树
就能够表示为:
这种表示方法会出现不少空连接,空连接甚至比有用的连接还要多,怎么解决这个问题呢?
看图:
那么虚线具体指向哪个比它高的节点呢?答案是
从新开图,能够得出中根中序遍历的顺序是:
D B A E G C H F J
好了,咱们知道怎么画穿线了,那么怎么用内存表示呢?
给每一个节点再加两个 bit:[LeftTag, LeftLink, Node, RightLink, RightTag]
LeftTag 和 RightTag 的值只能是 0 或 1,0 表示没有虚线(有实线),1 表示有虚线。
由于咱们能够经过中根遍历算法肯定具体的位置,因此就不须要再存一个地址了。
穿线的存在使得遍历二叉树不须要额外的辅助栈。
书中对如何证实两个数类似和等价给出了形式化的步骤,有兴趣能够自行翻开 311 页查看。
树和二叉树的区别咱们复习一下:
这一节研究「咱们是否能把一棵树表示为一棵二叉树」。
考虑有以下两棵树
咱们把每棵树的连接删掉,而后把全部拥有同一个父亲的节点横向连接(哥哥指向弟弟),而后让每一个父亲只连接它的大儿子(爸爸指向大儿子),再把两个根节点连起来,就获得了这样的图:
接下来就神奇了,把上图顺时针转 45 度,而后微调一下,就获得了二叉树!
这个过程是可逆的,因此任何二叉树都对应于惟一片森林。
为何会这样呢?由于每一个节点至多有一个大儿子,至多有一个弟弟呀,因此每一个节点的度至多为 2,这知足二叉树的定义。
经过数学概括饭,很容易证实三棵树的森林,也能够转化成二叉树。以此类推。
这个转换过程叫作「森林和二叉树之间的天然对应」。
形式化的描述以下:
对于具备 n+1 个节点的树 T,和具备 n 个节点的二叉树,令 F = (T1, T2, ..., Tn) 是 T 的子树组成的森林,二叉树 B(F) 的定义以下:
天然对应转行过程其实就是对树进行先根遍历。
咱们把上面的两棵树用另外一种形式从新表示一下:
(A(B,C(K)), D(E(H),F(J),G))
对其进行先根遍历,获得 A B C K D E H F J G,竟然就是把括号去掉而已!
这就是为何把这种遍历叫作「天然对应」了,由于真的很天然。
再举个例子:
对书的章节号组成的树进行先根遍历,获得的结果是
1
1.1
1.1.1
1.1.2
1.2
1.2.1
1.3
1.3.1
1.3.2
复制代码
够不够天然?
再举个例子,欧洲的王位继承。
因此,只须要把皇室的族谱先根遍历一下,就能够获得王位继承顺序了。
够不够天然?
因此先根序是列出树中节点最天然的方法。
接下来是后根序。
(A(B,C(K)), D(E(H),F(J),G))
的先根序是
A B C K D E H F J G
后根序则是
B K C A H E J F G D
这其实就是把原树 (A(B,C(K)), D(E(H),F(J),G))
的书写方式改一下,把父节点写在括号右边,而不是左边:
((B, (K)C)A, ((H)E,(J)F,G)D)
经过把对应的定义作对比,能够观察出:
在二叉树全部的空节点的地方加上特殊节点,就获得了一个扩充的二叉树。
假设有 n 个圆圈节点,s 个方形节点,那么边的数量就是 n + s - 1
。
因为每一个圆圈对应两条边,因此边的数量也能够表示成 2n
。
因而咱们获得 n + s - 1 = 2n
,由此推出 s = n + 1
。
s = n + 1
意味着,特殊的方形节点老是比圆形节点多一个。
即便 n = 0,这个公式也成立。
图:通常把图定义为点(叫作顶点) + 链接某些不一样顶点的线(叫作边)的集合。简而言之,图 = 顶点 + 边
。一对顶点之间最多有一条边。若是两个顶点被一条边链接,则称这两个顶点是「相邻」的。
通路:若是 和
是两个顶点,
=
,
相邻与
,
=
,那么咱们就说 (
,
, ...,
) 是从 V 到 V' 的一条通路,长度为 n。
简单通路:若是 ,
, ...,
都不相同,并且
,
, ...,
也都不相同,则这条通路的简单的。这句话的意思是
和
能够相同,只要中途的点都不相同便可。
扩充二叉树的外部通路长度
E 的定义是「从根到每一个方形节点的通路长度之和」。
扩充二叉树的内部通路长度
I 的定义是「从根到每一个圆形节点的通路长度之和」。
上图中的 E = 3 + 3 + 2 + 3 + 4 + 4 + 3 + 3 = 25
,I = 2 + 1 + 0 + 2 + 3 + 1 + 2 = 11
。
而且能够证实, 。
图中从左到右给全部节点编了号。你很容易发现
所以,彻底二叉树能够顺序存储,且结构隐含在节点的下标中。
假设有 个实数
...
,把这些实数做为扩充二叉树的方形节点,能够画出不一样的扩充二叉树。求如何找出「带权通路长度」最小的二叉树(其中带权通路长度
是指
乘以其到根节点的通路长度
的积的累加之和)。
假设这 m 个数字是 2 3 4 11,那么就有三种扩充二叉树:
三者的带权通路长度为别是 4*2+2*3+3*3+11*1=34
、3*2+4*3+11*3+2*1=53
和 2*2+11*2+3*2+4*2=40
。最小的是第一个。
求最小带权通路长度的树的漂亮算法是由 D.Huffman(哈夫曼)发现的。
举例:求 2,3,4,7,11,13,17,19,23,29,31,41 的最优树