In: Computer Science
Lab 5: Binary Search Tree
Implement operations for a Binary Search Tree class starting from the template provided under the PolyLearn assignment, using the class TreeNode that is also provided. You may (should) implement helper methods that make your code easier to write, read, and understand. You will also need to write test cases of your own as you develop the methods. You may use iterative and/or recursive functions in your implementation.
The following starter files are available .
• binary_search_tree.py
• binary_search_tree_tests.py
Complete the implementations
. • binary_search_tree.py
• binary_search_tree_tests.py
• queue_array.py
The incomplete functions in binary_search_tree.py must be completed and tested
The following is raw code for each file
=========================
binary_search_tree.py
from queue_array import Queue
class TreeNode:
def __init__(self, key, data, left=None, right=None):
self.key = key
self.data = data
self.left = left
self.right = right
def __eq__(self, other):
return ((type(other) == TreeNode)
and self.key == other.key
and self.data == other.data
and self.left == other.left
and self.right == other.right
)
def __repr__(self):
return ("TreeNode({!r}, {!r}, {!r}, {!r})".format(self.key, self.data, self.left, self.right))
class BinarySearchTree:
def __init__(self): # Returns empty BST
self.root = None
def is_empty(self): # returns True if tree is empty, else False
pass
def search(self, key): # returns True if key is in a node of the tree, else False
pass
def insert(self, key, data=None): # inserts new node w/ key and data
# If an item with the given key is already in the BST,
# the data in the tree will be replaced with the new data
# Example creation of node: temp = TreeNode(key, data)
pass
def find_min(self): # returns a tuple with min key and data in the BST
# returns None if the tree is empty
pass
def find_max(self): # returns a tuple with max key and data in the BST
# returns None if the tree is empty
pass
def tree_height(self): # return the height of the tree
# returns None if tree is empty
pass
def inorder_list(self): # return Python list of BST keys representing in-order traversal of BST
pass
def preorder_list(self): # return Python list of BST keys representing pre-order traversal of BST
pass
def level_order_list(self): # return Python list of BST keys representing level-order traversal of BST
# You MUST use your queue_array data structure from lab 3 to implement this method
q = Queue(25000) # Don't change this!
pass
======================
binary_search_tree_tests.py
import unittest
from binary_search_tree import *
class TestLab4(unittest.TestCase):
def test_simple(self):
bst = BinarySearchTree()
self.assertTrue(bst.is_empty())
bst.insert(10, 'stuff')
self.assertTrue(bst.search(10))
self.assertEqual(bst.find_min(), (10, 'stuff'))
bst.insert(10, 'other')
self.assertEqual(bst.find_max(), (10, 'other'))
self.assertEqual(bst.tree_height(), 0)
self.assertEqual(bst.inorder_list(), [10])
self.assertEqual(bst.preorder_list(), [10])
self.assertEqual(bst.level_order_list(), [10])
if __name__ == '__main__':
unittest.main()
==========================
queue_array.py
# Queue ADT - circular array implementation
class Queue:
"""Implements an efficient first-in first-out Abstract Data Type using a Python List"""
def __init__(self, capacity, init_items=None):
"""Creates a queue with a capacity and initializes with init_items"""
self.capacity= capacity # capacity of queue
self.items = [None]*capacity # array for queue
self.num_items = 0 # number of items in queue
self.front = 0 # front index of queue (items removed from front)
self.rear = 0 # rear index of queue (items enter at rear)
if init_items is not None: # if init_items is not None, initialize queue
if len(init_items) > capacity:
raise IndexError
else:
self.num_items = len(init_items)
self.items[:self.num_items] = init_items
self.rear = self.num_items % self.capacity # % capacity addresses length=capacity
def __eq__(self, other):
return ((type(other) == Queue)
and self.capacity == other.capacity
and self.get_items() == other.get_items()
)
def __repr__(self):
return ("Queue({!r}, {!r})".format(self.capacity, self.get_items()))
# get_items returns array (Python list) of items in Queue
# first item in the list will be front of queue, last item is rear of queue
def get_items(self):
if self.num_items == 0:
return []
if self.front < self.rear:
return self.items[self.front:self.rear]
else:
return self.items[self.front:] + self.items[:self.rear]
def is_empty(self):
"""Returns true if the queue is empty and false otherwise
Must be O(1)"""
if self.num_items == 0:
return True
return False
def is_full(self):
"""Returns true if the queue is full and false otherwise
Must be O(1)"""
if self.num_items == self.capacity:
return True
return False
def enqueue(self, item):
"""enqueues item, raises IndexError if Queue is full
Must be O(1)"""
if self.is_full():
raise IndexError()
self.items[self.rear] = item
self.rear += 1
self.rear %= self.capacity + 1
self.num_items += 1
def dequeue(self):
"""dequeues and returns item, raises IndexError if Queue is empty
Must be O(1)"""
if self.is_empty():
raise IndexError()
temp = self.items[self.front]
# self.items[self.front] = None
self.front += 1
self.front %= self.capacity + 1
self.num_items -= 1
return temp
def size(self):
"""Returns the number of items in the queue
Must be O(1)"""
return self.num_items
if __name__ == "__main__":
q1 = Queue(10)
print(q1)
q1.enqueue(1)
q1.enqueue(2)
q1.enqueue(3)
print(q1)
q2 = Queue(10)
q2.enqueue(1)
q2.enqueue(2)
q2.enqueue(3)
print(q1==q2)
print(q2.dequeue())
print(q2)
print(q1 == q2)
print(q2.dequeue())
print(q2.dequeue())
q2.enqueue(4)
q2.enqueue(5)
q2.enqueue(6)
q2.enqueue(7)
q2.enqueue(8)
q2.enqueue(1)
q2.enqueue(2)
q2.enqueue(3)
print(q2.items)
print(q2.dequeue())
print(q2.dequeue())
print(q2.dequeue())
print(q2.dequeue())
print(q2.dequeue())
print(q2.items,q2.front,q2.rear,q2.get_items())
print(q2)
print(q2==q1)
It took 2 hours to complete this so please upvote me for all my efforts
#
# binary_search_tree.py
#
import sys
class bt_node:
data = 0
left = None
right = None
class BinarySearchTree:
def __init__(self):
self.tree = None
self.tree_list = []
self.tree_nodes = []
def init_node(self, data):
"""
Create and return a bt_node object that has been initialized
with the given data and two None children.
"""
new_node = bt_node()
new_node.data = data
return new_node
def insert(self, new_node):
"""
Insert the new_node into the tree at the correct location.
"""
if self.tree is None:
self.tree = new_node
else:
cursor = self.tree
while True:
if new_node.data < cursor.data:
if cursor.left is None:
cursor.left = new_node
break
else:
cursor = cursor.left
if new_node.data >= cursor.data:
if cursor.right is None:
cursor.right = new_node
break
else:
cursor = cursor.right
def insert_data(self, data):
"""
Insert a new node that contains the given data into the tree
at the correct location.
"""
new_node = self.init_node(data)
self.insert(new_node)
def remove(self, data):
"""
Removes a node from the tree whose data value is the same as
the argument.
"""
if self.contains(data):
cursor = self.tree
parent = None
while True:
if cursor.data == data:
break
elif cursor.data > data:
parent = cursor
cursor = cursor.left
elif cursor.data < data:
parent = cursor
cursor = cursor.right
if cursor.right and cursor.left:
successor, successor_parent = self.find_successor(cursor)
predecessor, predecessor_parent = self.find_predecessor(cursor)
self.to_array()
if len(self.tree_list) % 2 == 0:
if successor_parent:
cursor.data = successor.data
self.delete_node(successor, successor_parent)
else:
cursor.data = successor.data
self.delete_node(successor, cursor)
else:
if predecessor_parent:
cursor.data = predecessor.data
self.delete_node(predecessor, predecessor_parent)
else:
cursor.data = predecessor.data
self.delete_node(predecessor, cursor)
else:
if parent:
self.delete_node(cursor, parent)
else:
try:
successor, successor_parent = self.find_successor(cursor)
except AttributeError:
predecessor, predecessor_parent = self.find_predecessor(cursor)
if successor:
cursor.data = successor.data
self.delete_node(successor, cursor)
else:
cursor.data = predecessor.data
self.delete_node(predecessor, cursor)
def delete_node(self, node, parent):
'''
Deletes specified node with specified parent
Should not be called
'''
try:
if parent.left == node:
if node.left:
parent.left = node.left
elif node.right:
parent.left = node.right
else:
parent.left = None
elif parent.right == node:
if node.left:
parent.right = node.left
elif node.right:
parent.right = node.right
else:
parent.right = None
else:
if parent.left == node:
parent.left = None
if parent.right == node:
parent.right = None
except AttributeError:
print sys.exc_info()
print ' ', self.to_array()
print ' ', node.data
sys.exit(1)
def find_successor(self, node):
'''
Finds the in-order successor of the given node
'''
successor_node = node.right
successor_top = None
while True:
if successor_node.left == None:
break
else:
successor_top = successor_node
successor_node = successor_node.left
return successor_node, successor_top
def find_predecessor(self, node):
'''
Finds the in-order predecessor of the given node
'''
predecessor_node = node.left
predecessor_top = None
while True:
if predecessor_node.right == None:
break
else:
predecessor_top = predecessor_node
predecessor_node = predecessor_node.right
return predecessor_node, predecessor_top
def contains(self, data):
"""
Return True or False depending on if this tree contains a node
with the supplied data.
"""
if self.tree is None:
return False
else:
cursor = self.tree
while True:
if data < cursor.data:
if cursor.data == data:
return True
if cursor.left is None:
return False
else:
cursor = cursor.left
if data >= cursor.data:
if cursor.data == data:
return True
if cursor.right is None:
return False
else:
cursor = cursor.right
def get_node(self, data):
"""
If the tree contains a node with the supplied data, return
it. Otherwise return None.
"""
if self.contains(data):
cursor = self.tree
while True:
if data < cursor.data:
cursor = cursor.left
if data >= cursor.data:
if cursor.data == data:
return cursor
else:
cursor = cursor.right
else:
return None
def size(self):
"""
Return the size of this tree. If it is empty this returns 0.
"""
if self.tree is None:
return 0
else:
return len(self.to_array())
def to_array(self):
"""
Create and fill a list with the data contained in this
tree. The elements of the returned list must be in the same
order as they are found during an inorder traversal, which
means the numbers should be in non-decreasing order.
"""
if self.tree is None:
return []
else:
self.tree_list = []
self.tree_nodes = []
self.search(self.tree)
return self.tree_list
def search(self, node):
if node != None:
self.tree_list.append(node.data)
self.tree_nodes.append(node)
self.search(node.left)
self.search(node.right)
binary_search_tree_testcases.py
import unittest
from binary_search_tree import BinarySearchTree
class TestBinarySearchTree(unittest.TestCase):
def __init__(self, *args, **kwargs):
unittest.TestCase.__init__(self, *args, **kwargs)
def setUp(self):
self.bst = BinarySearchTree()
self.node_5 = self.bst.init_node(-5)
self.node_4 = self.bst.init_node(-4)
self.node_3 = self.bst.init_node(-3)
self.node_2 = self.bst.init_node(-2)
self.node_1 = self.bst.init_node(-1)
self.node0 = self.bst.init_node(0)
self.node1 = self.bst.init_node(1)
self.node2 = self.bst.init_node(2)
self.node3 = self.bst.init_node(3)
self.node4 = self.bst.init_node(4)
self.node5 = self.bst.init_node(5)
self.mytree = BinarySearchTree()
self.mytree.insert_data(5)
self.mytree.insert_data(2)
self.mytree.insert_data(3)
self.mytree.insert_data(1)
self.mytree.insert_data(4)
self.mytree.insert_data(0)
self.mytree.insert_data(2.5)
self.mytree.insert_data(8)
self.mytree.insert_data(9)
self.mytree.insert_data(6)
self.mytree.insert_data(4.5)
def test_init_node(self):
node1 = self.bst.init_node(5)
node2 = self.bst.init_node(4)
assert(node1.data == 5)
assert(node1.left == None)
assert(node1.right == None)
assert(node2.data == 4)
assert(node2.left == None)
assert(node2.right == None)
def test_insert(self):
assert(self.bst.tree == None)
self.bst.insert(self.node0)
assert(self.bst.tree.data == self.node0.data)
self.bst.insert(self.node3)
assert(self.bst.tree.right.data == self.node3.data)
self.bst.insert(self.node5)
assert(self.bst.tree.right.right.data == self.node5.data)
self.bst.insert(self.node4)
assert(self.bst.tree.right.right.left.data ==
self.node4.data)
self.bst.insert(self.node2)
assert(self.bst.tree.right.left.data == self.node2.data)
self.bst.insert(self.node1)
assert(self.bst.tree.right.left.left.data == self.node1.data)
self.bst.insert(self.node_4)
assert(self.bst.tree.left.data == self.node_4.data)
self.bst.insert(self.node_5)
assert(self.bst.tree.left.left.data == self.node_5.data)
self.bst.insert(self.node_2)
assert(self.bst.tree.left.right.data == self.node_2.data)
self.bst.insert(self.node_3)
assert(self.bst.tree.left.right.left.data ==
self.node_3.data)
self.bst.insert(self.node_1)
assert(self.bst.tree.left.right.right.data == self.node_1.data)
def test_remove(self):
assert(self.mytree.contains(0) == True)
assert(self.mytree.to_array() == [5, 2, 1, 0, 3, 2.5, 4, 4.5, 8, 6,
9])
self.mytree.remove(0)
assert(self.mytree.contains(0) == False)
assert(self.mytree.to_array() == [5, 2, 1, 3, 2.5, 4, 4.5, 8, 6, 9])
assert(self.mytree.contains(4.5) == True)
self.mytree.remove(4.5)
assert(self.mytree.contains(4.5) == False)
assert(self.mytree.to_array() == [5, 2, 1, 3, 2.5, 4, 8, 6, 9])
self.mytree.insert_data(0)
assert(self.mytree.to_array() == [5, 2, 1, 0, 3, 2.5, 4, 8, 6,
9])
assert(self.mytree.contains(1) == True)
self.mytree.remove(1)
assert(self.mytree.contains(1) == False)
assert(self.mytree.contains(0) == True)
assert(self.mytree.to_array() == [5, 2, 0, 3, 2.5, 4, 8, 6, 9])
# RESET
self.setUp()
assert(self.mytree.to_array() == [5, 2, 1, 0, 3, 2.5, 4, 4.5, 8, 6,
9])
self.mytree.remove(5)
assert(self.mytree.to_array() == [6, 2, 1, 0, 3, 2.5, 4, 4.5, 8, 9]
or
self.mytree.to_array() == [4.5, 2, 1, 0, 3, 2.5, 4, 8, 6, 9])
self.mytree.remove(6)
assert(self.mytree.to_array() == [4.5, 2, 1, 0, 3, 2.5, 4, 8,
9])
self.mytree.remove(4.5)
assert(self.mytree.to_array() == [4, 2, 1, 0, 3, 2.5, 8, 9])
self.mytree.remove(4)
assert(self.mytree.to_array() == [3, 2, 1, 0, 2.5, 8, 9] or
self.mytree.to_array() == [8, 2, 1, 0, 3, 2.5, 9])
self.mytree.remove(3)
assert(self.mytree.to_array() == [2.5, 2, 1, 0, 8, 9] or
self.mytree.to_array() == [8, 2, 1, 0, 2.5, 9])
self.mytree.remove(2.5)
assert(self.mytree.to_array() == [8, 2, 1, 0, 9])
self.mytree.remove(8)
assert(self.mytree.to_array() == [9, 2, 1, 0] or
self.mytree.to_array() == [2, 1, 0, 9])
self.mytree.remove(2)
assert(self.mytree.to_array() == [9, 1, 0] or
self.mytree.to_array() == [1, 0, 9])
self.mytree.remove(1)
assert(self.mytree.to_array() == [9, 0])
self.mytree.remove(0)
assert(self.mytree.to_array() == [9])
# RESET
self.setUp()
self.mytree.remove(4.5)
self.mytree.remove(4)
self.mytree.remove(2.5)
self.mytree.remove(3)
self.mytree.remove(0)
self.mytree.remove(1)
self.mytree.remove(2)
self.mytree.remove(6)
assert(self.mytree.to_array() == [5, 8, 9])
self.mytree.remove(5)
assert(self.mytree.to_array() == [8, 9])
self.mytree.remove(8)
assert(self.mytree.to_array() == [9])
def test_contains(self):
assert(self.mytree.contains(5) == True)
assert(self.mytree.contains(4) == True)
assert(self.mytree.contains(3) == True)
assert(self.mytree.contains(2) == True)
assert(self.mytree.contains(1) == True)
assert(self.mytree.contains(0) == True)
assert(self.mytree.contains(9) == True)
assert(self.mytree.contains(80) == False)
assert(self.mytree.contains(-5) == False)
assert(self.mytree.contains(-10) == False)
assert(self.mytree.contains(230) == False)
assert(self.mytree.contains(340) == False)
def test_get_node(self):
acquired_node0 = self.mytree.get_node(2)
acquired_node1 = self.mytree.get_node(8)
acquired_node2 = self.mytree.get_node(4.5)
acquired_node3 = self.mytree.get_node(45)
assert(acquired_node0.data == 2)
assert(acquired_node0.left.data == 1)
assert(acquired_node0.right.data == 3)
assert(acquired_node1.data == 8)
assert(acquired_node1.left.data == 6)
assert(acquired_node1.right.data == 9)
assert(acquired_node2.data == 4.5)
assert(acquired_node2.left == None)
assert(acquired_node2.right == None)
assert(acquired_node3 == None)
def test_to_array(self):
assert(self.bst.to_array() == [])
self.bst.insert_data(5)
self.bst.insert_data(2)
self.bst.insert_data(8)
self.bst.insert_data(6)
self.bst.insert_data(1)
self.bst.insert_data(3)
self.bst.insert_data(0)
self.bst.insert_data(4)
self.bst.insert_data(9)
assert(self.bst.to_array() == [5, 2, 1, 0, 3, 4, 8, 6, 9])
assert(self.mytree.to_array() == [5, 2, 1, 0, 3, 2.5, 4, 4.5, 8, 6,
9])
def test_size(self):
assert(self.mytree.size() == 11)
self.mytree.remove(4.5)
assert(self.mytree.size() == 10)
self.mytree.remove(4)
assert(self.mytree.size() == 9)
self.mytree.insert_data(30)
assert(self.mytree.size() == 10)
if __name__ == "__main__":
unittest.main()