Algorithms Unit 1
Algorithms Unit 1
Important Questions:
Part A Questions
Define time complexity and space complexity. Write an algorithm for
adding n natural numbers and find the space required by that
algorithm
List the steps to write an Algorithm
Define Big ‘Oh’ notation.
Differentiate between Best, average and worst case efficiency.
Define recurrence relation.
How do you measure efficiency of the algorithm?
Write an algorithm to find the area and circumference of a circle.
How to measure algorithms running time?
List the desirable properties of algorithms
Write the recursive Fibonacci algorithm and its recurrence relation.
Write an algorithm to compute the GCD of two numbers.
Part B Questions
Discuss the concepts of asymptotic notations and its properties
What is divide and conquer strategy? Explain binary search problem in detail.
Solve the following using Brute-Force algorithm:
Find whether the given string follows the specified pattern and
return 0 or 1 accordingly
Examples
Pattern: “abba”, input ”redblueredblue” should
return 1 Pattern: “aaaa”, input
”asdasdasdasd” should return 1 Pattern:
“aabb”, input ”xyzabcxyzabc” should return 0
1.1.1 Examples on
Algorithms Example 1:
Problem statement: Calling a friend on the telephone
Algorithm Steps:
1. Pick up the phone and listen for a dial tone
2. Press each digit of the phone number on the phone
3. If busy, hang up phone, wait 2 minutes, jump to step 2
4. If no one answers, leave a message then hang up
5. If no answering machine, hang up and wait 2 hours, then jump to step 2
6. Talk to friend
7. Hang up phone
Example 2:
Problem statement: Find the largest number in the given list of numbers
Algorithm Steps:
1. Define a variable 'max' and initialize with '0'.
2. Compare first number (say 'x') in the list 'L' with 'max'.
f(n) = O(g(n))
Fig. Big - Oh Notation
Example
s:
100 , log (2000) , 104- -> have O(1)
(n/4) , (2n+3) , (n/100 + log(n)) > have O(n)
(n +n) , (2 n ) , (n +log(n))
2 2 2 > have O(n2)
O provides upper bounds.
f(n) = Ω(g(n))
f(n) = Θ(g(n))
Fig. Theta Notation
Examples:
100 , log (2000) , 104--> have Θ (1)
(n/4) , (2n+3) , (n/100 + log(n)) > have Θ (n)
(n +n) , (2 n ) , (n +log(n))
2 2 2 > have Θ (n2)
Θ provides exact bounds.
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
1.2.4 Worst Case, Average Case, and Best Case in Algorithm Analysis
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.
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).
Example
Binary search.
2. Recurrence Relation
A recurrence relation is an equation that defines a sequence based on a rule that
gives the next term as a function of the previous term(s). It helps in finding the
subsequent term (next term) with the previous term. If we know the previous term
in a given series, then we can easily determine the next term.
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(n)= T(n−1)+1 for n>1
T(0)= T(1)=0.
Step 1:
T(n) = 1 + T(n - 1)
Step 2:
T(n) = 1 + (1 + T(n - 2))
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:
Step 1:
T(n) = n + T(n - 1)
Step 2:
T(n) = n +(n - 1 + T(n - 2))
Step 3:
T(n) = n + (n - 1 + (n - 2 + T(n - 3))
Step 4:
T(n) = n +(n - 1+(n - 2 +(n –3 +T(n - 4))))
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:
a
2.2 Methods for solving Recurrence
Substitution Method
Iteration Method
Recursion Tree Method
Master Method
2.2.1 Substitution Method
Example:
T (n) = 1 if n=1
= 2T (n-1) if n>1
T (n) = 2T (n-1)
= 2[2T (n-2)] = 22T (n-2)
= 4[2T (n-3)] = 23T (n-3)
= 8[2T (n-4)] = 24T (n-4)
3. Searching
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:
Linear Search or Sequential Search
Binary Search
Interpolation Search
def LinearSearch(mylist, n,
k): for j in range(0, n):
if (mylist[j] == k):
return j
return -1
Execution:
Input
Given Elements : [1, 3, 5, 7, 9]
Enter the element to be searched :
3 Output
Element found at index: 1
Space Complexity
The space complexity of the linear search is O(1), as we don't need
any auxiliary space for the algorithm.
myarray = [3, 4, 5, 6, 7, 8, 9]
print("Elements in the array: " , myarray)
x = int(input("Enter the element to be searched : "))
if result != -1:
print("Element is present at index :" + str(result))
else:
print("Element not found ")
Execution:
Input
Elements in the array: [33, 44, 55, 66, 77, 88, 99]
Enter the element to be searched : 66
Output
Element is present at index :3
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
# 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")
Execution:
Input
Elements in the array : [2, 3, 4, 10, 40]
Enter the element to be searched : 10
Output
Element is present at index : 3
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).
n = len(arr)
index = interpolationSearch(arr, 0, n - 1, x)
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]
Enter the element to be searched : 20
Output
Worst-case - O(n)
The worst case occurs when the given data set is exponentially distributed.
4. Pattern Search
The Pattern Searching algorithms are sometimes also referred to as String
Searching Algorithms. These algorithms are useful in the case of searching a
pattern in a string.
string = "hellohihello"
print("Given String : ", string)
pattern = input("Enter the pattern to be searched :")
naïve_algorithm(string, pattern)
Execution:
Input
Given String : hellohihello
Enter the pattern to be searched :hi
Output
Pattern found at index: 5
Space Complexity
Since no extra space is needed, the space complexity of the naïve search is
O(1).
Given Patter
A B C C D D A E F G 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: n Length of the text
m Length of the pattern
Here, n = 10 and m
= 3.
d Number of characters in the input set.
Here, we have taken input set {A, B, C, ..., J}. So, d = 10.
Note: we can assume any suitable value for d.
Step
4:
Calculate the hash value of the pattern (CDD)
hash value for pattern(p) = Σ(v * dm-1) mod 13
= ((3 * 102) + (4 * 101) + (4 * 100))
mod 13
= 344 mod 13
= 6 choose a prime number (here, 13) in such a way
In the calculation above,
that we can perform all the calculations with single-precision arithmetic.
Now calculate the hash value for the first window (ABC)
hash value for text(t) = Σ(v * dm-1) mod 13
= ((1 * 102) + (2 * 101) + (3 * 100)) mod
13
= 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.
Simple Numerical example:
o Pattern length is 3 and string is “23456”
o Let us assume that we computed the value of the first window as 234.
o How to compute the value of the next
window “345”? It’s just (234 – 2*100)*10 +
5 and we get 345.
Unit I CS3401 Algorithms 27
hash value for text(t) = ((1 * 102) + ((2 * 101) + (3 * 100) - (1 * 102)) * 10 + (3 *
100)) mod 13
= 233 mod 13
= Therefore,
For BCC, t = 12 (≠6). 12 go for the next window.
After a few searches, we will get the match for the window CDA in the text.
d = 10
def search(pattern, text, q):
m = len(pattern)
n = len(text) p
= 0
t = 0
h = 1
i = 0
j = 0
for i in range(m-1):
h = (h*d) % q
j += 1
if j == m:
print("Pattern is found at position: " + str(i+1))
if i < n-m:
t = (d*(t-ord(text[i])*h) + ord(text[i+m])) % q
if t < 0:
t = t+q
search(pattern, text, q)
Space Complexity
Since no extra space is needed, the space complexity of the naïve search is
O(1).
Step 2:
Compare Pattern[i] with Pattern[j] ====>A is compared with B. Since
both were not matching, check the value of i.
i = 0, so set LPS[j] = 0 and increment ‘j’ value by 1.
0 1 2 3 4 5 6
LPS 0 0
Now, i = 0 & j = 2
Step 3:
Compare Pattern[i] with Pattern[j] ====>A is compared with C. Since
both were not matching, check the value of i.
i = 0, so set LPS[j] = 0 and increment ‘j’ value by 1.
0 1 2 3 4 5 6
LPS 0 0 0
Now, i = 0 & j = 3
Step 4:
Compare Pattern[i] with Pattern[j] ====>A is compared with D. Since
both were not matching, check the value of i.
i = 0, so set LPS[j] = 0 and increment ‘j’ value by 1.
0 1 2 3 4 5 6
LPS 0 0 0 0
Now, i = 0 & j = 4
Step 5:
Compare Pattern[i] with Pattern[j] ====>A is compared with A. Since both
are matching, set LPS[j] = i+1 and increment both ‘i’ & ‘j’ value by 1.
0 1 2 3 4 5 6
LPS 0 0 0 0 1
Now, i = 1 & j = 5
Step 6:
Compare Pattern[i] with Pattern[j] ====>B is compared with B. Since both
are matching, set LPS[j] = i+1 and increment both ‘i’ & ‘j’ value by 1.
0 1 2 3 4 5 6
Step 7:
Compare Pattern[i] with Pattern[j] ====>A is compared with D. Since
both were not matching, check the value of i.
i = 0, so set LPS[j] = 0 and increment ‘j’ value by 1.
0 1 2 3 4 5 6
LPS 0 0 0 0 1 2 0
Now, i = 0 & j = 7
Example:
Consider the following Text and Pattern
Text : ABC ABCDAB ABCDABCDABDE
Pattern: ABCDABD
LPS[] table for the above pattern is as follows:
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
l
e
f
t
t
o
r
i
g
h
t.
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
0 1 2 3 4 5 6
Pattern A B C D A B D
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.
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.
initial_point = []
m = 0
n = 0
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
Execution:
Input
Given String : hihellohihellohi
Enter the pattern to be searched
:hi Output
Pattern is found at index: 0
Pattern is found at index: 7
Pattern is found at index: 14
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, then currentvalue 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.
Second Pass:
Now, move to the next two elements and compare them
11 12 13 5 6
Here, 13 is greater than 12, thus both elements seems to be in ascending
order, hence, no swapping will occur. 12 also stored in a sorted sub-array
along with 11
Third Pass:
Now, two elements are present in the sorted sub-array which are 11 and 12
Moving forward to the next two elements which are 13 and 5
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
Here, again 11 and 5 are not sorted, hence swap again
5 11 12 13 6
Fourth Pass:
Now, the elements which are present in the sorted sub-array are 5, 11 and 12
Moving to the next two elements 13 and 6
5 11 12 13 6
Clearly, they are not sorted, thus perform swap between both
5 11 12 6 13
Now, 6 is smaller than 12, hence, swap again
5 11 6 12 13
Here, also swapping makes 11 and 6 unsorted hence, swap again
5 6 11 12 13
Finally, the list is completely sorted.
/* 5.1.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
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]
5.2.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.
Exampl
e:
Given array
elements:
0 1 2
1 12 9 5 6 10
3 4 5
Array is converted to Heap
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
0 1 2 3 4 5
81 89 9 1 14 76 54 22
6
7
Array is converted
1 to Heap
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 1
1
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.
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 1 14 9 54 89
1
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.
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 1 14 76 81 89
1
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 –
0 1 2 3 4 5 6 7
22 14 9 1 54 76 81 89
1
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.
Unit I CS3401 Algorithms 46
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 1 9 22 54 76 81 89
1
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 –
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 1 14 22 54 76 81 89
1
Now, heap has only one element left. After deleting it, heap will be empty.
# Change root
if largest !=
b:
array[b], array[largest] = array[largest], array[b]
heapify(array, a, largest)
# 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)
Execution:
Input
Original Array : [81, 89, 9, 11, 14, 76, 54, 22]
Output 5.2.6
Complexity Analysis of Heap sort
Time Complexity
Best case complexity - O(nlogn)
It occurs when there is no sorting required, i.e. the array is already sorted.
Worst case complexity - O(nlogn)
It occurs when the array elements are required to be sorted in reverse order. It
means suppose we need to sort the array elements in ascending order, but its
elements are in descending order.
Average case complexity - O(nlogn)