Binary Search Tree
Is an ordered dictionary ADT.
Operations with k = key, v = value:
This is also called Ordered Symbol Tables
Operations with k = key, v = value:
- insert(k, v):inserts an element with value v and key k
- search(k): returns the value with key k
- delete(k): deletes the element with key k
- contains(k): true if the dictionary contains an element with key k
- floor(k): returns next key ≤ k
- ceiling(k): returns next key ≥ k
- size(): returns the size of the dictionary
This is also called Ordered Symbol Tables
The left child is smaller than the parent and the right child is larger than the parent.
The height is the number of edges on the longest path from node to a leaf
Depth of anode is the number of edges to the root.
Height of tree = depth of deepest node
Properties
- Nodes left subtree contains keys with strictly less than node's keys
- Nodes right subtree contains keys with strictly more than node's keys.
- The left and right subtrees are binary trees
- There cannot be the same keys
No
-> repeats
-> repeats
Problem:
We are doing a charity call but we need to know exactly how many families earn exactly $a amount and less than $a amount or more than $a amount.
What kind of Operation do we need?
Operations:
- height(): returns the height of the node
- search(k) : returns an object v with key k
- searchMin() : finds the minimum key k
- searchMax() : finds the maximum key k
- successor(): returns next key > k
- predecessor(): returns next key < k
- insert(k,v): inserts an object v with key k
- delete(k) : deletes the node with key k
Height
Makes use of recursion to get the height.
We will check the height of the left and the right tree and return the biggest +1
ie. h(o) = max(h(o.left),h(o.right)) +1
ie. h(o) = max(h(o.left),h(o.right)) +1
We will stop checking when reach the base case,
ie. h(o) = 0 where o is the tree
PSEUDOCODE
function height()
leftHeight = -1 //this is a zero becos we will return a +1 (Basecase)
rightHeight = -1
if m_leftTree is not null
leftHeight = m_leftTree.height()
if m_rightTree is not null
rightHeight = m_rightTree.height()
return max(leftHeight, rightHeight) + 1
ie. h(o) = 0 where o is the tree
PSEUDOCODE
function height()
leftHeight = -1 //this is a zero becos we will return a +1 (Basecase)
rightHeight = -1
if m_leftTree is not null
leftHeight = m_leftTree.height()
if m_rightTree is not null
rightHeight = m_rightTree.height()
return max(leftHeight, rightHeight) + 1
In a standard BST with N nodes, what is worst case height?
->O(n)
It can be one sided and form a chain
Searching
We binary search inside a bst using recussion
If the searchingkey < currentKey
-> Go left
-> If left is null, return not found
else continue recusion
else if searchingkey > currentKey
-> Go Right
-> if right is null, return not found
else
return this
MinMax keys
The minimum key is at the left most node and the maximum key is the right most node
function searchMin()
if m_leftTree is not null
return m_leftTree.searchMin()
if m_leftTree is not null
return m_leftTree.searchMin()
return this
function searchMax()
if m_rightTree is not null
return m_rightTree.searchMax()
return this
In the standard BST, which search have the worst time case?
Searchmin , searchmax , search
->They are all the same O(h)
Successor
Successor is the node with the smallest key that is larger than K
If o has right subtree
-> min of right tree
else
If o is left child, its parent is successor
-> Move up one
If o is right child, the successor is an ancestor that is bigger than myself
-> Keep going up till find bigger
function successor() if m_rightTree is not null then return m_rightTree.searchMin() else |
while (p is not null) and (temp == p.rightTree)
//continue going up, stop loop when i am left tree
temp = p, p = temp.m_parent //change p into my parent
if p is null then return -1 //theres no successor
else return p
O(logn)/O(h)
For predecessor, everything reversed,
continue the loop if I am still left tree,
stop when I am the right child,
Return parent
Insertion
if insertedKey > current
-> go right
-> if key is null, put here //create new node right
else recurse insert
else if insertedKey < current
-> go left
-> Key is null, put here //create new node left
else recurse insert
return
function insert(Key k)
if k < m_key
if m_leftTree is not null
return m_leftTree.insert(k)
else
m_leftTree = new BinaryTree(k)
else if k > m_key
if m_rightTree is not null
return m_rightTree.insert(k)
else
m_rightTree = new BinaryTree(k)
else return
function insert(Key k)
if k < m_key
if m_leftTree is not null
return m_leftTree.insert(k)
else
m_leftTree = new BinaryTree(k)
else if k > m_key
if m_rightTree is not null
return m_rightTree.insert(k)
else
m_rightTree = new BinaryTree(k)
else return
Just need to go left or right and check for null each time, insert when see null
If not continue recursion
This is not AVL, we don't need to make it balance/ do swaps
This is the reason why there is a case where the bst is lopsided.
O(h)
Shape of the tree & Order of Insertion
Performance of searching and inserts depends on height.Height depends on shape
Shape depends on order of insertion (and deletions)
But it is not infinite amount of shapes when inserted in different order. There is always the same amount of shapes of BST. But they might be different to each other.
Delete
- Node not found, do nothing
- Node is a leaf, remove
- Node has one child, remove node and connect child to parent
- Node has 2 children, replace node with its successor. ,
if successor have child, reattach loose successor nodes with parents
if successor have child, reattach loose successor nodes with parents
Condition:
1. Successor s maintains the BST property when inserted to where k use to be
2. The successor s has at most 1 right child so we can reattach
O(h)
-> Traversal + Successor
O(h)
-> Traversal + Successor
Ceiling
-> Number does not have to be from the tree.
function ceiling(Node x, k)
if x is null then return null
if x.key == k then return x //if equal
if k > x.key then //go right
return ceiling(x.righttree,K)
if k < x.key then
t= ceiling(x.leftTree,k)
if x is null then return null
if x.key == k then return x //if equal
if k > x.key then //go right
return ceiling(x.righttree,K)
if k < x.key then
t= ceiling(x.leftTree,k)
// enter left tree check if its larger than key, if it is then recurse there
if t is not null, return t
if t is not null, return t
else return me
O(h)
Printing nodes in Order
1. InOrder
Left, self, Right
function in-order-traversal()
// Traverse left sub-tree
if m_leftTree is not null
m_leftTree.in-order-traversal()
visit(this)
// Traverse right sub-tree
if m_rightTree is not null then
m_rightTree.in-order-traversal()
- If theres a left subtree, traverse left
- else print myself, the right subtree downwards then go to parent
Left, self, Right
function in-order-traversal()
// Traverse left sub-tree
if m_leftTree is not null
m_leftTree.in-order-traversal()
visit(this)
// Traverse right sub-tree
if m_rightTree is not null then
m_rightTree.in-order-traversal()
- If theres a left subtree, traverse left
- else print myself, the right subtree downwards then go to parent
2. Pre-Order (Deep-Copy)
Self, Left, Right
function deepCopy()
n = new Node(key, value)
// Traverse left sub-tree
if m_leftTree is not null
n.m_leftTree = m_leftTree.deepCopy()
// Traverse right sub-tree
if m_rightTree is not null
n.m_rightTree = m_rightTree.deepCopy()
return n
function deepCopy()
n = new Node(key, value)
// Traverse left sub-tree
if m_leftTree is not null
n.m_leftTree = m_leftTree.deepCopy()
// Traverse right sub-tree
if m_rightTree is not null
n.m_rightTree = m_rightTree.deepCopy()
return n
3. Post-Order (Good for deleting)
Left, Right, Self
function deleteTree(Node n)
// Traverse left sub-tree
if n.m_leftTree is not null
deleteTree(n.m_leftTree)
// Traverse right sub-treeif n.m_rightTree is not null then
deleteTree(n.m_rightTree)
free(n)
function deleteTree(Node n)
// Traverse left sub-tree
if n.m_leftTree is not null
deleteTree(n.m_leftTree)
// Traverse right sub-treeif n.m_rightTree is not null then
deleteTree(n.m_rightTree)
free(n)