图解!10张图揭秘树和森林!

提及树,想必大多数人第一反应都是二叉树以及二叉树的各类亲戚,包括红黑树、平衡二叉树等。可是其实除了二叉树外,普通的树结构在数据结构中也占据着很是重要的一部分。程序员

不只如此,所谓百川成海,白木成林。既然有了树结构,天然而然也会有相应的森林结构。所以,本文就将从普通的树结构出发,探讨并介绍一下树和森林的那些事。web


 1   树的定义

树实际上就是由许多个节点组成的集合,只不过每一个节点的的组成是根据树状结构进行划分。一颗普通的树结构能够经过如下图来定义。数组

仍是再来罗嗦一遍,树的结构就像是一颗倒挂的树,结点的组成是以层级往下。一棵树由若干子树构成,而子树又有更小的子树构成。微信

树的血缘关系

对于树中的某个结点,最多只和上一层的结点有直接的关系,而与其下一层的多个结点有直接关系。其上一层的结点称为双亲结点,下一层的结点称为孩子结点。全部位于树的最底部,没有孩子结点的结点被称之为叶子节点。具备相同双亲的结点互为兄弟节点数据结构

树的家族等级

树是一个你们族,等级十分森严。树中某个结点的子树个数称为该结点的度。因此叶子结点也就是度为0的结点。而度不为0的结点被称之为内部结点。每个结点都具备本身的层次,该层次由高往低递增,根结点为第一层,根的孩子结点为第二层,依次类推。一棵树最大的层数称之为树的高度(或深度)。编辑器


 2   树的存储结构

因为普通的树结构并不像二叉树那么规则,多是多叉树的组合,所以很难用常规的线性结构来存储。所以树结构的存储须要将树家族中的关系剥离出来进行存储,保存了每一个结点之间的关系,整个树结构也就能依次进行恢复。flex

这就比如家族中的族谱同样,记录的是咱们和双亲以及兄弟姐妹的关系。对于树而言,则根据存储关系的不一样,可分为双亲表示、孩子表示以及孩子兄弟表示三种存储方法。spa

双亲表示法

树的双亲表示,显然就是经过录每一个结点的双亲结点来存储整颗树的层次关系。这里经常使用的一种存储结构就是数组。在连续的地址中存储树的结点,同时将之与其双亲结点在数组中的序号进行对应,这样一来就可以保存全部结点的双亲信息。.net

双亲表示法直接存储的是结点的双亲位置(对应于数组的下标),所以在求某个结点的双亲结点以及祖先结点时很是方便。可是却没法直接得到该结点的孩子结点的位置。指针

若须要查找指定结点的孩子以及后代结点,须要遍历整个数组并进行屡次判断才行。

孩子表示法

树的双亲表示法的缺点显而易见,因此最直接的解决办法就是干脆存孩子结点算了。还别说,孩子表示法就是这样一种表示方法。可是相较于双亲结点的存储,存储孩子结点有一个须要考虑的问题,就是某个结点的双亲结点最多只有一个,可是其孩子结点可能有多个。若是每一个孩子结点都存储在数组里,这样的方式不是一个明智的选择,而且也没有必要。

因此在使用孩子表示法来存储树的结构时,常使用数组+链表的结构。这种结构是否是很常见,跟解决哈希冲突的链地址法有殊途同归之意。在这样的链式结构中,用指针指示出结点的每一个孩子,每一个孩子的位置经过链表依次相连,这样就十分方便与查找每一个结点的子孙。

只不过问题依旧,若要找出寻找某个结点的双亲则一样须要遍历全部链表。不过,既然双亲表示和孩子表示都有了,简单粗暴的合并一下不就能够相互补充,共同进退吗。

所谓的双亲孩子表示法,直接将双亲表示和孩子表示组合起来便可。这样便可知足双亲的查找,也能够知足孩子的查找。

孩子兄弟表示法

原本有了双亲孩子表示法就已经足够用来存储树中的数据信息了,为何还要来一个孩子兄弟法呢?其实否则,孩子兄弟表示法反而是一种颇有意思且颇有价值的表示方式。

在孩子兄弟表示法中,咱们约定只存储每一个结点的第一个孩子结点和下一个兄弟结点。不只如此,结点的存储是经过链表进行的。话说不太清,仍是直接看图吧。

