2010-10-20 108 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

相關問題