Skip to content

Commit 0428473

Browse files
authored
Added LCA algorithms (#88)
Two algorithms for finding lowest common ancestor have been added.
1 parent 807f07e commit 0428473

File tree

2 files changed

+169
-1
lines changed

2 files changed

+169
-1
lines changed

pydatastructs/trees/binary_trees.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,143 @@ def rank(self, x):
415415
walk = p
416416
return r
417417

418+
def _simple_path(self, key, root):
419+
"""
420+
Utility funtion to find the simple path between root and node.
421+
422+
Parameter
423+
=========
424+
425+
key: Node.key
426+
Key of the node to be searched
427+
428+
Returns
429+
=======
430+
431+
path: list
432+
"""
433+
434+
stack = Stack()
435+
stack.push(root)
436+
path = []
437+
node_idx = -1
438+
439+
while not stack.is_empty:
440+
node = stack.pop()
441+
if self.tree[node].key == key:
442+
node_idx = node
443+
break
444+
if self.tree[node].left:
445+
stack.push(self.tree[node].left)
446+
if self.tree[node].right:
447+
stack.push(self.tree[node].right)
448+
449+
if node_idx == -1:
450+
return path
451+
452+
while node_idx != 0:
453+
path.append(node_idx)
454+
node_idx = self.tree[node_idx].parent
455+
path.append(0)
456+
path.reverse()
457+
458+
return path
459+
460+
def _lca_1(self, j, k):
461+
root = self.root_idx
462+
path1 = self._simple_path(j, root)
463+
path2 = self._simple_path(k, root)
464+
if not path1 or not path2:
465+
raise ValueError("One of two path doesn't exists. See %s, %s"
466+
%(path1, path2))
467+
468+
n, m = len(path1), len(path2)
469+
i = j = 0
470+
while i < n and j < m:
471+
if path1[i] != path2[j]:
472+
return self.tree[path1[i - 1]].key
473+
i += 1
474+
j += 1
475+
if path1 < path2:
476+
return self.tree[path1[-1]].key
477+
return self.tree[path2[-1]].key
478+
479+
def _lca_2(self, j, k):
480+
curr_root = self.root_idx
481+
u, v = self.search(j), self.search(k)
482+
if (u is None) or (v is None):
483+
raise ValueError("One of the nodes with key %s "
484+
"or %s doesn't exits"%(j, k))
485+
u_left = self.comparator(self.tree[u].key, \
486+
self.tree[curr_root].key)
487+
v_left = self.comparator(self.tree[v].key, \
488+
self.tree[curr_root].key)
489+
490+
while not (u_left ^ v_left):
491+
if u_left and v_left:
492+
curr_root = self.tree[curr_root].left
493+
else:
494+
curr_root = self.tree[curr_root].right
495+
496+
if curr_root == u or curr_root == v:
497+
if curr_root is None:
498+
return None
499+
return self.tree[curr_root].key
500+
501+
u_left = self.comparator(self.tree[u].key, \
502+
self.tree[curr_root].key)
503+
v_left = self.comparator(self.tree[v].key, \
504+
self.tree[curr_root].key)
505+
506+
if curr_root is None:
507+
return curr_root
508+
return self.tree[curr_root].key
509+
510+
def lowest_common_ancestor(self, j, k, algorithm=1):
511+
512+
"""
513+
Computes the lowest common ancestor of two nodes.
514+
515+
Parameters
516+
==========
517+
518+
j: Node.key
519+
Key of first node
520+
k: Node.key
521+
Key of second node
522+
algorithm: int
523+
The algorithm to be used for computing the
524+
lowest common ancestor.
525+
Optional, by default uses algorithm 1.
526+
527+
1 -> Determines the lowest common ancestor by finding
528+
the first intersection of the paths from v and w
529+
to the root.
530+
531+
2 -> Modifed version of the algorithm given in the
532+
following publication,
533+
D. Harel. A linear time algorithm for the
534+
lowest common ancestors problem. In 21s
535+
Annual Symposium On Foundations of
536+
Computer Science, pages 308-319, 1980.
537+
538+
Returns
539+
=======
540+
541+
Node.key
542+
The key of the lowest common ancestor in the tree.
543+
if both the nodes are present in the tree.
544+
545+
References
546+
==========
547+
548+
.. [1] https://en.wikipedia.org/wiki/Lowest_common_ancestor
549+
550+
.. [2] https://pdfs.semanticscholar.org/e75b/386cc554214aa0ebd6bd6dbdd0e490da3739.pdf
551+
552+
"""
553+
return getattr(self, "_lca_"+str(algorithm))(j, k)
554+
418555
class AVLTree(BinarySearchTree):
419556
"""
420557
Represents AVL trees.

pydatastructs/trees/tests/test_binary_trees.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,38 @@ def test_BinarySearchTree():
5454
assert b.delete(-10) is True
5555
assert b.delete(-3) is True
5656
assert b.delete(-13) is None
57-
raises(ValueError, lambda: BST(root_data=6))
57+
bl = BST()
58+
nodes = [50, 30, 90, 70, 100, 60, 80, 55, 20, 40, 15, 10, 16, 17, 18]
59+
for node in nodes:
60+
bl.insert(node, node)
61+
62+
assert bl.lowest_common_ancestor(80, 55, 2) == 70
63+
assert bl.lowest_common_ancestor(60, 70, 2) == 70
64+
assert bl.lowest_common_ancestor(18, 18, 2) == 18
65+
assert bl.lowest_common_ancestor(40, 90, 2) == 50
66+
67+
assert bl.lowest_common_ancestor(18, 10, 2) == 15
68+
assert bl.lowest_common_ancestor(55, 100, 2) == 90
69+
assert bl.lowest_common_ancestor(16, 80, 2) == 50
70+
assert bl.lowest_common_ancestor(30, 55, 2) == 50
71+
72+
assert raises(ValueError, lambda: bl.lowest_common_ancestor(60, 200, 2))
73+
assert raises(ValueError, lambda: bl.lowest_common_ancestor(200, 60, 2))
74+
assert raises(ValueError, lambda: bl.lowest_common_ancestor(-3, 4, 2))
75+
76+
assert bl.lowest_common_ancestor(80, 55, 1) == 70
77+
assert bl.lowest_common_ancestor(60, 70, 1) == 70
78+
assert bl.lowest_common_ancestor(18, 18, 1) == 18
79+
assert bl.lowest_common_ancestor(40, 90, 1) == 50
80+
81+
assert bl.lowest_common_ancestor(18, 10, 1) == 15
82+
assert bl.lowest_common_ancestor(55, 100, 1) == 90
83+
assert bl.lowest_common_ancestor(16, 80, 1) == 50
84+
assert bl.lowest_common_ancestor(30, 55, 1) == 50
85+
86+
assert raises(ValueError, lambda: bl.lowest_common_ancestor(60, 200, 1))
87+
assert raises(ValueError, lambda: bl.lowest_common_ancestor(200, 60, 1))
88+
assert raises(ValueError, lambda: bl.lowest_common_ancestor(-3, 4, 1))
5889

5990
def test_BinaryTreeTraversal():
6091
BST = BinarySearchTree

0 commit comments

Comments
 (0)