看起来彷佛有些诡异的形状,每一个结点都做为链表的一个节点,经过两个指针分别指向第一个孩子结点和下一个兄弟结点。为了防止你们看不懂,我举个例子。拿结点B来讲,它的第一个孩子结点是E,而它的下一个兄弟是与它处于同一层级的C。所以结点B的两个指针分别指向了EC

孩子兄弟表示法这样看起来彷佛很鸡肋,可是假如咱们调整一下右边这个图,就能看出其中的蹊跷了。

看出来了吗,孩子兄弟表示法实际上就是将一颗普通的树转换成了二叉树的形式。因此说二叉树为何这么重要,由于万变不离其中呀。看到这,其实也透露出树和二叉树之间的转换关系,许多二叉树上的性质和操做也能够借此运用在普通的树结构中。


 3   树的遍历

学过二叉树的同窗想必应该对前序遍历、中序遍历、后序遍历、中序遍历烂熟于心了吧,不管是迭代仍是非迭代的写法,都是基础得不能再基础的东西了。而对于普通的树而言,因为每一个结点子树的个数并不必定,所以很差规定前、中、后序的顺序。

因此通常而言对于树的遍历方式有两种,根据根结点被遍历的前后可分为先根遍历和后根遍历。

树的先根遍历是先访问树的根节点,而后依次遍历根结点的各个子树。如此递推。将一颗普通树转换为对应的二叉树时(孩子兄弟表示法),其实就至关因而前序遍历

树的后根遍历就不用多说了吧,跟先根遍历相反,先访问根结点的各颗子树,再访问树根结点。而树的后根遍历就至关于转换后二叉树的中序遍历。不信的话你试试。

 4   树、森林和二叉树的相互转换

写到这,忽然发现好像忘记介绍森林是什么东西了。其实森林的概念很简单,就是不少颗树。对,就是这样。

树、森林和二叉树本质上都是相似的结构,所以相互之间能够进行转换。任意一个森林或者一棵树均可以对应表示为一颗二叉树,而任何一颗二叉树也可以对应到一个森林或一棵树上。

树转换为二叉树,咱们在前面已经介绍过,就是经过树的孩子兄弟表示法。经过孩子兄弟法进行表示时,每个树均可以用一颗惟一的二叉树来表示。可是转换过来的二叉树却有一个很是显著的特色。仔细观察。

很显然,这不是一颗平衡的二叉树。而且,根节点是没有右子树的,我敢确定的说。这是由于根节点是没有兄弟结点的,它只有孩子结点,因此在转换为二叉树以后,必定是没有右子树的

不过这样的缺陷能够在森林中进行弥补。因为森林中有不少棵树,所以能够将其它树做为右子树。具体的实现步骤,先将森林中的每一棵树转换为二叉树,再将第一颗树的根结点做为转换后的二叉树的根。第一棵树的左子树做为转换后二叉树根结点的左子树,第二棵树做为转换后二叉树的右子树。第三颗树做为转换后二叉树根结点的右子树的右子树。以此类推。

我们来举个例子。这里有一个由三颗树构成的森林。

将上面三棵树分辨转换二叉树是如下形式。


而后将绿色二叉树做为蓝色二叉树根节点的右子树,将黄色二叉树做为绿色二叉树根节点的右子树,就能够获得森林转换为二叉树的结果。

根据以上的规则,一样能够将一颗二叉树转换为树和森林。


 5   总结

在数据结构中,估计树和森林不算很热门的结构,甚至许多工做过不少年的老码农都未曾用过。写这篇文章的时候,我也在想树和森林到底在实际中有什么用,彷佛最重要的部分就是将一颗普通的树转换成二叉树来处理。可是我想这就是它的价值所在吧。

许多真实场景中,可能数据之间的关系并不能直接经过二叉树来表示和存储,一开始可能都须要经过多叉树或者各类畸形的树结构来定义关系。这样的树确定是不适用于快速的处理和访问的,所以每每须要将这些奇形怪状的树转换为规则的二叉树来进行进一步的处理。最终为了回归到具体的应用,也须要将二叉树从新分解为最初的树或者森林结构来得到应用意义。

总的来讲,存在便是真理。不怕用不到,就怕想不到。

 - end - 

扫码添加好友,见识见识这个在大厂深耕的业余程序员。

本文分享自微信公众号 - 业余码农(Amateur_coder)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索