我在接受采访时被要求编写一个函数,给定一棵树和树中的两个节点,将最近的祖先返回给这两个孩子。
这是我的代码:
two_paths = []
def get_closest_ancestor(path1, path2):
for i in xrange(min(len(path1), len(path2))):
if path1[i] != path2[i]:
return path1[i-1]
def find_two_paths(root, a, b, path = []):
global two_paths
newpath = path[:]
newpath.append(root)
if len(two_nodes) == 2:
return
if root == a:
two_paths.append(newpath)
if root == b:
two_paths.append(newpath)
for child in root.children:
find_two_paths(child, a, b, newpath)
if __name__ == "__main__":
find_two_paths(root, a, b)
print get_closest_ancestor(two_paths)
然后我被要求给出我刚写的算法的Big O Complexity。
所以我们有2个函数,find_two_paths
和get_closest_ancestor
。
我的第一个问题是,我们在这里作为一个输入。我们的Big O符号中的n
是什么?
我认为我们将树的深度视为n
find_two_paths
显然会执行DFS搜索,因此我认为我们已经具有指数时间复杂度。
我们可以使用它是树的事实,并说复杂性为O(b^n)
,其中b
是树的分支因子,n
是深度吗? / p>
我们可以说它是O(2^n)
吗?
我们可以忽略get_closest_ancestor
函数,因为它是O(n)
并且被find_two_paths
严重制服?
答案 0 :(得分:0)
这绝对不是O(2^n)
。它快得多。对于图形问题,通常将n
定义为树中的节点数。因此,让我们总结一下您的算法的作用:
对于第1步,DFS is O(n)
。您访问每个节点一次,以便您的性能与节点数成比例。这里需要注意的一件事是你实际上是短路(虽然使用了全局),因此你不考虑所有节点。当然,最糟糕的情况是a
和b
是我们访问的最后两个节点,因此DFS将是O(n)
。
对于第2步,最糟糕的情况是路径在最后一个节点(a
和b
是兄弟节点)之前是相同的。因此,最差的性能将与最长列表的长度成比例。现在,我们可以批判性地思考第1步,并推断出访问每个节点的O(n)总是大于或等于我们在步骤2中可以拥有的最长路径(因此整体O(n)
将占主导地位,这将是O(n)
时间,但让我们彻底。如果我们对树有一些保证(这是一个平衡的二叉树),我们可以说最长的路径是O(log n)
,因此比较两条路径将是最差的O(log n)
。但是我们没有这样的保证。我们可以更普遍地说,如果我们可以将图的分支因子(每个节点的平均子节点数)近似为b
,那么我们可以近似地说最长的路径将是O(log_b n)
。但对于一般树(最坏情况是链表),路径为O(n)长度。因此,步骤2也是最坏情况O(n)。
因此O(n)
的整体运行时间。
答案 1 :(得分:0)
树度量标准复杂度通常是节点数。缺乏准确回答,特别是关于树中节点的顺序。一个简单的通用解决方案是将node1从node1构造到root,然后将node2从node2构造到root,同时在每个步骤验证当前节点是否在path1中。当然,如果您的树是有序的,那么显然可以进行一些优化。
假设 n 节点的树具有精确度 d ,那么构造path1需要 log_d(n)步骤,最坏的情况下,构造path2也是在最坏的情况下需要 log_d(n)步骤,但是对于每个步骤,您需要搜索到 log_d(n)的path1。然后在最后它需要 log_d(n)^ 2 。