Skip to content

feat: add Parent Pointer Tree #662

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
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