Binary Search Tree Flashcards
What is a Binary Search Tree?
BST, like a BT, is a rooted tree, stores a key, and left and right subtree.
Properies:
each node must be greater than all keys in the left subtree and not greater that all key in the right subtree
write a recursive function to insert key and construct a BST from a list.
we examine the root and recursively insert the new node to the left subtree if its key is less than that of the root or the right subtree if its key is greater than or equal to the root.
class Node: left = right = None
# Function to create a new binary tree node having a given key def \_\_init\_\_(self, data): self.data = data
# Function to perform inorder traversal on the tree def inorder(root):
if root is None: return inorder(root.left) print(root.data, end=' ') inorder(root.right)
# Recursive function to insert a key into a BST def insert(root, key):
# if the root is None, create a new node and return it if root is None: return Node(key)
# if the given key is less than the root node, # recur for the left subtree if key < root.data: root.left = insert(root.left, key)
# otherwise, recur for the right subtree else: # key >= root.data root.right = insert(root.right, key)
return root
# Function to construct a BST from given keys def constructBST(keys): root = None for key in keys: root = insert(root, key) return root
if __name__ == ‘__main__’:
keys = [15, 10, 20, 8, 12, 16, 25] root = constructBST(keys) inorder(root)
Write an iterative function to insert the key in a binary tree and construct a binary search tree.
In iterative solution we keep recording of the parent node.
1) if root is none: make a node and return root
2) parent = None, cur = root
3) while root: parent = cur
of key < cur.data: cur = cur.left
Write an iterative function to insert the key in a binary tree and construct a binary search tree.
In iterative solution we keep recording of the parent node.
1) if root is none: make a node and retur root
2) parent = None, cur = root
3) while root: parent = cur
if key < cur.data: cur = cur.left
else: cur = cur.left
4) check parent.data with key to insert the key to the left or right of key
############### code # A class to store a BST node class Node: # Function to create a new binary tree node having a given key def \_\_init\_\_(self, key): self.data = key self.left = self.right = None
# Function to perform inorder traversal on the tree def inorder(root):
if root is None: return inorder(root.left) print(root.data, end=' ') inorder(root.right)
# Iterative function to insert a key into a BST def insertIterative(root, key):
# start with the root node curr = root
# pointer to store the parent of the current node parent = None
# if the tree is empty, create a new node and set it as root if root is None: return Node(key)
# traverse the tree and find the parent node of the given key while curr:
# update the parent to the current node parent = curr
# if the given key is less than the current node, # go to the left subtree; otherwise, go to the right subtree. if key < curr.data: curr = curr.left else: curr = curr.right
# construct a node and assign it to the appropriate parent pointer if key < parent.data: parent.left = Node(key) else: parent.right = Node(key)
return root
# Function to construct a BST from given keys def constructBST(keys): root = None for key in keys: root = insertIterative(root, key) return root
if __name__ == ‘__main__’:
keys = [15, 10, 20, 8, 12, 16, 25] root = constructBST(keys) inorder(root)
Search a given key in BST, recursive
We begin by examining the root node. If the tree is null, the key we are searching for does not exist in the tree. Otherwise, if the key equals that of the root, the search is successful, we return the node. If the key is less than that of the root, we search the left subtree. Similarly, if the key is greater than that of the root, we search the right subtree. This process is repeated until the key is found or the remaining subtree is null. If the searched key is not found after a null subtree is reached, then the key is not present in the tree.
On average, binary search trees with n nodes have O(log(n)) height. However, in the worst case, binary search trees can have O(n) height (for skewed trees where all the nodes except the leaf have one and only one child) when the unbalanced tree resembles a linked list. ############ code
# Recursive function to search in a given BST def search(root, key, parent):
# if the key is not present in the key if root is None: print('Key not found') return
# if the key is found if root.data == key:
if parent is None: print(f'The node with key {key} is root node') elif key < parent.data: print('The given key is the left node of the node with key', parent.data) else: print('The given key is the right node of the node with key', parent.data) return
# if the given key is less than the root node, recur for the left subtree; # otherwise, recur for the right subtree
if key < root.data: search(root.left, key, root) else: search(root.right, key, root)
if __name__ == ‘__main__’:
keys = [15, 10, 20, 8, 12, 16, 25] root = None for key in keys: root = insert(root, key) search(root, 25, None)
Search a given key in BST,iterative
def searchIterative(root, key):
# start with the root node curr = root
# pointer to store the parent of the current node parent = None
# traverse the tree and search for the key while curr and curr.data != key:
# update the parent to the current node parent = curr
# if the given key is less than the current node, go to the left subtree; # otherwise, go to the right subtree if key < curr.data: curr = curr.left else: curr = curr.right
# if the key is not present in the key if curr is None: print('Key not found') return
if parent is None: print(f'The node with key {key} is root node') elif key < parent.data: print('The given key is the left node of the node with key', parent.data) else: print('The given key is the right node of the node with key', parent.data)
if __name__ == ‘__main__’:
keys = [15, 10, 20, 8, 12, 16, 25] root = None for key in keys: root = insert(root, key) searchIterative(root, 25)
Delet a Node in a Binary serach tree, iterative
we need two function:
1) finding minimum from a tree, it is going to be a successor of a deleted node if deleted node has both left and right child
2) the main delting function
In writing delting function,
we have search function (parent and current), the we consider three situation:
1) Case 1: Deleting a node with no children: remove the node from the tree. parent.left =None or parent.right = Node
2) Case 2: Deleting a node with two children: call the node to be deleted N. Do not delete N. Instead, choose either its inorder successor node or its inorder predecessor node, R. Copy the value of R to N, then recursively call delete on R until reaching one of the first two cases. If we choose the inorder successor of a node, as the right subtree is not NULL (our present case is a node with 2 children), then its inorder successor is a node with the least value in its right subtree, which will have at a maximum of 1 subtree, so deleting it would fall in one of the first 2 cases.
3) Case 3: Deleting a node with one child: remove the node and replace it with its child.
################### code # Helper function to find minimum value node in the subtree rooted at `curr` def getMinimumKey(curr): while curr.left: curr = curr.left return curr #\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_ # Function to delete a node from a BST def deleteNode(root, key):
# pointer to store the parent of the current node parent = None
# start with the root node curr = root
# search key in the BST and set its parent pointer while curr and curr.data != key:
# update the parent to the current node parent = curr
# if the given key is less than the current node, go to the left subtree; # otherwise, go to the right subtree if key < curr.data: curr = curr.left else: curr = curr.right
# return if the key is not found in the tree if curr is None: return root
# Case 1: node to be deleted has no children, i.e., it is a leaf node if curr.left is None and curr.right is None:
# if the node to be deleted is not a root node, then set its # parent left/right child to None if curr != root: if parent.left == curr: parent.left = None else: parent.right = None
# if the tree has only a root node, set it to None else: root = None
# Case 2: node to be deleted has two children elif curr.left and curr.right:
# find its inorder successor node successor = getMinimumKey(curr.right)
# store successor value val = successor.data
# recursively delete the successor. Note that the successor # will have at most one child (right child) deleteNode(root, successor.data)
# copy value of the successor to the current node curr.data = val
# Case 3: node to be deleted has only one child else:
# choose a child node if curr.left: child = curr.left else: child = curr.right
# if the node to be deleted is not a root node, set its parent # to its child if curr != root: if curr == parent.left: parent.left = child else: parent.right = child
# if the node to be deleted is a root node, then set the root to the child else: root = child
return root
Delet a Node from BST recursively
Function to find the maximum value node in the subtree rooted at `ptr` def findMaximumKey(ptr): while ptr.right: ptr = ptr.right return ptr
#\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_ # Function to delete a node from a BST def deleteNode(root, key):
# base case: the key is not found in the tree if root is None: return root
# if the given key is less than the root node, recur for the left subtree if key < root.data: root.left = deleteNode(root.left, key)
# if the given key is more than the root node, recur for the right subtree elif key > root.data: root.right = deleteNode(root.right, key)
# key found else:
# Case 1: node to be deleted has no children (it is a leaf node) if root.left is None and root.right is None: # update root to None return None
# Case 2: node to be deleted has two children elif root.left and root.right: # find its inorder predecessor node predecessor = findMaximumKey(root.left)
# copy value of the predecessor to the current node root.data = predecessor.data
# recursively delete the predecessor. Note that the # predecessor will have at most one child (left child) root.left = deleteNode(root.left, predecessor.data)
# Case 3: node to be deleted has only one child else: # choose a child node child = root.left if root.left else root.right root = child
return root
Construct a balanced BST from the given keys
Given an unsorted integer array that represents binary search tree (BST) keys, construct a height-balanced BST from it. For each node of a height-balanced tree, the difference between its left and right subtree height is at most 1.
The height of such BST in the worst-case can be as much as the total number of keys in the BST. The worst case happens when given keys are sorted in ascending or descending order, and we get a skewed tree where all the nodes except the leaf have one and only one child.
For height-balanced BSTs, with each comparison, skip about half of the tree so that each insertion operation takes time proportional to the logarithm of the total number of items n stored in the tree, i.e., log2n. This is much better than the linear time required to find items by key in an (unsorted) array or unbalanced trees.
We can easily modify the solution to get height-balanced BSTs if all keys are known in advance. The idea is to sort the given keys first. Then the root will be the middle element of the sorted array, and we recursively construct the left subtree of the root by keys less than the middle element and the right subtree of the root by keys more than the middle element.
The time complexity of the solution is O(n.log(n)), where n is the size of the BST, and requires space proportional to the tree’s height for the call stack.
code########
# A class to store a BST node class Node: def \_\_init\_\_(self, data, left=None, right=None): self.data = data self.left = left self.right = right
# Function to perform inorder traversal on the tree def inorder(root): if root is None: return
inorder(root.left) print(root.data, end=' ') inorder(root.right)
# Function to construct balanced BST from the given sorted list def construct(keys, low, high, root):
# base case if low > high: return root
# find the middle element of the current range mid = (low + high) // 2
# construct a new node from the middle element and assign it to the root root = Node(keys[mid])
# left subtree of the root will be formed by keys less than middle element root.left = construct(keys, low, mid - 1, root.left)
# right subtree of the root will be formed by keys more than the middle element root.right = construct(keys, mid + 1, high, root.right)
return root
# Function to construct balanced BST from the given unsorted list def constructBST(keys):
# sort the keys first keys.sort()
# construct a balanced BST and return the root node of the tree return construct(keys, 0, len(keys) - 1, None)
if __name__ == ‘__main__’:
# input keys keys = [15, 10, 20, 8, 12, 16, 25]
# construct a balanced binary search tree root = constructBST(keys)
# print the keys in an inorder fashion inorder(root)
Determine whether a given binary tree is a BST or not- using recursive and greedy seacrch
nstead of deciding based solely on a node’s values and its children, we also need information flowing down from the parent. In the tree above, if we could remember about the node containing the value 20, we would see that the node with value 5 is violating the BST property contract.
So, the condition we need to check at each node is:
If the node is the left child of its parent, it must be smaller than (or equal to) the parent, and it must pass down the value from its parent to its right subtree to make sure none of the nodes in that subtree is greater than the parent.
If the node is the right child of its parent, it must be larger than the parent, and it must pass down the value from its parent to its left subtree to make sure none of the nodes in that subtree is lesser than the parent.
The time complexity of the above solution is O(n), where n is the size of the BST, and requires space proportional to the tree’s height for the call stack.
code
def isBST(node, minKey, maxKey):
# base case if node is None: return True
# if the node's value falls outside the valid range if node.data < minKey or node.data > maxKey: return False
# recursively check left and right subtrees with an updated range return isBST(node.left, minKey, node.data) and \ isBST(node.right, node.data, maxKey)
# Function to determine whether a given binary tree is a BST def checkForBST(root):
if isBST(root, -sys.maxsize, sys.maxsize): print('The tree is a BST.') else: print('The tree is not a BST')
def swap(root):
left = root.left root. left = root.right root. right = left
Determine whether a given binary tree is a BST or not-
Using inorder subtree
We know that an inorder traversal of a binary search tree returns the nodes in sorted order. To determine whether a given binary tree is a BST, keep track of the last visited node while traversing the tree. Then for each encountered node in the inorder traversal, check whether the last visited node is smaller (or smaller/equal, if duplicates are to be allowed in the tree) compared to the current node.
code ##############
def isBST(root, prev):
# base case: empty tree is a BST if root is None: return True
# check if the left subtree is BST or not left = isBST(root.left, prev)
# value of the current node should be more than that of the previous node if root.data <= prev.data: return False
# update previous node data and check if the right subtree is BST or not prev.data = root.data return left and isBST(root.right, prev)
# Function to determine whether a given binary tree is a BST def checkForBST(node):
# pointer to store previously processed node in the inorder traversal prev = Node(-sys.maxsize)
# check if nodes are processed in sorted order if isBST(node, prev): print('The tree is a BST!') else: print('The tree is not a BST!')
Check if the given keys represent the same BSTs or not without building BST
Given two integer arrays, X and Y, representing a set of BST keys, check if they represent the same BSTs or not without building the tree. Assume that the keys are inserted into the BST in the same order as they appear in the array.
the time and sapve complexity is O(n2)
def isSameBST(X, Y, n):
# if no element is present in the list, return true if n == 0: return True
# if the first element differs in both lists (root node key), return false if X[0] != Y[0]: return False
# if the list contains only one key, return true if n == 1: return True
# take four auxiliary spaces of size `n-1` each (as maximum keys in # left or right subtree can be `n-1`) leftX = [None] * (n - 1) rightX = [None] * (n - 1) leftY = [None] * (n - 1) rightY = [None] * (n - 1) k = l = m = o = 0
# process the remaining keys and divide them into two groups for i in range(1, n):
# `leftX` will contain all elements less than `X[0]` if X[i] < X[0]: leftX[k] = X[i] k = k + 1
# `rightX` will contain all elements more than `X[0]` else: rightX[l] = X[i] l = l + 1
# `leftY` will contain all elements less than `Y[0]` if Y[i] < Y[0]: leftY[m] = Y[i] m = m + 1
# `rightY` will contain all elements more than `Y[0]` else: rightY[o] = Y[i] o = o + 1
# return false if the size of `leftX` and `leftY` differs, i.e., # the total number of nodes in the left subtree of both trees differs if k != m: return False
# return false if the size of `rightX` and `rightY` differs, i.e., # the total number of nodes in the right subtree of both trees differs if l != o: return False
# check left and right subtree return isSameBST(leftX, leftY, k) and isSameBST(rightX, rightY, l)
What is the Inorder Predecessor and Succssesor of a binary tree?
When you do inorder traversal of a Binary Tree, the neighbours of the given node are called predecessor and Successor.
Predecsessor: the right most element in the left subtree (findingMaximum(cur):
while cur.right:
cur = cur.right
return cur
Note: you call this function on cur.left)
Successor: The left most element in the right tree.
FindMinimum(cur):
while cur.left:
cur = cur.left
return cur
Note: you call this function on cur.right)
Find inorder predecessor for the given key in a BST
Given a BST, find the inorder predecessor of a given key in it. If the key does not lie in the BST, return the previous greater node (if any) present in the BST.
_______________________-
Inorder predecessor of a node locates on left subtree, right most child.
if the right most child is None, we need to find the ancesstor of a node is predecessor, i.e, the node visited before the given key.
If any such node is found, then the inorder predecessor is its parent;
otherwise it does not exist
O(n)
################## code############ # Function to find the maximum value node in a given BST def findMaximum(root): while root.right: root = root.right return root
# Iterative function to find inorder predecessor for a given key in a BST def findPredecessor(root, key):
# base case if not root: return None
prec = None while True:
# if the given key is less than the root node, visit the left subtree if key < root.data: root = root.left
# if the given key is more than the root node, visit the right subtree elif key > root.data: # update predecessor to the current node before visiting # right subtree prec = root root = root.right
# if a node with the desired value is found, the predecessor is the maximum # value node in its left subtree (if any) else: if root.left: prec = findMaximum(root.left) break
# if the key doesn't exist in the binary tree, return previous greater node if root is None: return prec
# return predecessor, if any return prec
# A class to store a BST node class Node: def \_\_init\_\_(self, data, left=None, right=None): self.data = data self.left = left self.right = right
# Recursive function to insert a key into a BST def insert(root, key):
# if the root is None, create a new node and return it if root is None: return Node(key)
# if the given key is less than the root node, recur for the left subtree if key < root.data: root.left = insert(root.left, key)
# if the given key is more than the root node, recur for the right subtree else: root.right = insert(root.right, key)
return root
if __name__ == ‘__main__’:
keys = [15, 10, 20, 8, 12, 16, 25]
''' Construct the following tree 15 / \ / \ 10 20 / \ / \ / \ / \ 8 12 16 25 '''
root = None for key in keys: root = insert(root, key) # find inorder predecessor for each key for key in keys: prec = findPredecessor(root, key) if prec: print(f'Predecessor of node {key} is {prec.data}') else: print('The predecessor does
write a function to search a key throughout a tree, return False if there is not such a node
def search(root, key):
traverse the tree and search for the key
while root:
# if the given key is less than the current node, go to the left
# subtree; otherwise, go to the right subtree
if root.data > key.data:
root = root.left
elif root.data < key.data: root = root.right # if node is found elif root.data == key.data: return True return False
Find the Lowest Common Ancestor (LCA) of two nodes in a BST
Given a BST and two nodes x and y in it, find the lowest common ancestor (LCA) of x and y. The solution should return null if either x or y is not the actual node in the tree.
One way is writing path 1 and path2 which are path of root to x and y. Then traverse both paths to find the last common element
We need to write 3 functions:
1) serach functiona and return True ( if node exists ) /False (if node does not exisi)
2) Assuming x and y are in the tree LCA(root, x, y)
3) final FindLCA(root, x, y), combining 1 and 2
We can recursively find the lowest common ancestor of nodes x and y present in the BST. The trick is to find the BST node, which has one key present in its left subtree and the other key present in the right subtree. If any such node is present in the tree, then it is LCA; if y lies in the subtree rooted at node x, then x is the LCA; otherwise, if x lies in the subtree rooted at node y, then y is the LCA.
##########################
# A class to store a BST node class Node: def \_\_init\_\_(self, data, left=None, right=None): self.data = data self.left = left self.right = right
# Recursive function to insert a key into a BST def insert(root, key):
# if the root is None, create a new node and return it if root is None: return Node(key)
# if the given key is less than the root node, recur for the left subtree if key < root.data: root.left = insert(root.left, key)
# if the given key is more than the root node, recur for the right subtree else: root.right = insert(root.right, key)
return root
# Iterative function to search a given node in a BST def search(root, key):
# traverse the tree and search for the key while root:
# if the given key is less than the current node, go to the left # subtree; otherwise, go to the right subtree
if key.data < root.data: root = root.left elif key.data > root.data: root = root.right # if the key is found, return true elif key == root: return True else: return False
# we reach here if the key is not present in the BST return False
# Recursive function to find the lowest common ancestor of given nodes # `x` and `y`, where both `x` and `y` are present in a BST def LCARecursive(root, x, y):
# base case: empty tree if root is None: return None
# if both `x` and `y` is smaller than the root, LCA exists in the left subtree if root.data > max(x.data, y.data): return LCARecursive(root.left, x, y)
# if both `x` and `y` are greater than the root, LCA exists in the right subtree elif root.data < min(x.data, y.data): return LCARecursive(root.right, x, y)
# if one key is greater (or equal) than the root and one key is smaller # (or equal) than the root, then the current node is LCA return root
# Print lowest common ancestor of two nodes in a BST def LCA(root, x, y):
# return if the tree is empty, or `x` or `y` is not present in the tree if not root or not search(root, x) or not search(root, y): return
# `lca` stores the lowest common ancestor of `x` and `y` lca = LCARecursive(root, x, y)
# if the lowest common ancestor exists, print it if lca: print('LCA is', lca.data) else: print('LCA does not exist')
Find Floor and Ceil in a Binary Search Tree- iteratively
Given a BST, find the floor and ceil of a given key in it. If the given key lies in the BST, then both floor and ceil are equal to that key; otherwise, the ceil is equal to the next greater key (if any) in the BST, and the floor is equal to the previous greater key (if any) in the BST.
The idea is simple – search for the given key in the tree and update the ceil to the current node before visiting its left subtree. Similarly, update the floor to the current node before visiting its right subtree. If the key is found in the BST, then the floor and ceil are equal to that key. If the key is not found in the BST, then the floor and ceil were already updated while searching for the key.
o(n), The auxiliary space required by the program is O(1).
code
def findFloorCeil(root, floor, ceil, key):
while root: # if a node with the desired value is found, both floor and ceil is equal # to the current node if root.data == key: floor = ceil = root break
# if the given key is less than the root node, visit the left subtree elif key < root.data: # update ceil to the current node before visiting the left subtree ceil = root root = root.left
# if the given key is more than the root node, visit the right subtree else: # update floor to the current node before visiting the right subtree floor = root root = root.right
return floor, ceil
Find inorder successor for the given key in a BST, Iteratively
Given a BST, find the inorder successor of a given key in it. If the given key does not lie in the BST, then return the next greater node (if any) present in the BST.
A node’s inorder successor is the node with the least value in its right subtree, i.e., its right subtree’s leftmost child. If the right subtree of the node doesn’t exist, then the inorder successor is one of its ancestors. To find which ancestors are the successor, we can move up the tree towards the root until we encounter a node that is left child of its parent. If any such node is found, then inorder successor is its parent; otherwise, inorder successor does not exist for the node.
the code is exactly like finding a predecessor.
in the recursive version, we start with searching the key.
However during the search, unlike the predecessor which we saved the root once we visit the left subtree, we save the root before moving the root to the next right subtree. This is for the cases that the node will be None
the once we found the node, we see if the node has the right subtree, if it has, we send the minimum function on the right subtree, save the ancestors and break the code.
if root is None we report the anc that we save throhjout the serach .
code
# Helper function to find minimum value node in a given BST def findMinimum(root): while root.left: root = root.left return root
# Iterative function to find an inorder successor for the given key in a BST def findSuccessor(root, key):
# base case if not root: return None
succ = None while True:
# if the given key is less than the root node, visit the left subtree if key < root.data: # update successor to the current node before visiting # left subtree succ = root root = root.left
# if the given key is more than the root node, visit the right subtree elif key > root.data: root = root.right
# if a node with the desired value is found, the successor is the minimum # value node in its right subtree (if any) else: if root.right: succ = findMinimum(root.right) break
# if the key doesn't exist in the binary tree, return next greater node if root is None: return succ
# return successor, if any return succ #################
if __name__ == ‘__main__’:
keys = [15, 10, 20, 8, 12, 16, 25] root = None for key in keys: root = insert(root, key) # find inorder successor for each key for key in keys: succ = findSuccessor(root, key) if succ: print(f'The successor of node {key} is {succ.data}') else: print(f'No Successor exists for node {key}') else:
Find inorder successor for the given key in a BST, recursively
Given a BST, find the inorder successor of a given key in it. If the given key does not lie in the BST, then return the next greater node (if any) present in the BST.
The idea is to search for the given node in the tree and update the successor to the current node before visiting its left subtree. If the node is found in the BST, return the least value node in its right subtree, and if the right subtree of the node doesn’t exist, then inorder successor is one of the ancestors of it, which has already been updated while searching for the given key.
Summary, if root is None, return the nacestor that you have saved so far, then first check the value on the root, if it is the target, the if it has the right sub tree, find min on left side.
other wise if root.data > ket: first save the anc =root then return function with root.left
else: return function on root.right
return root
time and space O(n) ##################### code
# Recursive function to find an inorder successor for a given key in a BST def findSuccessor(root, succ, key):
# base case if root is None: return succ
# if a node with the desired value is found, the successor is the minimum value # node in its right subtree (if any) if root.data == key: if root.right: return findMinimum(root.right)
# if the given key is less than the root node, recur for the left subtree elif key < root.data:
# update successor to the current node before recursing in the left subtree succ = root return findSuccessor(root.left, succ, key)
# if the given key is more than the root node, recur for the right subtree else: return findSuccessor(root.right, succ, key)
return succ