使用递归提高DFS的时间复杂度,使每个节点仅与其后代

时间:2015-05-31 15:44:51

标签: algorithm python-3.x tree language-agnostic depth-first-search

问题

有一个完美平衡的m-ary树,n级深。每个内部节点都有m个子节点。根据说深度为0,叶子节点被称为n级,所以每个叶子节点都有n个祖先。因此,树中的节点总数为:

T = 1 + m^2 + ... + m^n
  = (m^(n+1) - 1) / (m - 1)

以下是m = 3且n = 2的示例。

            a              (depth 0)
   _________|________
   |        |        |
   b        c        d     (depth 1)
___|___  ___|___  ___|___
|  |  |  |  |  |  |  |  |
e  f  g  h  i  j  k  l  m  (depth 2)

我正在编写深度优先搜索功能,以最深节点第一个和最左边节点第一种方式遍历整个树,并将每个节点的值插入到输出列表中。

我用两种不同的方式编写了这个函数,并想比较两个函数的时间复杂度。

虽然这个问题与语言无关,但我使用下面的Python代码来展示我的函数,因为Python代码看起来几乎就像伪代码。

解决方案

第一个功能是dfs1。它接受根节点为node参数,空输出列表为output参数。函数递归地下降到树中,访问每个节点并将节点的值附加到输出列表。

def dfs1(node, output):
    """Visit each node (DFS) and place its value in output list."""
    output.append(node.value)
    for child_node in node.children:
        dfs1(child_node, output)

第二个功能是dfs2。它接受根节点作为node参数,但不接受任何列表参数。该函数递归地下降到树中。在每个递归级别,在访问每个节点时,它会创建一个列表,其中包含当前节点及其所有后代的值,并将此列表返回给调用者。

def dfs2(node):
    """Visit nodes (DFS) and return list of values of visited nodes."""
    output = [node.value]
    for child_node in node.children:
        for s in dfs2(child_node):
            output.append(s)
    return output

分析

有两个变量可以定义问题的大小。

  • m - 每个子节点拥有的子节点数。
  • n - 每个叶节点具有的祖先数(树的高度)。

dfs1中,访问每个节点时花费了O(1)时间,因此访问所有节点所花费的总时间为

O(1 + m + m^2 + ... + m^n).

我不打算进一步简化这个表达。

dfs2中,访问每个节点所花费的时间与从该节点可到达的所有叶节点成正比。换句话说,在深度d访问节点时花费的时间是O(m ^(n-d))。因此,访问所有节点的总花费时间是

1 * O(m^n) + m * O(m^(n - 1)) + m^2 * O(m^(n - 2)) + ... + m^n * O(1)
= (n + 1) * O(m^n)

问题

是否可以以时间复杂度为

的方式编写dfs2
O(1 + m + m^2 + ... + m^n)

不改变算法的本质,即每个节点只为自己及其所有后代创建一个输出列表,而不必打扰可能具有其祖先值的列表?

完整的工作代码供参考

这是一个完整的Python代码,演示了上述功能。

class Node:
    def __init__(self, value):
        """Initialize current node with a value."""
        self.value = value
        self.children = []

    def add(self, node):
        """Add a new node as a child to current node."""
        self.children.append(node)

def make_tree():
    """Create a perfectly balanced m-ary tree with depth n.

    (m = 3 and n = 2)

                1              (depth 0)
       _________|________
       |        |        |
       2        3        4     (depth 1)
    ___|___  ___|___  ___|___
    |  |  |  |  |  |  |  |  |
    5  6  7  8  9 10 11 12 13  (depth 2)
    """
    # Create the nodes
    a = Node( 1);
    b = Node( 2); c = Node( 3); d = Node( 4)
    e = Node( 5); f = Node( 6); g = Node( 7);
    h = Node( 8); i = Node( 9); j = Node(10);
    k = Node(11); l = Node(12); m = Node(13)

    # Create the tree out of the nodes
    a.add(b); a.add(c); a.add(d)
    b.add(e); b.add(f); b.add(g)
    c.add(h); c.add(i); c.add(j)
    d.add(k); d.add(l); d.add(m)

    # Return the root node
    return a

def dfs1(node, output):
    """Visit each node (DFS) and place its value in output list."""
    output.append(node.value)
    for child_node in node.children:
        dfs1(child_node, output)

def dfs2(node):
    """Visit nodes (DFS) and return list of values of visited nodes."""
    output = [node.value]
    for child_node in node.children:
        for s in dfs2(child_node):
            output.append(s)
    return output

a = make_tree()

output = []
dfs1(a, output)
print(output)

output = dfs2(a)
print(output)

dfs1dfs2函数都会产生相同的输出。

['a', 'b', 'e', 'f', 'g', 'c', 'h', 'i', 'j', 'd', 'k', 'l', 'm']
['a', 'b', 'e', 'f', 'g', 'c', 'h', 'i', 'j', 'd', 'k', 'l', 'm']

1 个答案:

答案 0 :(得分:0)

如果在dfs1输出列表中通过引用传递,则ds1的复杂度为O(总节点)。
然而,在dfs2输出列表中返回并附加到父输出列表,从而为每个返回获取O(列表大小)。因此增加整体复杂性。如果输出列表的追加和返回都需要恒定时间,则可以避免此开销。
如果您的输出列表是“双端链接列表”,则可以执行此操作。因此,您可以返回输出列表的引用,而不是追加您可以连接两个双端链表(即O(1))。