以O(1)空间复杂度按BFS方式打印二叉树

7
我在想是否有可能只使用O(1)空间打印二叉树的广度优先遍历结果?
难点在于需要使用额外的空间来记忆下一层需要遍历的节点,这个空间会随着n的增加而增加。
既然对时间没有任何限制,也许有一些效率低(就时间而言)但可以实现这一目标的方法?
您有什么想法吗?

@Ted 这是一个可能出现在面试中的问题。 - clwen
4个回答

4
这将取决于一些更精细的定义,例如边缘是否具有后向链接。如果是这样,那么很容易,因为您只需沿着树上的后向链接进行跟踪即可。否则,我想不到在不使用O(lg节点数)空间的情况下完成它的方法,因为您至少需要记住“上面”的节点。
更新
哦,等等,当然可以通过时间和空间的权衡在O(1)空间内完成。在您想要执行后向链接的每个位置,保存您的位置并执行BFS,跟踪最近的节点,直到找到您的节点。然后返回到最近访问的节点并继续。
问题是,这是O(1)空间但O(n ^ 2)时间。
另一个更新
假设我们已经到达节点ni,并且我们想要到达该节点的父节点,我们将称其为wlg nj。我们已经确定了特殊的根节点n0。
修改广度优先搜索算法,以便在遵循定向边(nx,ny)时存储传出或“传入”节点。因此,当您遵循(nx,ny)时,保存n_x。
当您再次从n0开始BFS时,您保证(假设它确实是一棵树)在某个时候,您将转换边(nj,ni)。在那一点上,您观察到您回到了ni。您已经存储了nj,因此您知道反向边是(ni,nj)。
因此,您可以使用仅两个额外单元的单个回溯,一个用于n0,另一个用于“保存”的节点。这是O(1)
我不太确定O(n ^ 2)-现在很晚了,今天很辛苦,所以我不想编写证明。我确定它是O((| N | + | E |)^ 2),其中| N |和| E |分别是顶点和边的集合的大小。

我想不涉及后向链接,否则这个问题会很平凡。 - clwen
我对下面提到的IDDFS不熟悉,但如果它像广告中所说的那样工作,那就更好了,时间复杂度为O(lg lg #nodes)。 - Charlie Martin
1
@CharlieMartin:您能详细说明第二个示例吗?如果您需要为每个反向链接使用BFS,并且对于该BFS中的每个反向链接也使用BFS,那么似乎您需要更多的O(1)空间? - vgru
1
我仍然不明白这应该如何工作。假设您在某一点处的层次结构中深入了20个级别,并且您需要回溯到第19个级别。如果您只有一个节点的传入节点,那么如何“重新开始BFS”并“在某个时刻转换边缘”?在到达此处之前,您需要遍历所有其他节点吗?例如,请考虑此维基百科图像:您当前正在节点“e”。您的算法如何知道要打印的下一个节点是“f”? - vgru
你知道根节点在哪里吧?如果不知道,改一下算法,在开始时保存根节点;这只会添加恒定的空间,所以仍然是O(1)。现在,每次你想要“回溯”,都从根节点开始。你保证最终会再次找到你所在的节点(否则你怎么第一次到达那里的呢?)。 - Charlie Martin
显示剩余2条评论

2
一个有趣的特殊情况是堆。
来自 heapq 文档
堆是一种二叉树,其中每个父节点的值都小于或等于其任何子节点的值。此实现使用数组,其中对于所有 k,从零开始计数,heap[k] <= heap[2*k+1]heap[k] <= heap[2*k+2]。为了比较,认为不存在的元素是无限的。堆的有趣属性是它的最小元素总是根 heap[0]。[由 François Pinard 解释]
如何在内存中表示树(数组的索引):
                               0

              1                                 2

      3               4                5               6

  7       8       9       10      11      12      13      14

15 16   17 18   19 20   21 22   23 24   25 26   27 28   29 30

在这种情况下,数组中的节点已经按照广度优先顺序存储。
for value in the_heap:
    print(value)

在空间复杂度上是O(1)。


1

我知道这严格来说不是问题的答案,但是可以通过递归迭代加深深度优先搜索(IDDFS)以O(d)空间访问树的节点,其中d是树的深度。当然,堆栈需要占用一定的空间。对于平衡树,d = O(lg n),其中n是节点数。老实说,如果没有@Charlie Martin建议的反向链接,我真的看不出如何在恒定的空间中完成它。


-1

实现一个递归方法来获取给定层的树的所有节点是很容易的。因此,我们可以计算树的高度并获取每个级别的所有节点。这是树的层次遍历。但是,时间复杂度是O(n^2)。以下是Java实现(source)。

class Node 
{ 
    int data; 
    Node left, right; 
    public Node(int item) 
    { 
        data = item; 
        left = right = null; 
    } 
} 

class BinaryTree 
{ 
    Node root; 

    public BinaryTree() 
    { 
        root = null; 
    } 

    void PrintLevelOrder() 
    { 
        int h = height(root); 
        int i; 
        for (i=1; i<=h; i++) 
            printGivenLevel(root, i); 
    } 

    int Height(Node root) 
    { 
        if (root == null) 
           return 0; 
        else
        { 
            int lheight = height(root.left); 
            int rheight = height(root.right); 

        } 
    } 

    void PrintGivenLevel (Node root ,int level) 
    { 
        if (root == null) 
            return; 
        if (level == 1) 
            System.out.print(root.data + " "); 
        else if (level > 1) 
        { 
            printGivenLevel(root.left, level-1); 
            printGivenLevel(root.right, level-1); 
        } 
    }
}

这使用O(h)空间,其中h是树的高度。递归是使用调用堆栈实现的,这被视为辅助空间。 - kaya3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接