Binary Search Trees
Binary Search Trees
Binary search trees (BST) are binary trees where values are placed in a way that supports
efficient searching. In a BST, all values in the left subtree value in current node < all
values in the right subtree. This rule must hold for EVERY subtree, ie every subtree must
be a binary search tree if the whole tree is to be a binary tree. The following is a binary
search tree:
The following is NOT a binary search tree as the values are not correctly ordered:
A binary search tree allows us to quickly perform a search on a linking structure (under
certain conditions which we will explore later). To find a value, we simply start at the
root and look at the value. If our key is less than the value, search left subtree. If key is
greater than value search right subtree. It provides a way for us to do a "binary search"
on a linked structure which is not possible with a linear linked list. During the search, we
will never have to search the subtrees we eliminate in the search process... thus at worst,
searching for a value in a binary search tree is equivalent to going through all the nodes
from the root to the furthest leaf in the tree.
Insertion
To insert into a binary search tree, we must maintain the nodes in sorted order. There is
only one place an item can go. Example Insert the numbers 4,2,3,5,1, and 6 in to an
initially empty binary search tree in the order listed. Insertions always occur by inserting
into the first available empty subtree along the search path for the value:
Insert 4:
Insert 3: 3 is less than 4 but more than 2 so it goes to left of 4, but right of 2
Insert 5: 5 is > 4 so it goes right of 4
Removal
In order to delete a node, we must be sure to link up the subtree(s) of the node
properly. Let us consider the following situations.
Suppose we were to remove 7 from the tree. Removing this node is relatively simple, we
simply have to ensure that the pointer that points to it from node 6 is set to nullptr and
delete the node:
Now, Lets remove the node 6 which has only a left child but no right child. This is also
easy. all we need to do is make the pointer from the parent node point to the left child.
Instead, we should find the inorder successor (next biggest descendent) to 8 and
promote it so that it replaces 8.
The inorder successor can be found by going to the right child of 8 then going as far left
as possible. In this case, 9 is the inorder sucessor to 8:
The inorder successor will either be a leaf node or it will have a right child only. It will
never have a left child because we found it by going as far left as possible.
Once found, we can promote the inorder successor to take over the place of the node
we are removing. The parent of inorder successor must link to the right child of the
inorder succesor.
These include functions such as print, copy, even the code for destroying the structure is
a type of traversal.
Traversals can be done either depth first (follow a branch as far as it will go before
backtracking to take another) or breadfirst, go through all nodes at one level before
going to the next.
There are generally three ordering methods for depth first traversals. They are:
preorder
inorder
postorder
In each of the following section, we will use this tree to describe the order that the
nodes are "visited". A visit to the node, processes that node in some way. It could be as
simple as printing the value of the node:
Preorder traversals
visit a node
visit its left subtree
visit its right subtree
Inorder traversals:
Notice that this type of traversal results in values being listed in its sorted order
Postorder traversals:
Breadth-First Traversal
A breadfirst traversal involves going through all nodes starting at the root, then all its
children then all of its children's children, etc. In otherwords we go level by level.
BST Implemenation
To implement a binary search tree, we are going to borrow some concepts from linked lists as
there are some parts that are very similar. In these notes we will look a few of the functions and
leave the rest as an exercise.
Similar to a linked list, A binary search tree is made up of nodes. Each node can have a left or
right child, both of which could be empty trees. Empty trees are represented as nullptrs. The
binary search tree object itself only stores a single pointer that points at the root of the entire tree.
The data stored within the nodes must be of some type that is comparable. We will thus begin
our binary search tree class declaration in a similar manner to that of a linked list. The code for
each of the functions will be filled in later in this chapter.
Python
C++
class BST:
class Node:
# Node's init function
def __init__(self,data=None,left=None,right=None):
self.data = data
self.left = left
self.right = right
If you rename the data members above you actually see that its pretty similar to that of a doubly
linked list... The key to the operations of a BST lies not in what data is declared, but rather how
we organize the nodes. The next 2 sections of the notes we will look at the implementation of the
functions listed above. In some cases a function may be written both iteratively and recursively
and both versions will be looked at
Constructors
When we create our tree, we are going to start with an empty tree. Thus, our constructor simply
needs to initialize the data member to nullptr.
Iterative Methods
This section looks at the functions that are implemented iteratively (or the iterative
version of the functions)
Python
C++
class BST:
class Node:
# Node's init function
def __init__(self,data=None,left=None,right=None):
self.data = data
self.left = left
self.right = right
To do this we start at the root and compare that node's data against what we want. If it
matches, we have found it. If not, we go either left or right depending on how data
relates to the current node. If at any point we have an empty tree (ie the pointer we are
using for iterating through the tree becomes nullptr) we stop the search and return
false. If we find a node that matches we stop and return true.
Python
C++
class BST:
class Node:
# Node's init function
def __init__(self,data=None,left=None,right=None):
self.data = data
self.left = left
self.right = right
Dequeue front of node and process it by adding its non-nullptr children into the queue,
print the node
Repeat once again with 5 which has no children thus nothing is added to queue
Continue by removing 30
And one more time with 40
At this point queue is empty and thus, we have completed our breadthfirst print of the
tree.
In code form, this is how we will write our code (we assume we have a template queue
available:
Python
C++
class BST:
class Node:
# Node's init function
def __init__(self,data=None,left=None,right=None):
self.data = data
self.left = left
self.right = right
def breadth_first_print(self):
theNodes = Queue()
if(self.root != None):
theNodes.enqueue(self.root)
while(not theNodes.isEmpty()):
curr = theNodes.front()
theNodes.dequeue();
if(curr.left):
theNodes.enqueue(curr.left)
if(curr.right):
theNodes.enqueue(curr.right)
print(curr.data, end = " ")