2010-10-20 89 views
5

大家好:我读了下面的算法,找到二叉搜索树中两个节点的最小公共祖先。为什么这个算法的空间复杂度是O(1)

/* A binary tree node has data, pointer to left child 
    and a pointer to right child */ 
struct node 
{ 
    int data; 
    struct node* left; 
    struct node* right; 
}; 

struct node* newNode(int); 

/* Function to find least comman ancestor of n1 and n2 */ 
int leastCommanAncestor(struct node* root, int n1, int n2) 
{ 
/* If we have reached a leaf node then LCA doesn't exist 
If root->data is equal to any of the inputs then input is 
not valid. For example 20, 22 in the given figure */ 
if(root == NULL || root->data == n1 || root->data == n2) 
return -1; 

/* If any of the input nodes is child of the current node 
we have reached the LCA. For example, in the above figure 
if we want to calculate LCA of 12 and 14, recursion should 
terminate when we reach 8*/ 
if((root->right != NULL) && 
    (root->right->data == n1 || root->right->data == n2)) 
    return root->data; 
if((root->left != NULL) && 
(root->left->data == n1 || root->left->data == n2)) 
return root->data; 

if(root->data > n1 && root->data < n2) 
    return root->data; 
if(root->data > n1 && root->data > n2) 
    return leastCommanAncestor(root->left, n1, n2); 
if(root->data < n1 && root->data < n2) 
    return leastCommanAncestor(root->right, n1, n2); 
}  

需要注意的是上述函数假定N1比N2小。 时间复杂度:O(n)的空间复杂度:O(1)

这个算法是递归的,我知道,调用递归函数调用时,函数参数和其他相关的寄存器被压入堆栈,所以另一方面,需要额外的空间,另一方面,递归深度与树的大小或高度有关,比如n,它是否更有意义是O(n)?

感谢您在这里的任何解释!

+0

堆栈通常(当树是大体平衡)不超过_O(log n)的_空间。 – Svante 2010-10-20 16:10:04

回答

4

虽然你说对于算法的递归实现需要O(n)空间是因为需要的堆栈空间,但它只使用尾递归,这意味着它可以重新实现以使用O(1)空间循环:

int leastCommanAncestor(struct node* root, int n1, int n2) 
    while (1) 
    { 
    /* If we have reached a leaf node then LCA doesn't exist 
    If root->data is equal to any of the inputs then input is 
    not valid. For example 20, 22 in the given figure */ 
    if(root == NULL || root->data == n1 || root->data == n2) 
    return -1; 

    /* If any of the input nodes is child of the current node 
    we have reached the LCA. For example, in the above figure 
    if we want to calculate LCA of 12 and 14, recursion should 
    terminate when we reach 8*/ 
    if((root->right != NULL) && 
     (root->right->data == n1 || root->right->data == n2)) 
     return root->data; 
    if((root->left != NULL) && 
    (root->left->data == n1 || root->left->data == n2)) 
    return root->data; 

    if(root->data > n1 && root->data < n2) 
     return root->data; 
    if(root->data > n1 && root->data > n2) 
     root = root->left; 
    else if(root->data < n1 && root->data < n2) 
     root = root->right; 
    } 
} 

(注:else已被添加在保持语义不变。)

+0

编译器是否为我做这件事?任何微小的机会?另外,你注意到的其他东西没问题,但是省略其他东西也没有错,对吗? – Tracy 2010-10-21 02:34:55

+0

您最好的选择是查看编译器的文档 - 相信我,如果它有tail-call优化,它会自豪地提及它。快速谷歌表示gcc自3.4以来已经有了它。 Re'else':这是必要的,因为否则最后的'if'会查看* new *'root',这可能是错误的行为(例如,它可能是NULL,导致'root-> data'访问)。 – 2010-10-22 03:02:42

10

该算法涉及尾递归。在你的问题的上下文中,调用者的堆栈帧可以被调用者重用。换句话说,函数调用的所有嵌套顺序都是将结果传递给bucket bucket。因此,实际上只需要一个堆栈帧。

欲了解更多信息,请参阅维基百科的Tail Call

+0

+1,但请注意,大多数C和C++编译器只执行非常有限的尾部调用优化,或者根本没有执行优化,并且不一定会优化这种特定情况。 – 2010-10-20 15:31:05

+0

伟大的文章尾巴呼叫优化! – 2010-10-20 15:43:14

+0

所以它是由于尾递归,非常感谢! – Tracy 2010-10-21 02:35:50

相关问题