有一个完美平衡的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
有两个变量可以定义问题的大小。
在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)
dfs1
和dfs2
函数都会产生相同的输出。
['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']
答案 0 :(得分:0)
如果在dfs1输出列表中通过引用传递,则ds1的复杂度为O(总节点)。
然而,在dfs2输出列表中返回并附加到父输出列表,从而为每个返回获取O(列表大小)。因此增加整体复杂性。如果输出列表的追加和返回都需要恒定时间,则可以避免此开销。
如果您的输出列表是“双端链接列表”,则可以执行此操作。因此,您可以返回输出列表的引用,而不是追加您可以连接两个双端链表(即O(1))。