2010-10-20 6 views
5

Hallo alle: Ich lese den Algorithmus unten, um den niedrigsten gemeinsamen Vorfahren von zwei Knoten in einem binären Suchbaum zu finden.Warum die Raumkomplexität dieses Algorithmus ist 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); 
}  

anzumerken, dass vorstehende Funktion wird davon ausgegangen, dass n1 kleiner als n2 ist. Zeitkomplexität: O (n) Speicherkomplexität: O (1)

dieser Algorithmus ist rekursiv, ich weiß, dass, wenn ein rekursive Funktionsaufruf aufgerufen wird, wird die Funktionsargumente und andere damit zusammenhängende Register auf den Stapel geschoben werden, so zusätzlicher Raum wird benötigt, andererseits hängt die rekursive Tiefe mit der Größe oder Höhe des Baumes zusammen, n, macht es mehr Sinn, O (n) zu sein?

Danke für irgendwelche Erklärungen hier!

+0

der Stapel würde typischerweise (wenn der Baum in etwa ausgeglichen ist) nicht überschreiten _O (log n) _ Raum. – Svante

Antwort

4

Obwohl Sie zu Recht sagen, dass diese rekursive Implementierung des Algorithmus O (n) space wegen des benötigten Stack-Speicherplatzes erfordert, wird nur die Tail-Rekursion verwendet, was bedeutet, dass sie mit einem O (1) -Raum wieder verwendet werden kann Loop:

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; 
    } 
} 

(Beachten Sie, dass else hinzugefügt werden muss, in der Semantik unverändert zu halten.)

+0

Tut dies der Compiler für mich? Eine kleine Chance? Außerdem ist das, was du notiert hast, in Ordnung, aber das Auslassen von sonst ist auch nicht falsch, oder? – Tracy

+0

Die beste Wette ist, in der Dokumentation für Ihren Compiler zu schauen - glauben Sie mir, wenn es Tail-Call-Optimierung hat, wird es es stolz erwähnen. Eine schnelle Google schlägt vor, gcc hat es seit 3.4. Re 'else': Es ist notwendig, weil sonst das finale 'if' das * new *' root' betrachtet, welches das falsche Verhalten sein kann (zB könnte es NULL sein, was einen Absturz verursacht, wenn 'root-> data' ist zugegriffen). –

10

Dieser Algorithmus beinhaltet Tail-Rekursion. Im Rahmen Ihrer Frage kann der Stack-Frame des Aufrufers vom Angerufenen wiederverwendet werden. Mit anderen Worten, die gesamte verschachtelte Folge von Funktionsaufrufen wird das Ergebnis wie eine Eimerkette weiterleiten. Folglich wird tatsächlich nur ein Stapelrahmen benötigt.

Für mehr lesen, siehe Tail Call in der Wikipedia.

+0

+1, aber beachte, dass die meisten C- und C++ - Compiler nur sehr begrenzte oder gar keine Tail-Call-Optimierung durchführen und diesen Fall nicht unbedingt optimieren. –

+0

Großer Artikel Tail Call Optimierung! –

+0

so ist es wegen der Schwanz Rekursion, vielen Dank! – Tracy

Verwandte Themen