Algorithm 如何在没有额外内存的情况下在O(n)时间内遍历二叉树

Algorithm 如何在没有额外内存的情况下在O(n)时间内遍历二叉树,algorithm,data-structures,binary-tree,Algorithm,Data Structures,Binary Tree,给定一个具有整数、左指针和右指针的二叉树,如何在O(n)时间和O(1)额外内存(无堆栈/队列/递归)内遍历该树 给出了一个非O(n)总时间的解决方案,该方案将当前路径编码为整数(因此适用于深度有限的树) 我在寻找经典解 (扰流板) 对子节点中每个节点的父节点进行编码。任何好的算法书都会有这种算法,例如在Knuth中(TAOCP I.2.3.1遍历二叉树,练习21)。但是,由于此算法会就地修改树,因此在多线程环境中必须格外小心 您也可以使用线程树(参见Knuth)。这家伙的算法很有趣,但需要指出的

给定一个具有整数、左指针和右指针的二叉树,如何在O(n)时间和O(1)额外内存(无堆栈/队列/递归)内遍历该树

给出了一个非O(n)总时间的解决方案,该方案将当前路径编码为整数(因此适用于深度有限的树)

我在寻找经典解

(扰流板)


对子节点中每个节点的父节点进行编码。

任何好的算法书都会有这种算法,例如在Knuth中(TAOCP I.2.3.1遍历二叉树,练习21)。但是,由于此算法会就地修改树,因此在多线程环境中必须格外小心


您也可以使用线程树(参见Knuth)。

这家伙的算法很有趣,但需要指出的是,它确实需要O(logn)额外的空间位来遍历具有n个节点的二叉树。空间需求必须以位来衡量,而不是以字节来衡量——通常在使用大Oh符号时,它们会折叠成同一个东西,但像这样的情况说明了区分的重要性

要了解这一点,请询问如何使用单个整数存储(在32位平台上)遍历包含2^32-1个以上节点的树。

使用O(1)存储来记住“上次访问的节点”指针。您可以将其初始化为0或某个未知值

要遍历树,请从根节点开始。 查看节点的两个子节点。如果“上次访问的节点”等于右节点,则转到父节点。如果“上次访问”等于左侧节点,则转到右侧节点。否则,请转到左侧节点

重复上述步骤,直到完成整棵树的行走。 唯一真正聪明的方法是使用一个变量来记住你从哪里来,从而决定下一步要去哪里。这使得遍历具有确定性


你最终会采取O(n)步。您将访问每个中间节点三次,每个叶一次,因此您仍然是O(N)。存储是O(1)。

主要思想类似于列表反转算法,有一个非常难看的技巧性破解(从理论上看,可能是一个骗局),其基础是指针是(在人类目前已知的所有语言中)0模式4作为整数

这个想法是,你可以翻转树下路径上的指针指向上方。问题是,当你 回溯你需要知道是左指向上还是右指向上;在这一点上,我们使用黑客

伪代码如下:

current = root->left
next = current
while (current != null) {
  next = current->left
  current->left = static_cast<int>(prev) + 1 // ugly hack.
  current = next
}
status = done
while (current != root or status != done) {
  if (status = done) {
     if (static_cast<u32>(current->left) %4 = 1) {
         next = static_cast<u32>(current->left) -1
         current->left = prev
         status = middle
     }
     else {
         next = current->right
         current->right = prev
         status = done
     }
     prev = current
     current = next
  }
  else if (status == left) {
     if (current->left) {
       prev = current->left
       current->left = static_cast<u32>(next) +1
       next = current
     }
     else
       status = middle
  }
  else if (status == right) {
     if (current->right) {
        prev = current->right;
        current ->right = next;
        next = current
     }
     else
       status = done
  }
  else {// status == middle
     work_on_node(current)
     status = right
  }
}
current=root->left
下一个=当前
while(当前!=null){
下一步=当前->左
当前->左=静态施法(上一次)+1//丑陋的黑客。
当前=下一个
}
状态=完成
while(当前!=根目录或状态!=完成){
如果(状态=完成){
如果(静态施法(当前->左)%4=1){
下一步=静态施法(当前->左)-1
当前->左=上一
地位=中等
}
否则{
下一步=当前->右
当前->右侧=上一个
状态=完成
}
prev=当前
当前=下一个
}
else if(状态==左){
如果(当前->左){
上一个=当前->左
当前->左=静态(下一步)+1
下一个=当前
}
其他的
地位=中等
}
else if(状态==右侧){
如果(当前->右侧){
prev=当前->右侧;
当前->右=下一步;
下一个=当前
}
其他的
状态=完成
}
else{//status==中间
_节点上的工作_(当前)
状态=正确
}
}

这里是另一个解决方案


但我想知道,在像Java这样没有真正意义上的指针的语言中,是否有办法做到这一点。

每个节点都有父指针吗?递归到底是如何被视为“额外内存”的?无限递归将使用堆栈空间,通常超过O(1)。当然,没有父指针,节点只包含左和右。为什么不使用O(logn)额外内存或O(logn)额外时间如此重要?差别是微不足道的。呃,没关系,他声称是O(n),但他正在重访父母。这是:(我认为)@jeremy,不,这也不起作用,海报正在向节点写入数据。我在这里从TAOCP输入了算法,它也将数据写入节点,但之后将节点恢复到以前的状态。没有人说树是平衡的。该树可能是一个由200个节点组成的字符串,算法将在该树上失败。@ripper234:好的。。。我只是想指出,即使在算法的最佳(完美平衡)情况下,它仍然需要超过O(1)的空间。是的,树越不平衡,你就越接近O(n)空间,你就越快地耗尽单个整数中的位。你如何步进到父节点:你只有左指针和右指针?谁说“一个变量”?如果您需要使用10个变量,它仍然是O(1)存储空间这不管用。在访问其他节点之前,必须将“上次访问的节点”存储在某个位置,以便在返回时知道上次访问的是什么。所以,也许有点滥用术语,在Java中,除了指针之外什么都没有。