Skip to content
1 change: 1 addition & 0 deletions docs/source/pydatastructs/trees/m_ary_trees.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
M-ary Trees
===========

.. autoclass:: pydatastructs.ParentPointerTree
4 changes: 3 additions & 1 deletion pydatastructs/trees/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
__all__.extend(binary_trees.__all__)

from .m_ary_trees import (
MAryTreeNode, MAryTree
MAryTreeNode,
MAryTree,
ParentPointerTree
)

__all__.extend(m_ary_trees.__all__)
Expand Down
225 changes: 223 additions & 2 deletions pydatastructs/trees/m_ary_trees.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from pydatastructs.utils import MAryTreeNode
from pydatastructs.utils import MAryTreeNode, ParentPointerTreeNode
from pydatastructs.linear_data_structures.arrays import ArrayForTrees
from pydatastructs.utils.misc_util import (
Backend, raise_if_backend_is_not_python)

__all__ = [
'MAryTree'
'MAryTree',
'ParentPointerTree'
]

class MAryTree(object):
Expand Down Expand Up @@ -170,3 +171,223 @@ def __str__(self):
if j is not None:
to_be_printed[i].append(j)
return str(to_be_printed)

class ParentPointerTree(MAryTree):
"""
Implements a tree with parent pointers.

Parameters
==========

key
Required if tree is to be instantiated with
root otherwise not needed.
root_data
Optional, the root node of the tree.
If not of type TreeNode, it will consider
root as data and a new root node will
be created.
comp: lambda
Optional, A lambda function which will be used
for comparison of keys. Should return a
bool value. By default it implements less
than operator.

References
==========

.. [1] https://en.wikipedia.org/wiki/Tree_(data_structure)#Parent_pointer_tree
"""

__slots__ = ['root_idx', 'comparator', 'tree', 'size']

def __new__(cls, key=None, root_data=None, comp=None, **kwargs):
raise_if_backend_is_not_python(
cls, kwargs.get('backend', Backend.PYTHON))
obj = object.__new__(cls)

# Empty tree
if key is None:
obj.root_idx = None
obj.tree, obj.size = ArrayForTrees(ParentPointerTreeNode, []), 0
obj.comparator = lambda key1, key2: key1 < key2 \
if comp is None else comp
return obj

root = ParentPointerTreeNode(key, root_data)
root.is_root = True
obj.root_idx = 0
obj.tree, obj.size = ArrayForTrees(ParentPointerTreeNode, [root]), 1
obj.comparator = lambda key1, key2: key1 < key2 \
if comp is None else comp

return obj

@classmethod
def methods(cls):
return ['__new__', '__str__']

def insert(self, parent_key, key, data=None):
"""
Inserts data by the passed key using iterative
algorithm.

Parameters
==========

key
The key for comparison.
data
The data to be inserted.

Returns
=======

None
"""
if key is None:
raise ValueError("Key is required.")

# Empty tree
if self.size == 0:
if parent_key is not None:
raise ValueError("Parent key should be None.")

root = ParentPointerTreeNode(key, data)
root.is_root = True
self.tree.append(root)
self.size += 1
return

if parent_key is None:
raise ValueError("Parent key is required.")

if self.search(key) is not None:
raise ValueError("Key already exists.")

parent_node = self.search(parent_key)
new_node = ParentPointerTreeNode(key, data, parent_node)

self.tree.append(new_node)
self.size += 1

def delete(self, key):
"""
Deletes the data with the passed key
using iterative algorithm.

Parameters
==========

key
The key of the node which is
to be deleted.

Returns
=======

True
If the node is deleted successfully.

None
If the node to be deleted doesn't exists.

Note
====

The node is deleted means that the connection to that
node are removed but the it is still in tree.
"""
for idx in range(self.tree._last_pos_filled + 1):
if self.tree[idx] and self.tree[idx].key == key:
self.tree.delete(idx)
self.size -= 1
return True

return None

def search(self, key, **kwargs):
"""
Searches for the data in the tree
using iterative algorithm.

Parameters
==========

key
The key for searching.
get_parent: bool
If true then returns node of the
parent of the node with the passed
key.
By default, False

Returns
=======

ParentPointerTreeNode
The tree node if it was found
in the tree.
None
In all other cases.
"""
parent = kwargs.get('parent', False)

for idx in range(self.tree._last_pos_filled + 1):
node = self.tree[idx]
if node is not None and node.key == key:
if parent:
return node.parent
return node

return None


def least_common_ancestor(self, first_child_key, second_child_key):
"""
Finds the least common ancestor of two nodes in
the tree.

Parameters
==========

first_child_key
The key of the first child node.
second_child_key
The key of the second child node.

Returns
=======

ParentPointerTreeNode
The least common ancestor node.
None
If either of the nodes doesn't exist in the tree.
"""
first_node = self.search(first_child_key)
second_node = self.search(second_child_key)

