是否可以遍历一棵树而无需递归,堆栈或队列,而只需几个指针?


15

半年前,我坐在一个数据结构课上,如果有人可以在不使用递归,堆栈,队列等(或任何其他类似数据结构)而仅使用几个指针的情况下遍历一棵树,教授将提供额外的荣誉。我想出了一个我认为是对这个问题的明显答案,最终被教授接受了。我和同一系的另一位教授坐在一门离散数学课上,他断言没有递归,堆栈,队列等就不可能遍历一棵树,而且我的解决方案无效。

那么,有可能还是不可能?为什么或者为什么不?

编辑:为澄清起见,我在具有三个元素的二叉树上实现了这一点-每个节点上存储的数据和指向两个孩子的指针。我的解决方案仅需少量更改即可扩展到n元树。

我的数据结构老师没有对树的变异施加任何限制,实际上,后来我发现他自己的解决方案是使用子指针在回落的过程中指向树的后方。我的离散数学教授说,根据树的数学定义,树的任何突变都意味着它不再是树,他的定义还将排除指向父代的任何指针,这与我上面解决它的情况相符。


3
您需要指定约束。我可以变异这棵树吗?树如何表示?(例如,每个节点是否都有指向其父节点的父节点指针?)答案将取决于特定的约束;例如,没有指定这些约束,这不是一个适当的问题。
DW

2
我猜想教授们真正想表达的矛盾是“有额外的空格”。但是,无论如何,您的解决方案是什么?Ø1个
拉斐尔

Answers:


17

在此领域中,许多研究都是以“廉价”遍历树木和垃圾收集上下文中的一般列表结构的方法为主题的。

线程二叉树是二叉树的适应表示,其中一些零指针用于链接到树中的后继节点。这些额外的信息可用于遍历没有堆栈的树。但是,每个节点都需要一个额外的位来区分线程和子指针。维基百科:Tree_traversal

据我所知,可以使用线程方法遍历使用指针以通常方式实现的二叉树(每个节点左右指针),该方法属于Morris。NIL指针被临时重用以将路径穿入根目录。聪明的部分是,在遍历期间,可以使用临时线程链接在树中形成循环的方式来区分原始边缘。

好的部分:没有额外的数据结构。不良部分:略有作弊,堆栈以巧妙的方式位于树。非常聪明。

P. Mateti和R. Manghirmalani中显示了隐藏堆栈的证明:重新考虑了Morris的树遍历算法DOI:10.1016 / 0167-6423(88)90063-9

JM Morris:简单而便宜地遍历二叉树。IPL 9(1979)197-200 DOI:10.1016 / 0020-0190(79)90068-1

然后还有Lindstrom扫描。此方法“旋转”每个节点(父级和两个子级)中涉及的三个指针。如果要执行任何体面的预排序或后排序算法,则每个节点需要额外的位。如果您只想访问所有节点(3次,但是您不知道要执行哪一次访问),那么就可以做到这一点。

G. Lindstrom:扫描没有堆栈或标签位的列表结构。IPL 2(1973)47-51。DOI:10.1016 / 0020-0190(73)90012-4

也许最直接的方法是Robson的方法。在这里,经典算法所需的堆栈穿过叶子。

JM Robson:一种改进的算法,用于遍历没有辅助堆栈IPL 1(1973)149-152的二叉树。10.1016 / 0020-0190(73)90018-5

IPL =信息处理信函


我也喜欢这种解决方案,尽管我在计算机科学课程的第一年没有什么想得到的。是的,可能是按照我教授的规则作弊。
NL-向Monica致谢,

2
您可以为策略提供链接/参考吗?
拉斐尔

1
这种方法真正的缺点是您一次最多只能进行一次遍历。
吉尔(Gilles)'所以

6

v


这类似于提出该问题的数据结构教授用来解决该问题的解决方案。这位离散数学教授反对说,如果有指向父母的指针,那么“这已成为一幅图而不是一棵树”。
NL-向Monica

@NathanLiddle:这将取决于所使用的树定义(您未提供)。在“现实世界”中,Yuval的树表示是合理的,即使图论会说他定义的东西不是树。
拉斐尔

@Raphael是的,它符合原始教授的要求,因此对我来说是一个可以接受的答案。
NL-向Monica致谢,

0

我的解决方案是使用嵌套的for循环对树进行蛮横优先遍历。这无论如何都不是有效的,确实像树这样的递归数据结构正在请求递归遍历,但是问题是否在于是否可以有效地遍历一棵树呢?

Pseudocode:
root = pointer root 
depth = integer 0
finished = bool false
//If we n-ary tree also track how many children have been found 
//on the node with the most children for the purposes of this psuedocode 
//we'll assume a binary tree and insert a magic number of 2 so that we 
//can use bitwise operators instead of integer division 
while(!finished)
    ++depth
    treePosition = pointer root
    finished = true;
    for i := 0..2**depth
        for j := 0..depth
            if (i & j) //bitwise operator explained below
                // if right child doesn't exist break the loop
                treePosition = treePosition.rightChild
            else
                // if left child doesn't exist break the loop
                treePosition = treePosition.leftChild
        if j has any children
            finished = false
            do anything else you want when visiting the node

如您所见,对于前几个级别,它看起来像这样,伪代码中的按位运算符只是确定二进制树的左转或右转:

2**1       0               1
2**2   00      01      10      11
2**3 000 001 010 011 100 101 110 111

对于n元,您将采用i%(maxChildren ** j)/ j来确定0和maxChildren之间的路径。

在n元的每个节点上,您还需要检查子代数是否大于maxChildren,并进行适当更新。


如果您想使用的二进制数更多,则需要用一个变数来替换幻数2,该变数将递增以匹配它看到的最大子代数,而不是按位运算符,您需要除以相同的变量以得出您所在的树的深度的力量。
NL-向Monica

Ø1个ØlgñØ1个ØñØ1个例如,如果depth超出宽度int
DW

DW,提出问题的教授并没有对该问题施加限制,而令我困扰的是,我与离散数学教授的讨论之所以如此让我感到困扰的是,他从来没有承认甚至没有递归,堆栈,或排队,不计成本。我的解决方案演示的唯一的事情是,它是可以做任何事情迭代可以完成递归,即使你删除选项,栈,队列等
NL -道歉,莫妮卡

说没有O(1)额外空间是无法解决的,而宣称没有递归,堆栈或队列就无法解决问题则是另一回事。实际上,在看完我的代码后,离散数学教授仍然不会承认这一点,因为他在第一个for循环中说“ i”代替了队列。头脑硬怎么了?
NL-向Monica致谢,2013年

1
idepthdepthΘñΘñiØ1个
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.