0% found this document useful (0 votes)
26 views

Binary Search Trees

The document discusses binary search trees (BSTs). It defines BSTs as binary trees where values are placed in a way that supports efficient searching. It describes the properties that must hold for a tree to be a BST, including that all values in the left subtree must be less than the current node and all values in the right subtree must be greater. It then discusses insertion, removal, and traversal operations on BSTs.

Uploaded by

smitrajsinh1873
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
26 views

Binary Search Trees

The document discusses binary search trees (BSTs). It defines BSTs as binary trees where values are placed in a way that supports efficient searching. It describes the properties that must hold for a tree to be a BST, including that all values in the left subtree must be less than the current node and all values in the right subtree must be greater. It then discusses insertion, removal, and traversal operations on BSTs.

Uploaded by

smitrajsinh1873
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 19

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 2: 2 is < 4 so it goes left

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

Insert 1: 1 is < 4 so it goes to left of 4. 1 is also < 2 so it goes to left of 2

Insert 6: 6 is > 4 so it goes to right of 4. 6 is also > 5 so it goes to right of 5

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 start with the following binary search tree:

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.

Thus our tree is now:


Now suppose we wanted to remove a node like 8. We cannot simply make 4 point to 8's
child as there are 2 children. While it is possible to do something like make parent point
to right child then attach left child to the left most subtree of the right right child, doing
so would cause the tree to be bigger and is not a good solution.

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.

In the end we have:


Traversals
There are a number of functions that can be written for a tree that involves iterating
through all nodes of the tree exactly once. As it goes through each node exactly 1 time,
the runtime should not exceed O(n)

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.

Depth First Traversals

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

4, 2, 1, 3, 8, 6, 5, 7, 14, 13, 9, 11, 10, 12, 15, 16

Inorder traversals:

 visit its left subtree


 visit a node
 visit its right subtree

1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16

Notice that this type of traversal results in values being listed in its sorted order
Postorder traversals:

 visit its left subtree


 visit its right subtree
 visit a node

1, 3, 2, 5, 7, 6, 10, 12, 11, 9, 13, 16, 15, 14

This is the type of traversal you would use to destroy a tree.

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.

Given the following tree:

A breadthfirst traversal would result in:

4, 2, 8, 1, 3, 6, 14, 5, 7, 13, 15, 9, 16, 11, 10, 12

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

#BST's init function


def __init__(self):
self.root = None

def insert(self, data):


...

def inorder_print(self, data):


...

def pre_order_print(self, data):


...

def breadthfirst_print(self, data):


...

def search(self, data):


...

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.

template <typename T>


class BST{
struct Node{
T data_;
Node* left_;
Node* right_;
Node(const T& data, Node* left=nullptr, Node* right=nullptr){
data_=data;
left_=left;
right_=right;
}
};
//single data member pointing to root of tree
Node* root_;
public:
BST(){
root_=nullptr;
}
...
};

Iterative Methods
This section looks at the functions that are implemented iteratively (or the iterative
version of the functions)

Insert - Iterative version


This function will insert data into the tree. There are two ways to implement this
function, either iteratively or recursively. We will start by looking at the iterative solution.
In this situation, we begin by taking care of the empty tree situation. If the tree is empty
we simply create a node and make root_ point to that only node. Otherwise, we need to
go down our tree until we find the place where we must do the insertion and then
create the node.

 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

#BST's init function


def __init__(self):
self.root = None

def insert(self, data):


if self.root == None:
self.root = BST.Node(data)
else:
# curr points to the variable we are currently looking at
curr = self.root
inserted = False
while not inserted :
if(data < curr.data):
if(curr.left != None):
curr=curr.left
else:
curr.left = BST.Node(data)
inserted = True
else:
if (curr.right != None):
curr=curr.right
else:
curr.right= BST.Node(data)
inserted = True

Search - Iterative version


The key operation that is supported by a binary search tree is search. For our purposes
we will simply look at finding out whether or not a value is in the tree or not. The search
operation should never look at the entire tree. The whole point of the binary search tree
is to make this operation fast. We should search it so that we can eliminate a portion of
the tree with every search operation.

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

#BST's init function


def __init__(self):
self.root = None

def search(self, data):


curr = self.root
while curr != None :
if(data < curr.data):
curr=curr.left
elif data > curr.data:
curr=curr.right
else:
return curr
return None

Breadth First Print


Writing a breadth-first traversal involves using the queue data structure to order what
nodes to deal with next. You want to deal with the nodes from top to bottom left to
right, and thus you use the queue to order the nodes. Here is an example of how we will
do this.

We begin by declaring a queue (initially empty)

Prime the Queue


We start prime the queue by putting the root into the queue. In this example, we always
check to ensure no nullptrs are ever added to the queue. Alternatively we allow the
addition of nullptrs and decide how to deal with them when we dequeue.

Dequeue front of node and process it by adding its non-nullptr children into the queue,
print the node

Continue by dequeuing front node and processing it in same manner


Repeat again as 25 only has a right child only it is added

Repeat once again with 5 which has no children thus nothing is added to queue

Repeat again with 15 (also no children)


Repeat with 35 and add its children

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

#BST's init function


def __init__(self):
self.root = None

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 = " ")

You might also like