Skip to content

[WIP] Added binary tree traversals #21

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

Merged
merged 4 commits into from
Jun 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ __pycache__/
.pytest_cache/
pre_commit.ps1
.coverage
htmlcov
# for developement purposes
pds_debug.py
18 changes: 17 additions & 1 deletion pydatastructs/miscellaneous_data_structures/stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def __new__(cls, implementation='array', **kwargs):
if implementation == 'array':
return ArrayStack(
kwargs.get('maxsize', None),
kwargs.get('top', None),
kwargs.get('top', 0),
kwargs.get('items', None),
kwargs.get('dtype', int))
raise NotImplementedError(
Expand All @@ -74,6 +74,14 @@ def pop(self, *args, **kwargs):
raise NotImplementedError(
"This is an abstract method.")

@property
def is_empty(self):
return None

@property
def peek(self):
return None

class ArrayStack(Stack):

def __new__(cls, maxsize=None, top=0, items=None, dtype=int):
Expand Down Expand Up @@ -107,6 +115,14 @@ def pop(self):
self.items[self.top] = None
return r

@property
def is_empty(self):
return self.top == 0

@property
def peek(self):
return self.items[self.top - 1]

def __str__(self):
"""
Used for printing.
Expand Down
2 changes: 1 addition & 1 deletion pydatastructs/trees/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
)

from .binary_trees import (
Node, BinaryTree, BinarySearchTree
Node, BinaryTree, BinarySearchTree, BinaryTreeTraversal
)
__all__.extend(binary_trees.__all__)

Expand Down
198 changes: 197 additions & 1 deletion pydatastructs/trees/binary_trees.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
from __future__ import print_function, division
from pydatastructs.utils import Node
from pydatastructs.miscellaneous_data_structures import Stack
from pydatastructs.linear_data_structures import OneDimensionalArray
# TODO: REPLACE COLLECTIONS QUEUE WITH PYDATASTRUCTS QUEUE
from collections import deque as Queue

__all__ = [
'Node',
'BinaryTree',
'BinarySearchTree'
'BinarySearchTree',
'BinaryTreeTraversal'
]

class BinaryTree(object):
Expand Down Expand Up @@ -243,3 +248,194 @@ def delete(self, key):
self.tree[parent].right = child

return True

class BinaryTreeTraversal(object):
"""
Represents the traversals possible in
a binary tree.

Parameters
==========

tree: BinaryTree
The binary tree for whose traversal
is to be done.

Traversals
==========

- Depth First Search
In Order, Post Order, Pre Order Out Order

- Breadth First Search

Examples
========

>>> from pydatastructs import BinarySearchTree as BST
>>> from pydatastructs import BinaryTreeTraversal as BTT
>>> b = BST(2, 2)
>>> b.insert(1, 1)
>>> b.insert(3, 3)
>>> trav = BTT(b)
>>> dfs = trav.depth_first_search()
>>> [str(n) for n in dfs]
['(None, 1, 1, None)', '(1, 2, 2, 2)', '(None, 3, 3, None)']
>>> bfs = trav.breadth_first_search()
>>> [str(n) for n in bfs]
['(1, 2, 2, 2)', '(None, 1, 1, None)', '(None, 3, 3, None)']

References
==========

.. [1] https://en.wikipedia.org/wiki/Tree_traversal
"""

__slots__ = ['tree']

def __new__(cls, tree):
if not isinstance(tree, BinaryTree):
raise TypeError("%s is not a binary tree"%(tree))
obj = object.__new__(cls)
obj.tree = tree
return obj

def _pre_order(self, node):
"""
Utility method for computing pre-order
of a binary tree using iterative algorithm.
"""
visit = []
if node == None:
return visit
tree, size = self.tree.tree, self.tree.size
s = Stack(maxsize=size)
s.push(node)
while not s.is_empty:
node = s.pop()
visit.append(tree[node])
if tree[node].right != None:
s.push(tree[node].right)
if tree[node].left != None:
s.push(tree[node].left)
return visit

def _in_order(self, node):
"""
Utility method for computing in-order
of a binary tree using iterative algorithm.
"""
visit = []
tree, size = self.tree.tree, self.tree.size
s = Stack(maxsize=size)
while not s.is_empty or node != None:
if node != None:
s.push(node)
node = tree[node].left
else:
node = s.pop()
visit.append(tree[node])
node = tree[node].right
return visit

def _post_order(self, node):
"""
Utility method for computing post-order
of a binary tree using iterative algorithm.
"""
visit = []
tree, size = self.tree.tree, self.tree.size
s = Stack(maxsize=size)
s.push(node)
last = OneDimensionalArray(int, size)
last.fill(False)
while not s.is_empty:
node = s.peek
l, r = tree[node].left, tree[node].right
cl, cr = l == None or last[l], r == None or last[r]
if cl and cr:
s.pop()
visit.append(tree[node])
last[node] = True
continue
if not cr:
s.push(r)
if not cl:
s.push(l)
return visit

def _out_order(self, node):
"""
Utility method for computing out-order
of a binary tree using iterative algorithm.
"""
return reversed(self._in_order(node))

def depth_first_search(self, order='in_order', node=None):
"""
Computes the depth first search traversal of the binary
trees.

Parameters
==========

order : str
One of the strings, 'in_order', 'post_order',
'pre_order', 'out_order'.
By default, it is set to, 'in_order'.
node : int
The index of the node from where the traversal
is to be instantiated.

Returns
=======

list
Each element is of type 'Node'.
"""
if node == None:
node = self.tree.root_idx
if order not in ('in_order', 'post_order', 'pre_order', 'out_order'):
raise NotImplementedError(
"%s order is not implemented yet."
"We only support `in_order`, `post_order`, "
"`pre_order` and `out_order` traversals.")
return getattr(self, '_' + order)(node)

def breadth_first_search(self, node=None, strategy='queue'):
# TODO: IMPLEMENT ITERATIVE DEEPENING-DEPTH FIRST SEARCH STRATEGY
"""
Computes the breadth first search traversal of a binary tree.

Parameters
==========

strategy : str
The strategy using which the computation has to happen.
By default, it is set 'queue'.
node : int
The index of the node from where the traversal has to be instantiated.
By default, set to, root index.

Returns
=======

list
Each element of the list is of type `Node`.
"""
strategies = ('queue',)
if strategy not in strategies:
raise NotImplementedError(
"%s startegy is not implemented yet"%(strategy))
if node == None:
node = self.tree.root_idx
q, visit, tree = Queue(), [], self.tree.tree
q.append(node)
while len(q) > 0:
node = q.popleft()
visit.append(tree[node])
if tree[node].left != None:
q.append(tree[node].left)
if tree[node].right != None:
q.append(tree[node].right)
return visit
1 change: 1 addition & 0 deletions pydatastructs/trees/space_partitioning_trees.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from pydatastructs.utils import Node
# TODO: REPLACE COLLECTIONS QUEUE WITH PYDATASTRUCTS QUEUE
from collections import deque as Queue
from pydatastructs.linear_data_structures.arrays import _check_type

Expand Down
46 changes: 45 additions & 1 deletion pydatastructs/trees/tests/test_binary_trees.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from pydatastructs.trees.binary_trees import BinarySearchTree
from pydatastructs.trees.binary_trees import (
BinarySearchTree, BinaryTreeTraversal)
from pydatastructs.utils.raises_util import raises

def test_BinarySearchTree():
Expand Down Expand Up @@ -32,3 +33,46 @@ def test_BinarySearchTree():
bc = BST(1, 1)
assert bc.insert(1, 2) == None
raises(ValueError, lambda: BST(root_data=6))

def test_BinaryTreeTraversal():
BST = BinarySearchTree
BTT = BinaryTreeTraversal
b = BST('F', 'F')
b.insert('B', 'B')
b.insert('A', 'A')
b.insert('G', 'G')
b.insert('D', 'D')
b.insert('C', 'C')
b.insert('E', 'E')
b.insert('I', 'I')
b.insert('H', 'H')
trav = BTT(b)
pre = trav.depth_first_search(order='pre_order')
assert [str(n) for n in pre] == \
["(1, 'F', 'F', 3)", "(2, 'B', 'B', 4)", "(None, 'A', 'A', None)",
"(5, 'D', 'D', 6)", "(None, 'C', 'C', None)", "(None, 'E', 'E', None)",
"(None, 'G', 'G', 7)", "(8, 'I', 'I', None)", "(None, 'H', 'H', None)"]
ino = trav.depth_first_search()
assert [str(n) for n in ino] == \
["(None, 'A', 'A', None)", "(2, 'B', 'B', 4)", "(None, 'C', 'C', None)",
"(5, 'D', 'D', 6)", "(None, 'E', 'E', None)", "(1, 'F', 'F', 3)",
"(None, 'G', 'G', 7)", "(None, 'H', 'H', None)", "(8, 'I', 'I', None)"]
out = trav.depth_first_search(order='out_order')
assert [str(n) for n in out] == \
["(8, 'I', 'I', None)", "(None, 'H', 'H', None)", "(None, 'G', 'G', 7)",
"(1, 'F', 'F', 3)", "(None, 'E', 'E', None)", "(5, 'D', 'D', 6)",
"(None, 'C', 'C', None)", "(2, 'B', 'B', 4)", "(None, 'A', 'A', None)"]
post = trav.depth_first_search(order='post_order')
assert [str(n) for n in post] == \
["(None, 'A', 'A', None)", "(None, 'C', 'C', None)",
"(None, 'E', 'E', None)", "(5, 'D', 'D', 6)", "(2, 'B', 'B', 4)",
"(None, 'H', 'H', None)", "(8, 'I', 'I', None)", "(None, 'G', 'G', 7)",
"(1, 'F', 'F', 3)"]
bfs = trav.breadth_first_search()
assert [str(n) for n in bfs] == \
["(1, 'F', 'F', 3)", "(2, 'B', 'B', 4)", "(None, 'G', 'G', 7)",
"(None, 'A', 'A', None)", "(5, 'D', 'D', 6)", "(8, 'I', 'I', None)",
"(None, 'C', 'C', None)", "(None, 'E', 'E', None)",
"(None, 'H', 'H', None)"]
raises(NotImplementedError, lambda: trav.breadth_first_search(strategy='iddfs'))
raises(NotImplementedError, lambda: trav.depth_first_search(order='in_out_order'))