# One or both nodes do not exist
if first_node is None or second_node is None:
return None

first_ancestors = set()

while first_node is not None:
first_ancestors.add(first_node)
first_node = first_node.parent

while second_node is not None:
if second_node in first_ancestors:
return second_node # Found the least common ancestor
second_node = second_node.parent

return None # No common ancestor found

def __str__(self):
to_be_printed = []
for i in range(self.tree._last_pos_filled + 1):
if self.tree[i] is not None:
node = self.tree[i]
to_be_printed.append((node.key, node.data, str(node.parent)))
return str(to_be_printed)
66 changes: 65 additions & 1 deletion pydatastructs/trees/tests/test_m_ary_trees.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,69 @@
from pydatastructs import MAryTree
from pydatastructs.utils.misc_util import Backend
from pydatastructs.trees.m_ary_trees import MAryTree, ParentPointerTree


def test_MAryTree():
m = MAryTree(1, 1)
assert str(m) == '[(1, 1)]'


def _test_ParentPointerTree(backend):
PPT = ParentPointerTree

tree = PPT(1, "root", backend=backend)
assert tree.root_idx == 0
assert tree.tree[0].key == 1
assert tree.tree[0].data == "root"
assert tree.tree[0].parent is None

tree.insert(1, 2, "child_1")
tree.insert(1, 3, "child_2")
tree.insert(1, 4, "child_3")
assert tree.size == 4
assert tree.tree[1].key == 2
assert tree.tree[1].data == "child_1"
assert tree.tree[1].parent == tree.tree[0]
assert tree.tree[2].key == 3
assert tree.tree[2].data == "child_2"
assert tree.tree[2].parent == tree.tree[0]
assert tree.tree[3].key == 4
assert tree.tree[3].data == "child_3"
assert tree.tree[3].parent == tree.tree[0]

assert tree.search(2).data == "child_1"
assert tree.search(3).data == "child_2"
assert tree.search(4).data == "child_3"
assert tree.search(5) is None
assert tree.search(2, parent=True) == tree.tree[0]

tree.insert(2, 5, "child_4")
tree.insert(2, 6, "child_5")
assert tree.least_common_ancestor(5, 6) == tree.tree[1]
assert tree.least_common_ancestor(5, 3) == tree.tree[0]
assert tree.least_common_ancestor(2, 4) == tree.tree[0]
assert tree.least_common_ancestor(5, 7) is None

assert tree.delete(5) is True
assert tree.search(5) is None
assert tree.size == 5
assert tree.delete(6) is True
assert tree.search(6) is None
assert tree.size == 4
assert tree.delete(10) is None

expected = '''[(1, 'root', 'None'), (2, 'child_1', "(1, 'root')"), (3, 'child_2', "(1, 'root')"), (4, 'child_3', "(1, 'root')")]'''
assert str(tree) == expected

empty_tree = PPT(backend=backend)
assert empty_tree.size == 0
assert empty_tree.search(1) is None
assert empty_tree.delete(1) is None
assert empty_tree.least_common_ancestor(1, 2) is None

empty_tree.insert(None, 7, "child_6")

expected = '''[(7, 'child_6', 'None')]'''
assert str(empty_tree) == expected

def test_ParentPointerTree():
_test_ParentPointerTree(Backend.PYTHON)
1 change: 1 addition & 0 deletions pydatastructs/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
BinomialTreeNode,
AdjacencyListGraphNode,
AdjacencyMatrixGraphNode,
ParentPointerTreeNode,
GraphEdge,
Set,
CartesianTreeNode,
Expand Down
34 changes: 34 additions & 0 deletions pydatastructs/utils/misc_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
'Set',
'CartesianTreeNode',
'RedBlackTreeNode',
'ParentPointerTreeNode',
'TrieNode',
'SkipNode',
'minimum',
Expand Down Expand Up @@ -275,6 +276,39 @@ def add_children(self, *children):
def __str__(self):
return str((self.key, self.data))

class ParentPointerTreeNode(TreeNode):
"""
Represents node in trees with parent pointers.

Parameters
==========

key
Required for comparison operations.
data
Any valid data to be stored in the node.
parent
Reference to the parent node.
backend: pydatastructs.Backend
The backend to be used.
Optional, by default, the best available
backend is used.
"""
__slots__ = ['key', 'data', 'parent']

@classmethod
def methods(cls):
return ['__new__', '__str__']

def __new__(cls, key, data=None, parent: 'ParentPointerTreeNode' = None, **kwargs):
raise_if_backend_is_not_python(
cls, kwargs.get('backend', Backend.PYTHON))
obj = Node.__new__(cls)
obj.data, obj.key, obj.parent = data, key, parent
return obj

def __str__(self):
return str((self.key, self.data))

class LinkedListNode(Node):
"""
Expand Down
Loading