UNIT1
UNIT1
,1752'8&7,21
$/*25,7+0
1.1.1 Definition
The sequence of steps to be performed in order to solve a problem by the
computer is known as an algorithm.
4gP\_[T
Problem statement: Calling a friend on the telephone
Algorithm Steps
(i) Pick up the phone and listen for a dial tone
(v) If no answering machine, hang up and wait 2 hours, then jump to step 2
4gP\_[T !
Problem statement: Find the largest number in the given list of numbers
Algorithm Steps
(i) Define a variable ’max’ and initialize with ’0’.
(ii) Compare first number (say ’x’) in the list ’L’ with ’max’.
Introduction 1.3
(iv) Repeat step 2 and step 3 for all numbers in the list ’L’.
4. Input: An algorithm has zero or more inputs, taken from a specified set
of objects.
7. Generality – the algorithm must be able to work for a set of inputs rather
than a single input.
science, there are multiple algorithms to solve a problem. When we have more than
one algorithm to solve a problem, we need to select the best one. Performance analysis
helps us to select the best algorithm from multiple algorithms to solve a problem.
1. Whether that algorithm provides the exact solution for the problem
statement
Program Instruction
Execution
Space complexity includes both Auxiliary space and space used by input.
Auxiliary Space is the extra space or temporary space used by an algorithm.
Environmental Stack → used for storing the addresses while a module calls
another module or functions during execution.
Data space → used to store data, variables, and constants which are stored
by the program and it is updated during execution.
Example
Time taken to execute 1 statement = x milliseconds.
resource required. But it will not be provided. So instead of taking the exact amount
of resource, we represent that complexity in a general form (Notation) for analysis
process.
Example
Algorithm 1 : 25n3 + 2n + 1
Algorithm 2 : 1223n2 + 8n + 3
The term ’2n + 1’ have least significance than the term ’25n3’, and the term
’8n + 3’ in algorithm has least significance than the term ’1223n2’.
Definition
Asymptotic notations are mathematical tools to represent the time and space
complexity of algorithms for asymptotic analysis.
f(n) = O(g(n))
Big - Oh Notation
Examples
f(n) = Ω (g(n))
Omega Notation
1.8 Algorithms
Examples
100, log (2000), 104 → have Ω (1)
f (n) = Θ (g (n))
Theta Notation
Examples
100, log (2000), 104 → have Φ (1)
Asymptotic Notation
f(n) = O(g(n))
Big - Oh Notation ---- Upper Bound ⇒ Worst case
f(n) = Ù(g(n)) ))
Omega Notation ---- Lower Bound ⇒ Best case
f(n) = Φ (g(n))
Theta Notation ---- Upper & Lower Bound ⇒ Average case
Best case: Function which performs the minimum number of steps on input
data of size n.
Worst case: Function which performs the maximum number of steps on
input data of size n.
Average case: Function which performs an average number of steps on
input data of size n.
In linear search, Best case occurs when x is present at the first location. The
best case time complexity would be Ω (1)
In linear search, Worst case occurs when x is NOT present in the array. The
worst case time complexity of the linear search would be O(n).
In average case analysis, take all possible inputs and calculate the computing
time for all of the inputs. Sum all the calculated values and divide the sum
by the total number of inputs.
Example
In linear search, assume all cases are uniformly distributed (including the case
of x not being present in the array). After summing all the cases, divide the sum by
(n + 1).
Examples
Find if a number is even or odd.
Examples
Get the max/min value in an array.
Examples
Check if a collection has duplicated values.
Method 1:
Start on the first page of the book and go word by word until you find
matching word.
Method 2:
Open the book in the middle and check the first word on it.
If the word you are looking for is alphabetically more significant, then look
to the right. Otherwise, look in the left half.
Divide the remainder in half again, and repeat above step until you find
matching.
Example
Binary search.
Examples
Sorting algorithms like merge sort, quicksort, and others.
Examples:
Fibonacci series generation
Example 1
Recursive definition for the factorial function
n!=(n-1)! * n
Example 2
Recursive definition for Fibonacci sequence
Fib(n)=Fib(n-1)+Fib(n-2)
Recurrence relations are often used to model the cost of recursive functions. For
example, the number of multiplications required by a recursive version of the factorial
function for an input of size n will be zero when n = 0 or n = 1 (the base cases),
and it will be one plus the cost of calling fact on a value of n − 1.
Example 1
Let us see the expansion of the following recurrence equation.
T (0) = T (1) = 0.
Step 1:
T (n) = 1 + T (n − 1),
Step 2:
T (n) = 1 + (1 + T (n − 2)),
1.14 Algorithms
Step 3:
T (n) = 1 + (1 + (1 + T (n − 3))),
Step 4:
T (n) = 1 + (1 + (1 + (1 + T (n − 4)))),
Step 5:
This pattern will continue till we reach a sub-problem of size 1.
T (n) = 1 + (1 + (1 + (1 + (1 + (1 + ……))))
Step 6:
n
Thus the closed form of T (n) = 1 + T (n − 1) can be modeled as ∑ 1
i=1
Example 2
Let us see the expansion of the following recurrence equation.
T (n) = T (n − 1) + n
T (1) = 1.
Step 1:
T (n) = T (n − 1)
Step 2:
T (n) = n + (n − 2))
Step 3:
T (n) = n + (n − 1 + (n − 2 + T (n − 3))
Step 4:
T (n) = n + ((n − 2 + (n − 3 + T (n − 4))))
Introduction 1.15
Step 5:
This pattern will continue till we reach a sub-problem of size 1.
T (n) = n + (n − 1 + (n − 2 + (n − 3 + (n − 4 + …… 1))))
Step 6:
n
Thus the closed form of T (n) = n + T (n − 1) can be modeled as ∑ 1
i=1
Steps
Guess a solution through your experience.
Use induction to prove that the guess is an upper bound solution for the
given recurrence relation.
Example:
T (n) = 1 if n = 1
= 2T (n − 1) if n > 1
T (n) = 2T (n − 1)
= 2 [2T (n − 2)] = 22 T (n − 2)
= 4 [2T (n − 3)] = 23 T (n − 3)
= 8 [2T (n − 4)] = 24 T (n − 4)
1.16 Algorithms
Put n − i = 1 or i = n − 1 in (Eq. 1)
T (n) = 2n − 1 T (1)
= 2n − 1 1 { T (1) = 1 …… given }
= 2n − i
2. Iteration Methods
It means to expand the recurrence and express it as a summation of terms of
n and initial condition.
EXAMPLE 1
Consider the Recurrence
T (n) = 1 if n = 1
= 2T (n − 1) if n > 1
Solution:
T (n) = 2T (n − 1)
= 2 [2T (n − 2)] = 22 T (n − 2)
= 4 [2T (n − 3)] = 23 T (n − 3)
= 8 [2T (n − 4)] = 24 T (n − 4) (Eq. 1)
Repeat the procedure for i times
T (n) = 2i T (n − i)
Put n − i = 1 or i = n − 1 in (Eq. 1)
T (n) = 2n − 1 T (1)
= 2n − 1 1 { T (1) = 1 …… given }
= 2n − i
Introduction 1.17
EXAMPLE 2
Solution:
T (n) = T (n − 1) + 1
= (T (n − 2) + 1) + 1 = (T (n − 3) + 1) + 1 + 1
= T (n − 4) + 4 = T (n − 5) + 1 + 4
= T (n − 5) + 5 = T (n − k) + k
Where k=n−1
T (n − k) = T (1) = θ (1)
T (n) = θ (1) + (n − 1) = 1 + n − 1 = n −. 1 = n = θ (n)
3. Recursion Tree Method
Recursion is a fundamental concept in computer science and mathematics that
allows functions to call themselves, enabling the solution of complex problems through
iterative steps. One visual representation commonly used to understand and analyze
the execution of recursive functions is a recursion tree.
EXAMPLE 1
Consider the recurrence relation,
T (n) = 2T (n/2) + K
Solution
The given recurrence relation shows the following properties,
1.18 Algorithms
A problem size n is divided into two sub-problems each of size n/2. The cost
of combining the solutions to these sub-problems is K.
Each problem size of n/2 is divided into two sub-problems each of size n/4 and
so on.
At the last level, the sub-problem size will be reduced to 1. In other words, we
finally hit the base case.
Let’s follow the steps to solve this recurrence relation,
T (n) = 2T (n/2) + K
Here n/2 ∧ k is the problem size at the last level and it is always equal to 1.
Now we can easily calculate the value of k from the above expression by taking
log() to both sides. Below is a more clear derivation,
n=2∧k
So the level log (n) should have 2 ∧ (log (n)) nodes i.e. n nodes.
Total Cost = Cost of all levels except last level + Cost of last level
Total Cost = Cost for level-0 + Cost for level-1 + Cost for level-2
+ … + Cost for level-log (n) + Cost for last level
1.20 Algorithms
The cost of the last level is calculated separately because it is the base case
and no merging is done at the last level so, the cost to solve a single problem at
this level is some constant value. Let’s take it as O (1).
μ = 1800
s = 100
4. Master Method
The Master Method is used for solving the following types of recurrence
In the function to the analysis of a recursive algorithm, the constants and function
take on the following significance:
n/b is the size of each subproblem. (Here it is assumed that all subproblems
are essentially the same size.)
f (n) is the sum of the work done outside the recursive calls, which includes
the sum of dividing the problem and the sum of combining the solutions
to the subproblems.
Master Theorem
It is possible to complete an asymptotic tight bound in these three cases:
T (n) = (nlogb a)
EXAMPLE 1
n
T (n) = 8T ⎛⎜ ⎞⎟ + 1000n2 apply master theorem on it.
⎝2⎠
Solution:
n
Compare T (n) = 8T ⎛⎜ ⎞⎟ + 1000n2 with
⎝2⎠
n
T (n) = aT ⎛⎜ ⎞⎟ + f (n) with a ≥ 1 and b > 1
⎝b⎠
1000 n2 = 0 (n3 − ε)
Since this equation holds, the first case of the master theorem applies to the
given recurrence relation, thus resulting in the conclusion:
T (n) = O− (nlogb a)
6($5&+,1*
Searching is a technique that helps to find whether the given element is present
in the set of elements. Any search is said to be successful or unsuccessful depending
upon whether the element that is being searched is found or not. Some of the standards
searching techniques are:
Binary Search
Interpolation Search
Step
Step 1:
1: Read the search element from the user
Step 2: Compare, the search element with the first element in the array.
Step 3:
Step 3: If both are matched, then display “Given element found!!!” and terminate
the program
Introduction 1.23
Step 4: If both are not matched, then compare search element with the next element
Step 4:
in the array.
Step 5:
Step 5: Repeat steps 3 and 4 until the search element is compared with the last
element in the array.
Step 6: If the last element in the array is also not matched, then display “Element
Step
not found!!!” and terminate the function.
1. Python Program to search the given element in the list of items using
Linear Search
Example
Case 1
Input: Search 20
12 5 10 15 31 20 25 2 40
0 1 2 3 4 5 6 7 8
Case 2
Input: Search 26
12 5 10 15 31 20 25 2 40
0 1 2 3 4 5 6 7 8
Given the array of elements: 59, 58, 96, 78, 23 and the element to be searched
is 96, the working of linear search is as follows:
1.24 Algorithms
mylist = [1, 3, 5, 7, 9]
print("Given Elements : ", mylist)
k = int(input("Enter the element to be searched : "))
n = len(mylist)
result = LinearSearch(mylist, n, k)
if(result == -1):
print("Element not found")
else:
print("Element found at index: ", result)
Execution:
Input
Given Elements : [1, 3, 5, 7, 9]
Output
Element found at index: 1
Introduction 1.25
Time Complexity
Example:
Space Complexity
The space complexity of the linear search is O(1), as we don’t need any
auxiliary space for the algorithm.
/* 1. Python Program to search the given element in the list of items using
Binary Search using Iterative approach */
start + end
Formula for calculating middle is, Mid =
2
Execution:
Input
Elements in the array: [33, 44, 55, 66, 77, 88, 99]
Output
Element is present at index : 3
/* 2. Python Program to search the given element in the list of items using
Binary Search using Recursive approach */
If the middle value equal to the number that we are looking for, the middle
value is returned.
If the middle value is less than the value, we are looking then our recursive
function binary_search() again and increase the mid value by one and assign
to low.
If the middle value is greater than the value we are looking then our
recursive function binary_search() again and decrease the mid value by one
and assign it to low.
Program
def mybinary_search(myarr, low, high, x):
if high >= low:
mid = (high + low) // 2
if myarr[mid] == x:
return mid
# If element is smaller than mid, then it can only
# be present in left subarray
elif myarr[mid] > x:
return mybinary_search(myarr, low, mid - 1, x)
# Else the element can only be present in right subarray
else:
return mybinary_search(myarr, mid + 1, high, x)
else:
# Element is not present in the array
return -1
# Test data
myarr = [ 2, 3, 4, 10, 40 ]
print("Elements in the array :", myarr)
x = int(input("Enter the element to be searched : "))
# Function call
result = mybinary_search(myarr, 0, len(myarr)-1, x)
if result != -1:
print("Element is present at index : ", str(result))
else:
print("Element is not present in array")
1.30 Algorithms
Execution:
Input
Elements in the array : [2, 3, 4, 10, 40]
Output
Element is present at index : 3
Time Complexity
Best case - O(1)
The best case occurs when the target element is found in the middle of
list/array. Since only one comparison is made, the time complexity is O(1).
Worst-case - O(logn)
The worst occurs when the algorithm keeps on searching for the target
element until the size of the array reduces to 1. Since the number of
comparisons required is logn, the time complexity is O(logn).
Space Complexity
Since no extra space is needed, the space complexity of the binary search
is O(1).
on the search item. If the search item is near to the first element in the list, then
the starting search position is likely to be near the start of the list.
Binary Search always checks the value at middle index. But, interpolation
search may check at different locations based on the value of element being
searched.
Step
Step 2:2: Assign start = 0 & end = n-1
Step
Step 3:3: Calculate position ( pos ) to start searching by using formula:
⎡ (end − start) ⎤
pos = start + ⎢ ∗ (e − A [start]) ⎥
⎣ (A [end] − A [start]) ⎦
Step
Step 4:
4: If A[pos] == e , element found at index pos.
Step
Step 5:
5: Otherwise if e A[pos] we make start = pos + 1
Step
Step 6:
6: Else if e A[pos] we make end = pos -1
Step
Step 7:
7: Do steps 3, 4, 5, 6.
While : start <= end && e >= A[start] && e =< A[end]
e >= A[start] is done when the element we are looking for is greater than
or equal to the starting element of sub-array we are looking in.
e =< A[end] is done when the element we are looking for is less than or
equal to the last element of sub-array we are looking in.
1.32 Algorithms
/* Python Program to search the given element in the list of items using
Interpolation Search */
Example: Element to be searched = 4.
Program
def interpolationSearch(arr, lo, hi, x):
if (lo <= hi and x >= arr[lo] and x <= arr[hi]):
pos = lo + ((hi-lo)//(arr[hi]-arr[lo])*(x - arr[lo]))
if arr[pos] == x:
return pos
if arr[pos] < x:
return interpolationSearch(arr, pos + 1, hi, x)
if arr[pos] > x:
return interpolationSearch(arr, lo, pos - 1, x)
return -1
arr = [10, 12, 13, 16, 18, 19, 20,
21, 22, 23, 24, 33, 35, 42, 47]
print("Elements in the array :", arr)
x = int(input("Enter the element to be searched : "))
n = len(arr)
index = interpolationSearch(arr, 0, n - 1, x)
Introduction 1.33
if index != -1:
print("Element found at index", index)
else:
print("Element not found")
Execution:
Input
Elements in the array : [10, 12, 13, 16, 18, 19, 20, 21, 22, 23, 24, 33, 35, 42, 47]
Output
Element found at index 6
Time Complexity
Best case - O(1)
The best-case occurs when the target is found exactly as the first expected
position computed using the formula. As we only perform one comparison,
the time complexity is O(1).
Worst-case - O(n)
The worst case occurs when the given data set is exponentially distributed.
Space Complexity
Since no extra space is needed, the space complexity of the interpolation
search is O(1).
1.34 Algorithms
The Rabin-Karp-Algorithm
Finite Automata
Steps
1. n → length [T]
2. m → length [P]
3. for s ← 0 to n -m
4. do if P [1.....m] = T [s + 1....s + m]
5. then print “Pattern occurs with shift” s
Input
string = “This is my class room”
pattern = “class”
1.36 Algorithms
Output
Pattern found at index 11
Input:
string = “AABAACAADAABAABA”
pattern = = “AABA”
Output
Pattern found at index 0
1. Python Program to search the pattern in the given string using Naïve
Match algorithm
def naïve_algorithm(string, pattern):
n = len(string)
m = len(pattern)
if m > n:
print("Pattern not found")
return
for i in range(n - m + 1):
j = 0
while j < m:
if string[i + j] != pattern[j]:
break
j += 1
if j == m:
print("Pattern found at index: ", i)
string = "hellohihello"
print("Given String : ", string)
pattern = input("Enter the pattern to be searched :")
naïve_algorithm(string, pattern)
Execution:
Input
Given String : hellohihello
Output
Pattern found at index: 5
Time Complexity
Best Case Complexity- O(n).
Best case complexity occurs when the first character of the pattern is not
present in string.
String = “HIHELLOHIHELLO”
Pattern = “ LI”
1.38 Algorithms
Case
Case 1:
1: When all the characters of the string and pattern are same.
String = “HHHHHHHHHHHH”
Pattern = “ HHH”
Case
Case 2:
2: When only the last character is different.
String = “HHHHHHHHHHHM”
Pattern = “ HHM”
Space Complexity
Since no extra space is needed, the space complexity of the naïve search
is O(1).
Advantages
The comparison of the pattern with the given string can be done in any
order
Disadvantage
Naive method is inefficient because information from a shift is not used
again.
Introduction 1.39
• If the hash value of the current substring and the pattern are same,
check if the substring is same as the pattern.
• If they are same, store the starting index as a valid answer. Otherwise,
continue for the next substrings.
Step 1:
Take the input string and the pattern, which we want to match.
Given string:
A B C C D D A E F G
Pattern:
C D D
Step 2:
Here, we have taken first ten alphabets only (i.e. A to J) and given the
weights.
A B C D E F G H I J
1 2 3 4 5 6 7 8 9 10
Step 3:
Here, n = 10 and m = 3.
Here, we have taken input set {A, B, C, ..., J}. So, d = 10.
Step 4:
Calculate the hash value of the pattern (CDD)
Introduction 1.41
= 344 mod 13
= 6
In the calculation above, choose a prime number (here, 13) in such a way that
we can perform all the calculations with single-precision arithmetic.
Now calculate the hash value for the first window (ABC)
= 123 mod 13
= 6
Compare the hash value of the pattern with the hash value of the text. If
they match then, character-matching is performed. In the above examples,
the hash value of the first window (i.e. text) matches with pattern, so go
for character matching between ABC and CDD. Since they do not match
so, go for the next window.
Step 5:
We calculate the hash value of the next window by subtracting the first
term and adding the next term as shown below.
• How to compute the value of the next window “345”? It’s just (234
– 2*100)*10 + 5 and we get 345.
1.42 Algorithms
hash value for text(t) = ((1 * 102) + ((2 * 101) + (3 * 100) - (1 * 102))
* 10 + (3 * 100)) mod 13
= 233 mod 13
= 12
After a few searches, we will get the match for the window CDA in the text.
Execution:
Input
Given String : hihellohi
Output
Pattern is found at position: 3
Time Complexity
Best Case Complexity - O(n+m).
The average and best-case running time of the Rabin-Karp algorithm is
O(n+m), but its worst-case time is O(nm).
Space Complexity
Since no extra space is needed, the space complexity of the naïve search
is O(1).
Advantages
Extends to 2D patterns.
Disadvantage:
Arithmetic operations is slower than character comparisons.
Step
Step 2:
2: Define variables i & j. Set i = 0, j = 1 and LPS[0] = 0.
Step
Step 3:
3: Compare the characters at Pattern[i] and Pattern[j].
Step 4:
Step 4: If both are matched then set LPS[j] = i+1 and increment both i & j values
by one. Goto Step 3.
Step 5:
Step 5: If both are not matched then check the value of variable ’i’. If it is ’0’
then set LPS[j] = 0 and increment ’j’ value by one, if it is not ’0’ then set i =
LPS[i-1]. Goto Step 3.
Step
Step 6:
6: Repeat above steps until all the values of LPS[] are filled.
Example:
Given Pattern
A B C D A B D
Initialize LPS[] table with size 7 which is equal to the length of the pattern
Step 1:
Define variables i & j.
Introduction 1.45
0 1 2 3 4 5 6
LPS 0
Step 2:
0 1 2 3 4 5 6
LPS 0 0
Now, i = 0 & j = 2
Step 3:
0 1 2 3 4 5 6
LPS 0 0 0
Now, i = 0 & j = 3
Step 4:
0 1 2 3 4 5 6
LPS 0 0 0 0
1.46 Algorithms
Now, i = 0 & j = 4
Step 5:
0 1 2 3 4 5 6
LPS 0 0 0 0 1
Now, i = 1 & j = 5
Step 6:
0 1 2 3 4 5 6
LPS 0 0 0 0 1 2
Now, i = 2 & j = 6
Step 7:
i = 0
0 1 2 3 4 5 6
LPS 0 0 0 0 1 2
Now, i = 0 & j = 6
Introduction 1.47
Step 8:
0 1 2 3 4 5 6
LPS 0 0 0 0 1 2 0
Now, i = 0 & j = 7
0 1 2 3 4 5 6
LPS 0 0 0 0 1 2 0
If it is ’0’ then start comparing the first character of the pattern with the
next character to the mismatched character in the text.
If it is not ’0’ then start comparing the character which is at an index value
equal to the LPS value of the previous character to the mismatched character
in pattern with the mismatched character in the Text.
Example
Consider the following Text and Pattern
0 1 2 3 4 5 6
LPS 0 0 0 0 1 2 0
Step 1:
Start comparing the first character of the pattern with the first character of
Text from left to right.
Text A B C A B C D A B A B C D A B C D A B D E
0 1 2 3 4 5 6
Pattern A B C D A B D
Step 2:
Start comparing first charater in pattern with next character in Text.
Text A B C A B C D A B A B C D A B C D A B D E
0 1 2 3 4 5 6
Pattern A B C D A B D
Step 3:
Since LPS value is ‘2’ no need to compare Pattern[0] & Pattern[1] values.
Text A B C A B C D A B A B C D A B C D A B D E
0 1 2 3 4 5 6
Pattern A B C D A B D
Introduction 1.49
Step 4:
Since LPS value is ‘2’ no need to compare Pattern[0] & Pattern[1] values.
Text A B C A B C D A B A B C D A B C D A B D E
0 1 2 3 4 5 6
Pattern A B C D A B D
Step 5:
Since LPS value is ‘2’ no need to compare Pattern[0] & Pattern[1] values.
Compare pattern[2] with mismatched character in Text.
Text A B C A B C D A B A B C D A B C D A B D E
0 1 2 3 4 5 6
Pattern A B C D A B D
Here all the characters of the pattern matched with the substring in the
Text, which starts at index value 15. Hence, conclude that pattern found at
index 15.
while m != a:
if text[m] == pattern[n]:
m += 1
n += 1
else:
n = prefix_arr[n-1]
if n == b:
initial_point.append(m-n)
n = prefix_arr[n-1]
elif n == 0:
m += 1
return initial_point
def get_prefix_arr(pattern, b):
prefix_arr = [0] * b
n = 0
m = 1
while m != b:
if pattern[m] == pattern[n]:
n += 1
prefix_arr[m] = n
m += 1
elif n != 0:
n = prefix_arr[n-1]
else:
prefix_arr[m] = 0
m += 1
return prefix_arr
string = "hihellohihellohi"
print("Given String : ", string)
pat = input("Enter the pattern to be searched :")
initial_index = KMP_String(pat, string)
for i in initial_index:
print(’Pattern is found at index: ’,i)
Execution:
Input
Given String : hihellohihellohi
Output
Pattern is found at index: 0
Time Complexity
Worst case complexity of KMP algorithm is O(m+n).
• Once this prefix suffix table is created, actual search complexity is O(n).
Space Complexity
Space complexity of KMP algorithm isO(m) because some pre-processing
work is involved.
Advantages
The running time of the KMP algorithm is O(m + n), which is very fast.
The algorithm never needs to move backwardsthe input text T. It makes
the algorithm good for processing very large files.
Disadvantage
Doesn’t work so well as the size of the alphabets increases.
6257,1*
Sorting is the processing of arranging the data in ascending and descending order.
There are several types of sorting in data structures namely,
Bubble sort
Insertion sort
Selection sort
Bucket sort
Heap sort
Quick sort
Insertion sort is a simple sorting algorithm that works similar to the way you
play cards in your hands. The array is virtually split into a sorted and an unsorted
part. Values from the unsorted part are picked and placed at the correct position in
the sorted part.
Insertion sort
Introduction 1.53
Steps
Step 1:
The first element in the array is assumed to be sorted.
Step 2:
Take the second element and store it separately in currentvalue. Compare
currentvalue with the first element. If the first element is greater than
currentvalue, thencurrentvalue is placed in front of the first element.Now,
the first two elements are sorted.
Step 3:
Take the third element and compare it with the elements on the left of it.
Placed it just behind the element smaller than it. If there is no element
smaller than it, then place it at the beginning of the array.
Step 4:
Similarly, place every unsorted element at its correct position. Repeat until
list is sorted.
Example
List = [12, 11, 13, 5, 6]
First Pass
Initially, the first two elements of the array are compared in insertion sort.
12 11 13 5 6
Here, 12 is greater than 11. They are not in the ascending order and 12 is
not at its correct position. Hence, swap 11 and 12.
11 12 13 5 6
1.54 Algorithms
Second Pass
Now, move to the next two elements and compare them
11 12 13 5 6
Third Pass
Now, two elements are present in the sorted sub-array which are 11 and
12
11 12 13 5 6
Both 5 and 13 are not present at their correct place so swap them
11 12 5 13 6
After swapping, elements 12 and 5 are not sorted, thus swap again
11 5 12 13 6
5 11 12 13 6
Fourth Pass
Now, the elements which are present in the sorted sub-array are 5, 11 and
12
5 11 12 13 6
Clearly, they are not sorted, thus perform swap between both
5 11 12 6 13
5 11 6 12 13
5 6 11 12 13
/* 1. Python Program to sort the elements in the list using Insertion sort */
def insertionSort(arr):
for index in range(1,len(arr)):
currentvalue = arr[index]
position = index
while position>0 and arr[position-1]>currentvalue:
arr[position]=arr[position-1]
position = position-1
arr[position]=currentvalue
arr = [54,26,93,17,77,91,31,44,55,20]
print("Given list : ", arr)
insertionSort(arr)
print("Sorted list : ",arr)
Execution:
Input
Given list : [54, 26, 93, 17, 77, 91, 31, 44, 55, 20]
Output
Sorted list : [17, 20, 26, 31, 44, 54, 55, 77, 91, 93]
Time Complexity
Best case complexity - O(n)
It occurs when there is no sorting required, i.e. the array is already sorted.
Space Complexity
Space complexity of insertion sort is O(1)
1. Heap
A heap is a complete binary tree, and the binary tree is a tree in which
the node can have the utmost two children. A complete binary tree is a
binary tree in which all the levels except the last level, i.e., leaf node, should
be completely filled, and all the nodes should be left-justified.
If the index of any element in the array is i, the element in the index 2i+1
will become the left child and element in 2i+2 index will become the right
child. Also, the parent of any element at index i is given by the lower
bound of (i-1)/2.
Introduction 1.57
Example
Given array elements:
= 0.5 = 1
~ 0 index,
= 1,
1.58 Algorithms
All nodes in the tree follow the property that they are greater than their
children i.e. the largest element is at the root and both its children and
smaller than the root and so on. Such a heap is called a max-heap. If instead,
all nodes are smaller than their children, it is called a min-heap
4. “Heapify” process
Starting from a complete binary tree, we can modify it to become a
Max-Heap by running a function called heapify on all the non-leaf elements
of the heap. Heapify process uses recursion.
Pseudocode
heapify(array)
Root = array[0]
Largest = largest( array[0] , array [2*0 + 1]. array[2*0+2])
if(Root != Largest)
Swap(Root, Largest)
Introduction 1.59
The top element isn’t a max-heap but all the sub-trees are max-heaps.To
maintain the max-heap property for the entire tree, we will have to keep
pushing 2 downwards until it reaches its correct position.
Steps
Step
Step 1:
1: Construct a Binary Tree with given list of Elements.
Step
Step 2:
2: Transform the Binary Tree into Max Heap.
Step
Step 3:
3: Since the tree satisfies Max-Heap property, then the largest item is stored
at the root node. Three operations at each step are -
• Swap: Remove the root element and put at the end of the array (nth
position) Put the last item of the tree (heap) at the vacant place.
• Heapify: Heapify the root element again so that we have the highest
element at root.
Step
Step 4:
4: Put the removed element into the Sorted list.
Step
Step 5:
5: Repeat the same until Max Heap becomes empty.
Step
Step 6:
6: Display the sorted list.
1.60 Algorithms
Example:
Construct binary heap with the given list of elements
After converting the given heap into max heap, the array elements are -
0 1 2 3 4 5 6 7
89 81 76 22 14 9 54 11
Next, we have to delete the root element (89) from the max heap. To delete
this node, we have to swap it with the last node, i.e. (11). After deleting the root
element, we again have to heapify it to convert it into max heap.
Introduction 1.61
After swapping the array element 89 with 11, and converting the heap into
max-heap, the elements of array are –
0 1 2 3 4 5 6 7
81 22 76 11 14 9 54 89
In the next step, again, we have to delete the root element (81) from the max
heap. To delete this node, we have to swap it with the last node, i.e. (54). After
deleting the root element, we again have to heapify it to convert it into max heap.
After swapping the array element 81 with 54 and converting the heap into
max-heap, the elements of array are –
0 1 2 3 4 5 6 7
76 22 54 11 14 9 81 89
1.62 Algorithms
In the next step, we have to delete the root element (76) from the max heap
again. To delete this node, we have to swap it with the last node, i.e. (9). After
deleting the root element, we again have to heapify it to convert it into max heap.
After swapping the array element 76 with 9 and converting the heap into
max-heap, the elements of array are –
0 1 2 3 4 5 6 7
54 22 9 11 14 76 81 89
In the next step, again we have to delete the root element (54) from the max
heap. To delete this node, we have to swap it with the last node, i.e. (14). After
deleting the root element, we again have to heapify it to convert it into max heap.
After swapping the array element 54 with 14 and converting the heap into
max-heap, the elements of array are –
Introduction 1.63
0 1 2 3 4 5 6 7
22 14 9 11 54 76 81 89
In the next step, again we have to delete the root element (22) from the max
heap. To delete this node, we have to swap it with the last node, i.e. (11). After
deleting the root element, we again have to heapify it to convert it into max heap.
After swapping the array element 22 with 11 and converting the heap into
max-heap, the elements of array are –
0 1 2 3 4 5 6 7
14 11 9 22 54 76 81 89
In the next step, again we have to delete the root element (14) from the max
heap. To delete this node, we have to swap it with the last node, i.e. (9). After
deleting the root element, we again have to heapify it to convert it into max heap.
After swapping the array element 14 with 9 and converting the heap into
max-heap, the elements of array are –
1.64 Algorithms
0 1 2 3 4 5 6 7
11 9 14 22 54 76 81 89
In the next step, again we have to delete the root element (11) from the max
heap. To delete this node, we have to swap it with the last node, i.e. (9). After
deleting the root element, we again have to heapify it to convert it into max heap.
After swapping the array element 11 with 9, the elements of array are –
0 1 2 3 4 5 6 7
9 11 14 22 54 76 81 89
Now, heap has only one element left. After deleting it, heap will be empty.
0 1 2 3 4 5 6 7
9 11 14 22 54 76 81 89
/* 5. Python Program to sort the elements in the list using Heap sort */
def heapify(array, a, b):
largest = b
l = 2 * b + 1
root = 2 * b + 2
if l < a and array[b] < array[l]:
largest = l
if root < a and array[largest] < array[root]:
largest = root
# Change root
if largest != b:
array[b], array[largest] = array[largest], array[b]
heapify(array, a, largest)
# sort an array of given size
def Heap_Sort(array):
a = len(array)
# Building maxheap..
for b in range(a // 2 - 1, -1, -1):
heapify(array, a, b)
# swap elements
for b in range(a-1, 0, -1):
array[b], array[0] = array[0], array[b]
heapify(array, b, 0)
array = [81,89,9,11,14,76,54,22]
print("Original Array :", array)
Heap_Sort(array)
a = len(array)
print ("Sorted Array : ", array)
Time Complexity
Best case complexity - O(nlogn)
It occurs when there is no sorting required, i.e. the array is already sorted.
Space Complexity
Space complexity of Heap sort is O(1)
7. Comparative Analysis
Time Complexity
Algorithm Space Complexity
Best case Worst-case Average case
Insertion sort O(n) O(n2) O(n2) O(1)
Heap Sort O(nlogn) O(nlogn) O(nlogn) O(1)
PART - A QUESTIONS
1. Define time complexity and space complexity. Write an algorithm for adding n
natural numbers and find the space required by that algorithm
2. List the steps to write an Algorithm
3. Define Big ‘Oh’ notation.
4. Differentiate between Best, average and worst case efficiency.
5. Define recurrence relation.
6. How do you measure efficiency of the algorithm?
7. Write an algorithm to find the area and circumference of a circle.
8. How to measure algorithms running time?
9. List the desirable properties of algorithms
10. Write the recursive Fibonacci algorithm and its recurrence relation.
11. Write an algorithm to compute the GCD of two numbers.
Introduction 1.67
PART - B QUESTIONS
1. Discuss the concepts of asymptotic notations and its properties
2. What is divideand conquer strategy? Explain binary search problem in detail.
3. Solve the following using Brute-Force algorithm:
4. Find whether the given string follows the specified pattern and return 0 or 1
accordingly
EXAMPLES
Pattern: “abba”, input ”redblueredblue” should return 1