Lowest Common Ancestor
March 11, 2011
We begin with the minimal code needed to insert elements in a binary tree:
(define (tree k l r) (vector k l r))
(define (key t) (vector-ref t 0))
(define (lkid t) (vector-ref t 1))
(define (rkid t) (vector-ref t 2))
(define nil (vector 'nil 'nil 'nil))
(define (nil? t) (eq? t nil))
(define (lookup lt? t k)
(cond ((nil? t) #f)
((lt? k (key t)) (lookup lt? (lkid t) k))
((lt? (key t) k) (lookup lt? (rkid t) k))
(else #t)))
(define (insert lt? t k)
(cond ((nil? t) (tree k nil nil))
((lt? k (key t)) (tree (key t) (insert lt? (lkid t) k) (rkid t)))
((lt? (key t) k) (tree (key t) (lkid t) (insert lt? (rkid t) k)))
(else (tree k (lkid t) (rkid t)))))
Given that, the tree shown on the previous page can be built with the following statements:
(define t nil)
(set! t (insert < t 8))
(set! t (insert < t 3))
(set! t (insert < t 10))
(set! t (insert < t 1))
(set! t (insert < t 6))
(set! t (insert < t 14))
(set! t (insert < t 4))
(set! t (insert < t 7))
(set! t (insert < t 13))
The lowest common ancestor is the first node on which the two desired elements are on opposite sides; if both elements are less than the current key, descend the left sub-tree and try again, if both elements are greater than the current key, descend the right sub-tree and try again, otherwise the current node is the desired result. The only trick is to branch the right way on equality; that long first condition is just lo ≤ (key t) ≤ hi, but since we only have lt?
it looks odd:
(define (lca lt? t lo hi)
(cond ((and (not (lt? (key t) lo))
(not (lt? hi (key t)))) (key t))
((lt? (key t) lo) (lca lt? (rkid t) lo hi))
(else (lca lt? (lkid t) lo hi))))
Here are some tests:
(display (lca < t 4 7)) (newline) ; 6
(display (lca < t 4 10)) (newline) ; 8
(display (lca < t 1 4)) (newline) ; 3
(display (lca < t 1 3)) (newline) ; 3
(display (lca < t 3 6)) (newline) ; 3
You can run the code at http://programmingpraxis.codepad.org/h8u1RuTR.
My Haskell solution (see http://bonsaicode.wordpress.com/2011/03/11/programming-praxis-lowest-common-ancestor/ for a version with comments):
@Remco: how does that work in a binary tree that is not a binary search tree?
@ProgrammingPraxis: did you mean binary tree or binary search tree?
Serabe: I’m not sure I know the difference between a binary tree and a binary search tree. The trees we are talking about have nodes with two children, all nodes with keys less than the current node in the left child and all nodes with keys greater than the current node in the right child.
That’s a binary search tree. A tree whose nodes have two children are binary trees (without the order restriction). I guess you can see now that Remco’s algorithm cannot work on binary trees, without the “search” part.
@Serabe: Correct me if i am wrong , I think order is implicit for binary trees , say isAncestor relation ship.
http://en.wikipedia.org/wiki/Binary_tree — including the statement “Binary trees are used to implement binary search trees and binary heaps.”
http://en.wikipedia.org/wiki/Binary_search_tree
The LCA problem is interesting in both cases. With a binary *search* tree, one can use a more-specialized more-efficient algorithm.
Here’s a version for binary trees that are not binary search trees. It is of course less efficient than the original since it now has to search the entire tree.
First, a Python version. My
insert()
method is a bit cumbersome,since it checks whether the current node has a left/right subtree before
heading down it. I owe the
lca()
implementation to Remco’ssuccinct Haskell code.
I’m also trying to learn Common Lisp with the help of Paul Graham’s “Ansi
Common Lisp;” here’s a Common Lisp attempt:
Both versions assume we’re working with a binary search tree with only numbers
in it (or at least objects that can be compared with ==/=).
If we are not allowed to use comparison operator , and if it is not necessary
that m < n , then this program might work , but i don't know how efficient it is?
#lang racket
(define-struct binary-tree (root left right))
(define immediate-ancestor (make-hash))
(define (make-ancestor tree ancestor)
(cond
[(empty? tree) (void)]
[else (hash-set! immediate-ancestor (binary-tree-root tree) ancestor)
(make-ancestor (binary-tree-left tree) (binary-tree-root tree))
(make-ancestor (binary-tree-right tree) (binary-tree-root tree))]))
(define (get-ancestor k)
(hash-ref immediate-ancestor k #f))
(define (get-common m n)
(define mark-visited (make-hash))
(define (set-path-m m)
(let ([ancestor (get-ancestor m)])
(hash-set! mark-visited m #t)
(unless (equal? ancestor m)
(set-path-m ancestor))))
(define (get-common1 n)
(let ([ancestor (get-ancestor n)])
(if (hash-ref mark-visited n #f)
n
(get-common1 ancestor))))
(set-path-m m)
(get-common1 n))
(define btree
(make-binary-tree
8
(make-binary-tree
3
(make-binary-tree 1 empty empty)
(make-binary-tree
6
(make-binary-tree 4 empty empty)
(make-binary-tree 7 empty empty)))
(make-binary-tree
10
empty
(make-binary-tree
14
(make-binary-tree
13
empty
empty)
empty))))
(make-ancestor btree (binary-tree-root btree))
(get-common 4 7)
(get-common 4 10)
(get-common 1 4)
(get-common 1 3)
(get-common 3 6)
Ok formatting does not seem to work .
Here is the link : http://codepad.org/Q5frvH8P
One Correction :
Is-Ancestor relationship example i gave is not right in the current context.
Discrete structure book that i have defines binary tree to be a ordered tree.
Wikipedia does not say that it has to be ordered, i wonder which one is correct.
If all the elements of binary tree are distinct then i guess we could easily define
the ordering.
cheers
Just ignore my previous post , all binary tree are ordered, thats it period.
Sorry for spamming , this is last post for today.
I have slightly modified and commented my previous code .
New link is at http://codepad.org/GKK9n6Td
Common Lisp solution for general binary tree (represented using lists).
In F#
type BTree =
| Empty
| Node of BTree * int * BTree
let rec lowest_common_ancestor (tree: BTree) (a: int) (b: int) =
match tree with
| Empty -> failwith "Wrong input"
| Node (l, n, r) -> if a <= n && n <= b then n
elif a < n && b < n then lowest_common_ancestor l a b
else lowest_common_ancestor r a b
Solution in Perl: http://pastebin.com/r22zy559
Boy, do I need to learn Ocaml/F#.
In the general “binary tree” case (where the tree is not necessarily a search tree) a naive approach is just finding the first common element of two lists:
ancestors_of_a = …
ancestors_of_b = …
most_recent_common_ancestor = first_common_element(ancestors_of_a, ancestors_of_b)
It would be interesting to see if there is a better algorithm for general binary trees.
My approach focuses on traversing the tree using DFS, saving each node’s path from the root, and then comparing the nodes’ path in order to find the lowest common ancestor. Here’s the Java function that traverses the tree:
Here’s the function that finds the lowest common ancestor:
The entire implementation can be seen here.
@Maurits : If there exist a efficient algorithm for binary search tree ,
then same algorithm can be used for any binary tree, just number them accordingly.
This one number the nodes of binary tree and uses Remco Niemeijer’s code (first post)
to compute the LCA :
http://codepad.org/m0U1KVje
Here’s a solution in good old fashioned C:
Python version that subclasses the list built-in type, and adds ‘insert’ and ‘lca’ methods.
Items are stored in the list such that for any self[ndx], self[2*ndx+1] is the root of the left subtree (items self[ndx]).
Oops. Tried using < and > symbols in the test.
Anyway, for an item at index n, the left/right subtrees are rooted at 2n+1 and 2n+2, respectively.
Given the same list structure in my previous post, the following lca()-function finds the lowest common ancestor for any two nodes regardless of whether the items in the tree are sorted, i.e., for any binary tree. The only requirement is that for an item at list[n], the two children are at list[2n+1] and list[2n+2].
[…] Yet another PP challenge piqued my interest. Here’s the problem statement: […]