This notebook was prepared by Marco Guajardo. Source and license info is on [GitHub](https://github.com/donnemartin/interactive-coding-challenges).

Solution Notebook

Problem: Implement a binary search tree with insert, delete, different traversals & max/min node values

Constraints

  • Is this a binary tree?
    • Yes
  • Is the root set to None initially?
    • Yes
  • Do we care if the tree is balanced?
    • No
  • What do we return for the traversals?
    • Return a list of the data in the desired order
  • What type of data can the tree hold?
    • Assume the tree only takes ints. In a realistic example, we'd use a hash table to convert other types to ints.

Test Cases

Insert

  • Always start with the root
  • If value is less than the root, go to the left child
  • if value is more than the root, go to the right child

Delete

  • Deleting a node from a binary tree is tricky. Make sure you arrange the tree correctly when deleting a node.
  • Here are some basic instructions
  • If the value to delete isn't on the tree return False

Traverals

  • In order traversal -left, center, right
  • Pre order traversal - center, left, right
  • Post order traversal - left, right, center
  • Return list for all traverals

Max & Min

  • Find the max node in the binary search tree
  • Find the min node in the binary search tree

treeIsEmpty

  • check if the tree is empty

Algorithm

Refer to the Solution Notebook. If you are stuck and need a hint, the solution notebook's algorithm discussion might be a good place to start.

Algorithm

Insert

  • If root is none, insert at root
  • Else
    • While node is not None
      • if value is less go left child
      • If value is more go right child
  • Time complexity: O(log(n))
  • Space complexity: O(n)

Min Node

  • Keep going to the left child until you reach None and return the value
  • Time complexity: O(log(n))
  • Space complexity: O(n)

Max Node

  • Keep going to the right child until you reach None and return the value
  • Time complexity: O(log(n))
  • Space complexity: O(n)

Traversals

  • In order

    • While the node is not None
      • Call left child recursively
      • Append data
      • Call right child recursively
  • Post order

    • While the node is not None
      • Call left child recursively
      • Call right child recursively
      • Append data
  • Pre order

    • While the node is not None
      • Append data
      • Call left child recursively
      • Call right child recursively
  • Time complexity: O(n) for all traversals
  • Space complexity: O(n)

Delete

  • First, find value to delete
  • If value is not in tree
    • Return False
  • If value found
    • Check if the node is a left child or right child
      • If node is left child
        • Find the biggest value in all the node's children and replace it with it
      • If node is right child
        • Find the smalles value in all the node's children and replace it with it
  • Time complexity: O(log(n))
  • Space complexity: O(n)

Code


In [7]:
%%writefile binary_search_tree.py

class Node (object):
	def __init__ (self, data):
		self.data = data
		self.rightChild = None
		self.leftChild = None

class BinaryTree (object):
	def __init__ (self):
		self.root = None

	def insert (self, newData):
		leaf = Node(newData)

		if self.root is None:
			self.root = leaf
		else:
			current = self.root
			parent = self.root
			while current is not None:
				parent = current
				if newData < current.data:
					current = current.leftChild
				else:
					current = current.rightChild

			if newData < parent.data:
				parent.leftChild = leaf
			else:
				parent.rightChild = leaf

	# returns false if the item to be deleted is not on the tree
	def delete (self, data):
		current = self.root
		parent = self.root
		isLeft = False

		if current is None:
			return False

		while current is not None and current.data is not data:
			parent = current
			if data < current.data:
				current = current.leftChild
				isLeft = True 
			else:
				current = current.rightChild
				isLeft = False

		if current is None:
			return False

		if current.leftChild is None and current.rightChild is None:
			if current is self.root:
				self.root = None
			elif isLeft:
				parent.leftChild = None
			else:
				parent.rightChild = None

		elif current.rightChild is None:
			if current is self.root:
				self.root = current.leftChild
			elif isLeft:
				parent.leftChild = current.leftChild
			else:
				parent.rightChild = current.leftChild

		elif current.rightChild is None:
			if current is self.root:
				self.root = current.rightChild
			elif isLeft:
				parent.lChild = current.rightChild
			else:
				parent.rightChild = current.rightChild

		else:
			succesor = current.rightChild
			succesorParent = current

			while succesor.leftChild is not None:
				succesorParent = succesor
				succesor = succesor.leftChild

			if current is self.root:
				self.root = succesor
			elif isLeft:
				parent.leftChild = succesor
			else:
				parent.rightChild = succesor

			succesor.leftChild = current.leftChild

			if succesor is not current.rightChild:
				succesorParent.leftChild = succesor.rightChild
				succesor.rightChild = current.rightChild

		return True 


	def minNode (self):
		current = self.root
		while current.leftChild is not None:
			current = current.leftChild

		return current.data

	def maxNode (self):
		current = self.root
		while current.rightChild is not None:
			current = current.rightChild

		return current.data

	def printPostOrder (self):
		global postOrder
		postOrder = []

		def PostOrder(node):
			if node is not None:
				PostOrder(node.leftChild)
				PostOrder(node.rightChild)
				postOrder.append(node.data)

		PostOrder(self.root)
		return postOrder

	def printInOrder (self):
		global inOrder 
		inOrder = []

		def InOrder (node):
			if node is not None:
				InOrder(node.leftChild)
				inOrder.append(node.data)
				InOrder(node.rightChild)

		InOrder(self.root)
		return inOrder

	def printPreOrder (self):
		global preOrder
		preOrder = []

		def PreOrder (node):
			if node is not None:
				preOrder.append(node.data)
				PreOrder(node.leftChild)
				PreOrder(node.rightChild)

		PreOrder(self.root)
		return preOrder

	def treeIsEmpty (self):
		return self.root is None


Overwriting binary_search_tree.py

In [8]:
%run binary_search_tree.py

In [9]:
%%writefile test_binary_search_tree.py
from nose.tools import assert_equal

class TestBinaryTree(object):

	def test_insert_traversals (self):
		myTree = BinaryTree()
		myTree2 = BinaryTree()
		for num in [50, 30, 70, 10, 40, 60, 80, 7, 25, 38]:
			myTree.insert(num)
		[myTree2.insert(num) for num in range (1, 100, 10)]

		print("Test: insert checking with in order traversal")
		expectVal = [7, 10, 25, 30, 38, 40, 50, 60, 70, 80]
		assert_equal(myTree.printInOrder(), expectVal)
		expectVal = [1, 11, 21, 31, 41, 51, 61, 71, 81, 91]
		assert_equal(myTree2.printInOrder(), expectVal)

		print("Test: insert checking with post order traversal")
		expectVal = [7, 25, 10, 38, 40, 30, 60, 80, 70, 50]
		assert_equal(myTree.printPostOrder(), expectVal)
		expectVal = [91, 81, 71, 61, 51, 41, 31, 21, 11, 1]
		assert_equal(myTree2.printPostOrder(), expectVal)


		print("Test: insert checking with pre order traversal")
		expectVal = [50, 30, 10, 7, 25, 40, 38, 70, 60, 80]
		assert_equal(myTree.printPreOrder(), expectVal)
		expectVal = [1, 11, 21, 31, 41, 51, 61, 71, 81, 91]
		assert_equal(myTree2.printPreOrder(), expectVal)


		print("Success: test_insert_traversals")

	def test_max_min_nodes (self):
		myTree = BinaryTree()
		myTree.insert(5)
		myTree.insert(1)
		myTree.insert(21)

		print("Test: max node")
		assert_equal(myTree.maxNode(), 21)
		myTree.insert(32)
		assert_equal(myTree.maxNode(), 32)

		print("Test: min node")
		assert_equal(myTree.minNode(), 1)

		print("Test: min node inserting negative number")
		myTree.insert(-10)
		assert_equal(myTree.minNode(), -10)

		print("Success: test_max_min_nodes")

	def test_delete (self):
		myTree = BinaryTree()
		myTree.insert(5)

		print("Test: delete")
		myTree.delete(5)
		assert_equal(myTree.treeIsEmpty(), True)
		
		print("Test: more complex deletions")
		[myTree.insert(x) for x in range(1, 5)]
		myTree.delete(2)
		assert_equal(myTree.root.rightChild.data, 3)
        
		print("Test: delete invalid value")
		assert_equal(myTree.delete(100), False)


		print("Success: test_delete")

def main():
    testing = TestBinaryTree()
    testing.test_insert_traversals()
    testing.test_max_min_nodes()
    testing.test_delete()
    
if __name__=='__main__':
    main()


Overwriting test_binary_search_tree.py

In [10]:
%run -i test_binary_search_tree.py


Test: insert checking with in order traversal
Test: insert checking with post order traversal
Test: insert checking with pre order traversal
Success: test_insert_traversals
Test: max node
Test: min node
Test: min node inserting negative number
Success: test_max_min_nodes
Test: delete
Test: more complex deletions
Test: delete invalid value
Success: test_delete