继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

线索二叉树之初步剖析(献给那些想形象思考二叉树遍历过程的人)

慕桂英3389331
关注TA
已关注
手记 339
粉丝 43
获赞 187

   对于二叉树的遍历通常习惯采用递归的方法,当树的规模很大的时候,递归的深度就会很深,这就导致了对空间的浪费。在此,我们先不讨论二叉树遍历的本质,以及递归的详细过程。我先先来研究一下,二叉树本身:

图1  二叉树

图1所示为一个二叉树的结构,我们注意结点的特征。结点包含了三个数据:存储值,指向左子节点的左指针,以及指向右子节点的右指针。

我们考虑在计算机中的实现:对于每一个节点,我们都需要分配相同大小的空间。能够指向左右子节点的指针,是有效的,因为我们能够通过这些指针访问到下一个左、右子节点。但一棵树中,还存在着空指针,尤其是叶子结点。这些空指针没有被有效的利用,严格意义上讲,就是一种浪费。很容易发现,对于一个含有n个结点的二叉树,实际上有2n个指针域(每个结点有左右两个指针)。现在思考有几个指针得到了利用,多少个没有得到利用。除了头结点没有被指针指向(假设先不考虑头指针),其余的n-1个节点都被一个指针指向。因此这2n个指针中,有n-1个指针得到了利用,则言下之意,还剩下(2n-(n-1))=n+1个指针时空指针域。那么这些没有被用到的指针可以被利用吗?

答案是肯定的,这些空指针域可以被利用,而且很好用。

我们先来回顾二叉树的遍历过程,以图1所示二叉树为例。考虑它的中序遍历:从根节点A开始,先找它的左子结点,是B;再看B,B也有左子结点,是D;D还有左子结点,是H;再看H,H没有左子结点了。所以中序遍历第一个元素为H,再看H有没有右子结点,没有,因此H以及H的后续结点(都为空)访问完毕,此时我们来访问它的前序结点,是D。..........直到遍历所有结点。

 在此,我们思考这样一个问题:我们是如何在访问到了H,回头找到D的,进一步找到B的.....。对,我们的核心机制在栈,是通过弹栈的方式。我们找到了后续访问的元素。且先不谈论栈这种机制带来的巨大内存消耗,我们思考这样一个问题:假如我们知道了D的位置(仅仅是知道了D的位置,而不是知道了所有元素的位置),我们想知道树中,访问完了D,与D近邻的下面一个元素是谁???

如果我们没有更为优良的机制,我们的做法只能是:做一遍二叉树遍历(对,就是通过栈机制),遍历至少D之前所有的元素和接下来一个元素,我们才知道D接下来是谁?如果我们想访问C接下来的一个元素是谁呢?,对不起,只能遗憾的告诉你,我们要他妈遍历整个二叉树了。说到了这里,你对这个查找过程是不是看起来很眼熟??似曾相识???对,没错,这他妈就是单向链表的查找,时间复杂度为O(n)的单向链表查找!!!!(是不是恍然大悟,现在你明白了普通二叉树遍历的本质是什么了吧!!!)。

你可能觉得这也太蠢了吧,找一个元素我要遍历整颗树,没错,我也是这么想的,这是真的蠢。。。。那么有没有某种方式,使得查找不要这么蠢呢?有,这就是线索二叉树。

鉴于我画图本领不强,我就不就图1所示二叉树进行线索二叉树的详细讲解了,对此,来个简单的二叉树结构

比如:

图2  一个较为简单的二叉树

上述图2是一个较为简单的二叉树,在上文中我们已经提到过,对于这样一个普通的二叉树,其遍历过程更像是一个单向链表的实现。我们对这个二叉树进行中序遍历,得到结果为:DBAECF。

正如上述所说,我们可以采用栈的方式来实现,但是很明显,栈的实现在空间资源消耗,以及效率上都不高。

我们再回到上述那个问题,假如我们知道了D,想知道如何访问B,按照我们使用栈的方式,OK,你只能再遍历B以前所有的元素。这种方式真的沙雕。假如我们在第一次遍历时,使得D的空指针指向B(对,就是那个没用到的指针,这个时候用到了)。于是,上述结构变成了这样一种结构:

 图3  线索二叉树中序遍历路径

上述是一个线索二叉树的构件图。来看看这个图,我们在第一次遍历这个图的时候,假如遍历到了D,继续遍历B,我们就将D指向B,同样的B指向了A,这样我们就知道每个元素的邻近元素是谁。此时有没有发现,这个线索二叉树的遍历过程,很像一个东西的构建?是什么?对,就是双向链表的建立!!!!这就是线索二叉树遍历的本质:构建了一个类似双向链表的结构。使得我们对结点的访问效率更高!!

作者:少年π 

原文出处:https://www.cnblogs.com/shaonianpi/p/10460408.html  

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP