Unit 4 Notes
Unit 4 Notes
TREE
A tree is non-linear and a hierarchical data structure consisting of a collection of nodes such
that each node of the tree stores a value and a list of references to other nodes (the
“children”).
This data structure is a specialized method to organize and store data in the computer to be
used more effectively. It consists of a central node, structural nodes, and sub-nodes, which
are connected via edges. We can also say that tree data structure has roots, branches, and
leaves connected with one another.
Recursive Definition:
A tree consists of a root, and zero or more subtrees T1, T2, … , Tk such that there is an edge
from the root of the tree to the root of each subtree.
1
Why Tree is considered a non-linear data structure?
The data in a tree are not stored in a sequential manner i.e, they are not stored linearly.
Instead, they are arranged on multiple levels or we can say it is a hierarchical structure. For
this reason, the tree is considered to be a non-linear data structure.
Parent Node: The node which is a predecessor of a node is called the parent node of
that node. {2} is the parent node of {6, 7}.
Child Node: The node which is the immediate successor of a node is called the child
node of that node. Examples: {6, 7} are the child nodes of {2}.
Root Node: The topmost node of a tree or the node which does not have any parent
node is called the root node. {1} is the root node of the tree. A non-empty tree must
contain exactly one root node and exactly one path from the root to all other nodes of
the tree.
Leaf Node or External Node: The nodes which do not have any child nodes are called
leaf nodes. {6, 14, 8, 9, 15, 16, 4, 11, 12, 17, 18, 19} are the leaf nodes of the tree.
Ancestor of a Node: Any predecessor nodes on the path of the root to that node are
called Ancestors of that node. {1, 2} are the ancestor nodes of the node {7}
Descendant: Any successor node on the path from the leaf node to that node. {7,
14} are the descendants of the node. {2}.
Sibling: Children of the same parent node are called siblings. {8, 9, 10} are called
siblings.
Level of a node: The count of edges on the path from the root node to that node. The
root node has level 0.
Internal node: A node with at least one child is called Internal Node.
Neighbour of a Node: Parent or child nodes of that node are called neighbors of that
node.
Subtree: Any node of the tree along with its descendant.
2
Properties of a Tree:
Number of edges: An edge can be defined as the connection between two nodes. If a
tree has N nodes then it will have (N-1) edges. There is only one path from each node to
any other node of the tree.
Depth of a node: The depth of a node is defined as the length of the path from the root
to that node. Each edge adds 1 unit of length to the path. So, it can also be defined as
the number of edges in the path from the root of the tree to the node.
Height of a node: The height of a node can be defined as the length of the longest path
from the node to a leaf node of the tree.
Height of the Tree: The height of a tree is the length of the longest path from the root
of the tree to a leaf node of the tree.
Degree of a Node: The total count of subtrees attached to that node is called the degree
of the node. The degree of a leaf node must be 0. The degree of a tree is the maximum
degree of a node among all the nodes in the tree.
Here,
Node A is the root node
B is the parent of D and E
3
D and E are the siblings
D, E, F and G are the leaf nodes
A and B are the ancestors of E
BINARY TREE
A binary tree is a data structure in which every node or vertex has at most two
children.
Binary Tree is a form of a tree whose nodes cannot have more than two children.
Each node of the binary tree has two pointers associated with it, one points to the left
child, and the other points to the right child of the node.
It is an unordered tree having no fixed organized structure for the arrangement of
nodes.
Binary Tree is slow for the searching, insertion, or deletion of the data because of its
unordered structure.
4
Types of Binary Tree
The tree can only be considered as the full binary tree if each node must
contain either 0 or 2 children.
The full binary tree can also be defined as the tree in which each node must
contain 2 children except the leaf nodes.
5
In the above tree, we can observe that each node is either containing zero or two children;
therefore, it is a Full Binary tree.
o The number of leaf nodes is equal to the number of internal nodes plus 1. In the above
example, the number of internal nodes is 5; therefore, the number of leaf nodes is
equal to 6.
o The maximum number of nodes is the same as the number of nodes in the binary tree,
i.e., 2h+1 -1.
o The minimum number of nodes in the full binary tree is 2*h-1.
o The minimum height of the full binary tree is log2(n+1) - 1.
o The maximum height of the full binary tree can be computed as:
n= 2*h - 1
n+1 = 2*h
h = n+1/2
The complete binary tree is a tree in which all the nodes are completely filled except
the last level. In the last level, all the nodes must be as left as possible. In a complete binary
tree, the nodes should be added from the left.
6
Properties of Complete Binary Tree
A tree is a perfect binary tree if all the internal nodes have 2 children, and all the leaf
nodes are at the same level.
The below tree is not a perfect binary tree because all the leaf nodes are not at the same level.
7
Not a Perfect Binary Tree
The degenerate binary tree is a tree in which all the internal nodes have only one children.
Step 1:
This tree is a degenerate binary tree because all the nodes have only one child. It is also
known as a right-skewed tree as all the nodes have a right child only.
Step 2:
This tree is also a degenerate binary tree because all the nodes have only one child. It is also
known as a left-skewed tree as all the nodes have a left child only.
8
Binary Tree Program:
9
10
Tree Traversal
Traversal is a process to visit all the nodes of a tree and may print their values too.
Because, all nodes are connected via edges (links) we always start from the root (head) node.
That is, we cannot randomly access a node in a tree. There are three ways which we use to
traverse a tree −
In-order Traversal
Pre-order Traversal
Post-order Traversal
In-order Traversal
In this traversal method, the left subtree is visited first, then the root and later the right sub-
tree. We should always remember that every node may represent a subtree itself.
Left-Root-Right
Example:
11
Inorder Traversal
Pre-order Traversal
In this traversal method, the root node is visited first, then the left subtree and finally the right
subtree.
Root-Left-Right
Example:
Post-order Traversal
In this traversal method, the root node is visited last, hence the name. First we traverse the
left subtree, then the right subtree and finally the root node.
12
Left-Right-Root
13
preorder(root.right)
def postorder(root):
if root:
postorder(root.left)
postorder(root.right)
print(str(root.val)+"-->",end=' ')
root=Node(1)
root.left=Node(2)
root.right=Node(3)
root.left.left=Node(4)
root.left.right=Node(5)
print("Inorder traversal")
inorder(root)
print("\n preorder traversal")
preorder(root)
print("\n Postorder traversal")
postorder(root)
14
Operations of Binary Search Tree:
Insertion
It is used add the new node with the current position in the Binary Search Tree.
Example:
15
Inserting new node value 120 into the tree.
Step 1:
First should compare the element 120 with the root node 21. So 120 is greater than the root
node.
So move towards the right subtree of 21
120>21 Moves right subtree
Step 2:
Now element 120 is compared with the node 30. It is greater tha 30 so it moves towards the
right subtree.
120>30 Moves right subtree
Step 3:
Again the element 120 is compared with the node 100. It is greater than 100 and after the
node 100 there is no node.
So want to insert the element 120 to the right side of 100
16
def __init__(self,key):
self.key=key
self.lchild=None
self.rchild=None
def insert(self,data):
if self.key is None:
self.key=data
return
if self.key==data:
return
if self.key>data:
if self.lchild:
self.lchild.insert(data)
else:
self.lchild=BST(data)
else:
if self.rchild:
self.rchild.insert(data)
else:
self.rchild=BST(data)
root=BST(10)
root.insert(20)
Deletion
It will delete the given node if it is present in the Binary Search tree.
17
Three cases of deletion operation:
Case 1:
The node having zero child is to be deleted.
The node 3,7,12,25,120 are not having any child. That is zero child.
Case 2:
The node which having one child is to be deleted
18
The node 100 having only one child. So delete the node 100
After deleting the node 100. The binary search tree should be as follows
Case 3:
The node which is having 2child is to be deleted.
19
Here the node 10 is deleted and replace the value of node 5 into that. In left subtree we want
to choose the highest value among the left subtree.
Hence it is replaced.
Example program for Deletion Operation:
class BST:
def delete(self,data):
if self.key is None:
print("Tree is empty")
return
if data<self.key:
if self.lchild:
self.lchild=self.lchild.delete(data)
else:
print("Given node is not present in the tree")
elif data>self.key:
if self.rchild:
self.rchild=self.rchild.delete(data)
else:
print("Given node isnot present in the tree")
else:
if self.lchild is None:
temp=self.rchild
20
self=None
return temp
node=self.rchild
while node.lchild:
node=node.lchild
self.key=node.key
self.rchild=self.rchild.delete(node)
root=BST(i)
list=[6,3,1,6,98,3,7]
for i in list:
root.insert(i)
root.delete(6)
Output
Search
Search operation is used to find whether a given node is present in the binary search
tree or not.
Searching process is begin from the root node.
First it will check binary search tree is empty or not.
If given value is not equal to root node value, then check given value<root node
value. If it is then search left subtree.
If given value is greater than the root node value, ie) given value>root node value. If
it is then search right subtree.
Example:
21
Step 1:
In this example want to search the element 12.
So to search the element 12, we should first compare the element 12 with the root node 21.
Here the searching element 12 is less than the root node.
12<21 Move left subtree
Element 12 is less than the root node so we want to search the element in left side of the root
node 21.
Step 2:
In left subtree the element 12 is greater than 10. So it traverse to the right side of the node10.
12>10 Move right subtree
Step 3:
Now the element 12 compare with the node 12. Hence it is matched. Thus the key founded
and search is successful
22
Example program for search operation:
In Search operations search for the number, we start at the root, and then we compare the
value to be searched with the value of the root, if it’s equal we are done with the search if
it’s smaller we know that we need to go to the left subtree because in a binary search tree
all the elements in the left subtree are smaller and all the elements in the right subtree are
larger. Searching an element in the binary search tree is basically this traversal, at each step
we go either left or right and at each step we discard one of the sub-trees.
print("\nPreorder
traversal:",end='')
preorder(root)
print("\nPostorder
traversal:",end='')
postorder(root)
print("\nDelete 10")
AVL TREES
AVL Tree is invented by GM Adelson - Velsky and EM Landis in 1962. The tree is
named AVL in honour of its inventors.
AVL tree is a self-balancing binary search tree in which each node maintains extra
information called a balance factor whose value is either -1, 0 or +1.
23
AVL tree got its name after its inventor Georgy Adelson-Velsky and Landis.
Balance Factor
Balance factor of a node in an AVL tree is the difference between the height of the
left subtree and that of the right subtree of that node.
Balance Factor = (Height of Left Subtree - Height of Right Subtree) or (Height of
Right Subtree - Height of Left Subtree)
The self balancing property of an avl tree is maintained by the balance factor. The
value of balance factor should always be -1, 0 or +1.
Example:
Complexity
Algorithm Average case Worst case
24
Delete o(log n) o(log n)
AVL Rotations
We perform rotation in AVL tree only in case if Balance Factor is other than -1, 0, and 1.
There are basically four types of rotations which are as follows:
1. RR Rotation
When BST becomes unbalanced, due to a node is inserted into the right subtree of the
right subtree of A, then we perform RR rotation, RR rotation is an anticlockwise rotation,
which is applied on the edge below a node having balance factor -2
2. LL Rotation
When BST becomes unbalanced, due to a node is inserted into the left subtree of the left
subtree of C, then we perform LL rotation, LL rotation is clockwise rotation, which is applied
on the edge below a node having balance factor 2.
25
3. LR Rotation
4. RL Rotation
26
AVL tree is also a binary search tree therefore, all the operations are performed in the
same way as they are performed in a binary search tree.
Searching and traversing do not lead to the violation in property of AVL tree.
However, insertion and deletion are the operations which can violate this property and
therefore, they need to be revisited.
SN Operatio Description
n
2 Deletion Deletion can also be performed in the same way as it is performed in a binary
search tree. Deletion may also disturb the balance of the tree therefore,
various types of rotations are used to rebalance the tree.
Example:
Insertion
Construct AVL tree by inserting the following data
14,17,11,7,53,4,13,12,8,60,19,16,20
Step 1: Insert 14
Step 2: Insert 17
Step 3: Insert 11
27
Step 4: Insert 7
Step 5: Insert 53
Step 6: Insert 4
Step 7: Insert 13
28
Step 8: Insert 12
Step 9: Insert 8
29
Step 10: Insert 60
30
Hence the tree is balanced.
31
Deletion
Step 2: Delete 7
Step 3: Delete 11
32
Step 4: Delete 14
33
Step 5: Delete 17
Example Program:
import sys
34
root.height = 1 + max(self.getHeight(root.left),
self.getHeight(root.right))
# Update the balance factor and balance the tree
balanceFactor = self.getBalance(root)
if balanceFactor > 1:
if key < root.left.key:
return self.rightRotate(root)
else:
root.left = self.leftRotate(root.left)
return self.rightRotate(root)
if balanceFactor < -1:
if key > root.right.key:
return self.leftRotate(root)
else:
root.right = self.rightRotate(root.right)
return self.leftRotate(root)
return root
# Function to delete a node
def delete_node(self, root, key):
# Find the node to be deleted and remove it
if not root:
return root
elif key < root.key:
root.left = self.delete_node(root.left, key)
elif key > root.key:
root.right = self.delete_node(root.right, key)
else:
if root.left is None:
temp = root.right
35
root = None
return temp
elif root.right is None:
temp = root.left
root = None
return temp
temp = self.getMinValueNode(root.right)
root.key = temp.key
root.right = self.delete_node(root.right,
temp.key)
if root is None:
return root
# Update the balance factor of nodes
root.height = 1 + max(self.getHeight(root.left),self.getHeight(root.right))
balanceFactor = self.getBalance(root)
# Balance the tree
if balanceFactor > 1:
if self.getBalance(root.left) >= 0:
return self.rightRotate(root)
else:
root.left = self.leftRotate(root.left)
return self.rightRotate(root)
if balanceFactor < -1:
if self.getBalance(root.right) <= 0:
return self.leftRotate(root)
else:
root.right = self.rightRotate(root.right)
return self.leftRotate(root)
return root
36
# Function to perform left rotation
def leftRotate(self, z):
y = z.right
T2 = y.left
y.left = z
z.right = T2
z.height = 1 + max(self.getHeight(z.left),self.getHeight(z.right))
y.height = 1 + max(self.getHeight(y.left),self.getHeight(y.right))
return y
# Function to perform right rotation
def rightRotate(self, z):
y = z.left
T3 = y.right
y.right = z
z.left = T3
z.height = 1 + max(self.getHeight(z.left),
self.getHeight(z.right))
y.height = 1 + max(self.getHeight(y.left),
self.getHeight(y.right))
return y
# Get the height of the node
def getHeight(self, root):
if not root:
return 0
return root.height
# Get balance factore of the node
def getBalance(self, root):
if not root:
37
return 0
return self.getHeight(root.left) - self.getHeight(root.right)
def getMinValueNode(self, root):
if root is None or root.left is None:
return root
return self.getMinValueNode(root.left)
def preOrder(self, root):
if not root:
return
print("{0} ".format(root.key), end="")
self.preOrder(root.left)
self.preOrder(root.right)
# Print the tree
def printHelper(self, currPtr, indent, last):
if currPtr != None:
sys.stdout.write(indent)
if last:
sys.stdout.write("R----")
indent += " "
else:
sys.stdout.write("L----")
indent += "| "
print(currPtr.key)
self.printHelper(currPtr.left, indent, False)
self.printHelper(currPtr.right, indent, True)
myTree = AVLTree()
root = None
nums = [33, 13, 52, 9, 21, 61, 8, 11]
for num in nums:
38
root = myTree.insert_node(root, num)
myTree.printHelper(root, "", True)
key = 13
root = myTree.delete_node(root, key)
print("After Deletion: ")
myTree.printHelper(root, "", True)
Output:
HEAP
Heap is a data structure that follows a complete binary tree's property and satisfies the
heap property.
Therefore, it is also known as a binary heap.
39
Create a Heap
A heap is created by using python’s inbuilt library named heapq. This library has the relevant
functions to carry out various operations on heap data structure. Below is a list of these
functions.
heapify − This function converts a regular list to a heap. In the resulting heap the
smallest element gets pushed to the index position 0. But rest of the data elements are
not necessarily sorted.
heappush − This function adds an element to the heap without altering the current
heap.
heappop − This function returns the smallest data element from the heap.
heapreplace − This function replaces the smallest data element with a new value
supplied in the function.
Creating a Heap
A heap is created by simply using a list of elements with the heapify function. In the below
example we supply a list of elements and the heapify function rearranges the elements
bringing the smallest element to the first position.
Example
import heapq
H = [21,1,45,78,3,5]
heapq.heapify(H)
print(H)
Output
[1, 3, 5, 78, 21, 45]
import heapq
H = [21,1,45,78,3,5]
heapq.heapify(H)
print(H)
heapq.heappush(H,8)
print(H)
Output
[1, 3, 5, 78, 21, 45]
[1, 3, 5, 78, 21, 45, 8]
40
Removing from heap
It remove the element at first index by using this function. In the below example the function
will always remove the element at the index position 1.
Example
import heapq
H = [21,1,45,78,3,5]
heapq.heapify(H)
print(H)
heapq.heappop(H)
print(H)
Output
[1, 3, 5, 78, 21, 45]
[3, 21, 5, 78, 45]
Example:
Step 1:
Step 2:
41
Replacing in a Heap
The heap replace function always removes the smallest element of the heap and inserts the
new incoming element at some place not fixed by any order.
Example
import heapq
H = [21,1,45,78,3,5]
heapq.heapify(H)
print(H)
heapq.heapreplace(H,6)
print(H)
Output
Example:
42
Types of heap
Max Heap
The root node will be always the largest of all the elements.
For every node i, the value of node is less than or equal to its parent value.
A[parent[i]] >= A[i]
i=3
A[parent[i]]>=A[i]
A[1]>=A[3]
70>=40.
This condition is TRUE
43
Algorithm for Max Heap
MaxHeap(array, size)
loop from the first index down to zero
call maxHeapify
def left(k):
return 2 * k + 1
def right(i):
return 2 * k + 2
def build_max_heap(A):
n = int((len(A)//2)-1)
for k in range(n, -1, -1):
max_heapify(A,k)
44
A = [3,9,2,1,4,5]
build_max_heap(A)
print(A)
Output
[9, 4, 5, 1, 3, 2]
Min Heap
For every node i, the value of node isgreater than or equal to its parent value.
A[parent(i)] <= A[i]
45
remove nodeDeleted
def left(k):
return 2 * k + 1
def right(k):
return 2 * k + 2
def build_min_heap(A):
n = int((len(A)//2)-1)
for k in range(n, -1, -1):
min_heapify(A,k)
A = [3,9,2,1,4,5]
build_min_heap(A)
print(A)
Output
[1, 3, 2, 9, 4, 5]
46
Example
Insert the elements 20,70, 110, 210, 50, 130
Solution:
Take m=3 and m-1=2
47
48
In m-Way tree of order m, each node contains a maximum of m – 1 elements and m
children.
The goal of m-Way search tree of height h calls for O(h) no. of accesses for an
insert/delete/retrieval operation. Hence, it ensures that the height h is close to log_m(n + 1).
The number of elements in an m-Way search tree of height h ranges from a minimum
of h to a maximum of .
An example of a 5-Way search tree is shown in the figure below. Observe how each node
has at most 5 child nodes & therefore has at most 4 keys contained in it.
49
The structure of a node of an m-Way tree is given below:
class node:
def __init__(self):
self.count = -1
self.value = [-1]*(MAX + 1)
self.child = [None]*(MAX + 1)
Here, count represents the number of children that a particular node has
The values of a node stored in the array value
The addresses of child nodes are stored in the child array
The MAX macro signifies the maximum number of values that a particular node can
contain
Searching for a key in an m-Way search tree is similar to that of binary search tree
To search for 77 in the 5-Way search tree, shown in the figure, we begin at the root &
as 77> 76> 44> 18, move to the fourth sub-tree
In the root node of the fourth sub-tree, 77< 80 & therefore we move to the first sub-tree
of the node. Since 77 is available in the only node of this sub-tree, we claim 77 was
successfully searched
50
def search(val, root, pos):
if (root == None):
return None
else :
if (searchnode(val, root, pos)):
return root
else:
return search(val, root.child[pos], pos)
def searchnode(val, n, pos):
if (val < n.value[1]):
pos = 0
return 0
else :
pos = n.count
while ((val < n.value[pos]) and pos > 1):
pos-=1
if (val == n.value[pos]):
return 1
else:
return 0
search():
51
searchnode():
52