Design and Analysis of Algorithms
Design and Analysis of Algorithms
UNIT I: INTRODUCTION
Greedy Approach - Optimal Merge Patterns - Huffman Code - Job Sequencing with Deadline
- Tree Vertex Splitting - Dynamic Programming:- Dice Throw - Optimal Binary Search
algorithms.
References:
1. Thomas H. Cormen, Charles E. Leiserson, and Ronald L. Rivest and Clifford Stein,
“Introduction to Algorithms”, Third Edition, PHI Learning Private Limited, 2012.
2. Alfred V.Aho, John E.Goldberg, “Genetic Algorithm In Search Optimization and
Machine Learning”, Pearson Education India, 2013.
3. Anany Levitin, “Introduction to the Design and Analysis of Algorithms”, Third
Edition, Pearson Education, 2012
4. Ellis Horowitz, Sartaj Sahni and Sanguthevar Rajasekaran, “Fundamentals of
Computer Algorithms”, Second Edition, Universities Press, 2007
1
UNIT I:
1. INTRODUCTION
1.1 Algorithm:
2
1. Understanding the problem:
This is the first step in designing of algorithm.
Read the problem’s description carefully to understand the problem statement
completely.
Ask questions for clarifying the doubts about the problem.
Identify the problem types and use existing algorithm to find solution.
Input (instance) to the problem and range of the input get fixed.
3
b. Choosing between Exact and Approximate Problem Solving:
An algorithm that can solve the problem exactly and produce correct
result is called an exact algorithm.
The algorithm that can solve the problem approximately and not able
to produce exact solution is called an approximation algorithm.
Example: extracting square roots, solving nonlinear equations, and
evaluating definite integrals.
c. Algorithm Design Techniques:
An algorithm design technique is a general approach to solving
problems algorithmically. It is suitable for a variety of problems from
different areas of computing.
Programs = Algorithms + Data Structures
Algorithms and Data Structures are independent, but they are
combined together to develop program. The selection of proper data
structure is necessary before designing the algorithm.
Some of the design techniques are Brute Force, Divide and Conquer,
Dynamic Programming, Greedy Technique and so on.
3. Methods of specifying an algorithm:
There are 3 methods:
1. Natural language:
It is Easy but ambiguous. Such a specification creates difficulty while
actually implementing it. So many programmers prefer to have
specification of algorithm by means of Pseudocode.
2. Pseudo code:
It is a Mixture of programming language and natural language.
Pseudocode is usually more precise than natural language.
3. Flow chart:
It is a pictorial representation of the algorithm. Here symbols are used. It
is a method of expressing an algorithm by a collection of connected
geometric shapes containing descriptions of the algorithm’s steps.
4. Proving algorithms correctness:
4
The algorithm yields a required result for every legitimate input in a finite amount of
time. A common technique for proving correctness is to use mathematical induction because
an algorithm’s iterations provide a natural sequence of steps needed for such proofs. For an
approximation algorithm, the error produced by the algorithm does not exceed a predefined
limit.
5. Analyzing an algorithm:
Algorithm analysis is an important part and it is the determination of the amount of
time and space resources required to execute it.
Analysis of algorithm is the process of analyzing the problem-solving capability of
the algorithm in terms of the time and size required (the size of memory for storage while
implementation). However, the main concern of analysis of algorithms is the required time or
performance.
Types of Algorithm Analysis:
1. Best case: Define the input for which algorithm takes less time or minimum time. In
the best case calculate the lower bound of an algorithm.
2. Worst case: Define the input for which algorithm takes a long time or maximum
time. In the worst calculate the upper bound of an algorithm.
3. Average case: In the average case take all random inputs and calculate the
computation time for all inputs.
6. Coding an algorithm:
The coding / implementation of an algorithm is done by a suitable programming
language. It is very essential to write an optimized code (efficient code) to reduce the burden
of compiler.
1.3 Time Complexity
The time required by the algorithm to solve given problem is called time
complexity of the algorithm. Time complexity is very useful measure in algorithm
analysis.
The time complexity of an algorithm quantifies the amount of time taken by an
algorithm to run as a function of the length of the input.
It is important to note that the time to run is a function of the length of the input and
not the actual execution time of the machine on which the algorithm is running on.
Time complexity or Big O notation is typically written as T(n), where n is a variable
related to the size of the input.
5
There are different types of time complexities used, such as:
It also includes memory required by input variables. Basically, it's the sum of
auxiliary space and the memory used by input variables.
6
1) Fixed Part - It is independent of the characteristics of input and output. It includes
instruction (code) space, space for simple variables, fixed-size component variables
and constants.
2) Variable Part - It depends on instance characteristics. It consists of the space
needed by component variables whose size is dependent on the particular problem
instance being solved, the space needed by referenced variables, and the recursion
stack space.
Space complexity is a parallel concept to time complexity. If we need to create an array of
size n, this will require O(n) space. If we create a two-dimensional array of size n*n, this will
require O(n2) space.
There are many notations available to compute space complexity:
Big-O notation : OO:
This big-O notation describes the asymptotic upper bound, the worst case of space
complexity. It's the measure of the maximum amount of space the algorithm takes to
grow with respect to the size of input data.
Omega Notation: ΩΩ
The omega notation describes the asymptotic lower bound. It is the best-case scenario
in terms of time and space complexity. Actually, it's the minimum amount of space
our algorithm takes to grow with respect to the input data.
Theta Notation: θθ
The theta notation means that the space complexity of an algorithm is between its
upper-bound and lower-bound space complexity.
7
The space complexity is constant, so, it can be expressed in big-O notation as O (1).
8
1.6.3 Asymptotic Notations:
Asymptotic Notation is a way of comparing function that ignores constant factors and
small input sizes. Three notations are used to calculate the running time complexity of an
algorithm:
1. Big-oh notation:
Big-oh is the formal method of expressing the upper bound of an algorithm's running
time. It is the measure of the longest amount of time. The function f (n) = O (g (n)) [read as
"f of n is big-oh of g of n"] if and only if exist positive constant c and such that
f (n) ⩽ k.g (n)f(n)⩽k.g(n) for n>n0n>n0 in all case
Hence, function g (n) is an upper bound for function f (n), as g (n) grows faster than f (n)
Example:
9
Figure 1.3 Omega (Ω) Notation
Example:
f (n) =8n2+2n-3≥8n2-3
=7n2+(n2-3)≥7n2 (g(n))
Thus, k1=7
3. Theta (θ):
The function f (n) = θ (g (n)) [read as "f is the theta of g of n"] if and only if there
exists positive constant k1, k2 and k0 such that
k1 * g (n) ≤ f(n)≤ k2 g(n)for all n, n≥ n0
Example:
The Theta Notation is more precise than both the big-oh and Omega notation. The function f
(n) = θ (g (n)) if g(n) is both an upper and lower bound.
10
1.7 Complexity Analysis
Typical Complexities of an Algorithm
Constant Complexity:
It imposes a complexity of O(1). It undergoes an execution of a constant
number of steps like 1, 5, 10, etc. for solving a given problem. The count of
operations is independent of the input data size.
Logarithmic Complexity:
It imposes a complexity of O(log(N)). It undergoes the execution of the order
of log (N) steps. To perform operations on N elements, it often takes the logarithmic
base as 2.
For N = 1,000,000, an algorithm that has a complexity of O(log(N)) would
undergo 20 steps (with a constant precision). Here, the logarithmic base does not hold
a necessary consequence for the operation count order, so it is usually omitted.
11
Figure 1.6 Logarithmic Complexity
Linear Complexity:
It imposes a complexity of O(N). It encompasses the same number of steps as that
of the total number of elements to implement an operation on N elements.
For example, if there exist 500 elements, then it will take about 500 steps.
Basically, in linear complexity, the number of elements linearly depends on the
number of steps. For example, the number of steps for N elements can be N/2 or 3*N.
It also imposes a run time of O(n*log(n)). It undergoes the execution of the order
N*log(N) on N number of elements to solve the given problem.
For a given 1000 elements, the linear complexity will execute 10,000 steps for
solving a given problem.
12
Figure 1.7 Linear Complexity
Quadratic Complexity:
It imposes a complexity of O(n2). For N input data size, it undergoes the order
of N2 count of operations on N number of elements for solving a given problem.
If N = 100, it will endure 10,000 steps. In other words, whenever the order of
operation tends to have a quadratic relation with the input data size, it results in
quadratic complexity.
For example, for N number of elements, the steps are found to be in the order
of 3*N2/2.
13
For example, if there exist 100 elements, it is going to execute 1,000,000
steps.
Exponential Complexity:
It imposes a complexity of O(2n), O(N!), O(nk), …. For N elements, it will
execute the order of count of operations that is exponentially dependable on the input
data size.
For example, if N = 10, then the exponential function 2N will result in 1024.
Similarly, if N = 20, it will result in 1048 576, and if N = 100, it will result in a
number having 30 digits. The exponential function N! grows even faster; for example,
if N = 5 will result in 120. Likewise, if N = 10, it will result in 3,628,800 and so on.
14
Performance of an algorithm is a process of making evaluative judgement about
algorithms.
It can also be defined as follows...
Performance of an algorithm means predicting the resources which are required to an
algorithm to perform its task.
That means when we have multiple algorithms to solve a problem, we need to select a
suitable algorithm to solve that problem. We compare algorithms with each other which are
solving the same problem, to select the best algorithm. To compare algorithms, we use a set
of parameters or set of elements like memory required by that algorithm, the execution speed
of that algorithm, easy to understand, easy to implement, etc.,
Generally, the performance of an algorithm depends on the following elements...
1. Whether that algorithm is providing the exact solution for the problem?
2. Whether it is easy to understand?
3. Whether it is easy to implement?
4. How much space (memory) it requires to solve the problem?
5. How much time it takes to solve the problem? Etc.,
When we want to analyse an algorithm, we consider only the space and time required by that
particular algorithm and we ignore all the remaining elements. Based on this information,
performance analysis of an algorithm can also be defined as follows...
Performance analysis of an algorithm is the process of calculating space and time required by
that algorithm.
The following metrics are used to evaluate the performance of an algorithm:
The amount of space necessary to perform the algorithm’s task (Space Complexity). It
consists of both program and data space.
The time necessary to accomplish the algorithm’s task (Time Complexity).
15
Notation of Performance Measurement
b. Big – Ω (Omega)
16
Figure 1.12 Big - Ω
The omega notation represents the lowest bound of an algorithm’s execution time. It
specifies the shortest time an algorithm requires for all input values. It is the best-case
scenario for the time complexity of an algorithm.
c. Big – Θ (Theta)
17
Test data is the specific input values or datasets used to evaluate the algorithm's
behavior and performance.
It's essential to use a diverse set of test cases that cover a range of scenarios and input
configurations. This helps ensure that the algorithm performs well in various
situations.
Test data may include best-case, average-case, and worst-case scenarios to provide a
comprehensive understanding of the algorithm's efficiency under different conditions.
1.11. Experimental Setup
The experimental setup includes all the details and conditions under which the
algorithm is tested and analysed.
Parameters such as the hardware (e.g., CPU, memory), software environment,
programming language, and compiler settings should be specified.
The experimental setup should be well-documented and reproducible to allow others
to verify and replicate the results.
The choice of programming language and compiler can impact the algorithm's
performance, so it's crucial to provide this information.
SAMPLE QUESTIONS:
1. Define an algorithm and explain its key characteristics.
2. Explain the difference between best-case, worst-case, and average-case time
complexities. Provide an example for each.
3. Analyze the space complexity of a recursive algorithm to find the factorial of a
number.
4. Compare and contrast time complexity and space complexity. How can an algorithm
be optimized in terms of space complexity?
5. Define Big O notation and explain its significance in analyzing algorithmic
complexity.
6. Given two functions f(n) = 2n^2 + 3n and g(n) = n^2 + 5, determine whether f(n) is
O(g(n)).
7. Discuss the importance of complexity analysis in the design and evaluation of
algorithms.
8. Compare and contrast time complexity and computational complexity. Provide
examples to illustrate the concepts.
18
9. Explain the concept of empirical analysis in the context of algorithm performance
measurement.
10. How does the instance size affect the performance of an algorithm? Provide examples
to illustrate your answer.
11. Describe the role of test data in evaluating the efficiency of an algorithm. How can
different types of data impact performance?
19
UNIT II: Mathematical Foundations
2.1. Solving Recurrence equations
F(n)=4f(n/2)
F(2n)=4f(n)
F(n)=n2
So, T(n) is order of n2
Guess T(n)=O(n3)
20
Step 2: verify the induction
Assume T(k)<=ck3
T(n)=4T(n/2)+n
<=4c(n/2)3 +n
<=cn3/2+n
<=cn3-(cn3/2-n)
T(n)<=cn3 as (cn3/2 –n) is always positive
So what we assumed was true.
T(n)=O(n3)
Step 3: solve for constants
Cn3/2-n>=0
n>=1
c>=2
Now suppose we guess that T(n)=O(n2) which is tight upper bound
Assume (k)<=ck2
So, we should prove that T(n)<=cn2
T(n)=4T(n/2)+n
4c(n/2)2+n
cn2+n
So, T(n) will never be less than cn2. But if we will take the assumption of T(k)=c1 k2-c2k,
then we can find that T(n) = O(n2)
2.3 Iterative Method
Example:
T(n)=2T(n/2)+n
=> 2[2T(n/4) + n/2 ]+n
=>22T(n/4)+n+n
=> 22[2T(n/8)+ n/4]+2n
=>23T(n/23) +3n
After k iterations, T(n)=2kT(n/2k)+kn--------------(1)
Sub problem size is 1 after n/2k=1 => k=logn
So,after logn iterations ,the sub-problem size will be 1.
So,when k=logn is put in equation 1
21
T(n)=nT(1)+nlogn
nc+nlogn ( say c=T(1))
O(nlogn)
2.4 Recursion Tree Method
In a recursion tree, each node represents the cost of a single sub-problem somewhere
in the set of recursive problems invocations .we sum the cost within each level of the
tree to obtain a set of per level cost, and then we sum all the per level cost to
determine the total cost of all levels of recursion.
Constructing a recursion tree for the recurrence T(n)=3T(n/4)+cn2
22
Constructing a recursion tree for the recurrence T (n)= 3T (n=4) + cn2.. Part (a) shows T (n),
which progressively expands in (b)–(d) to form the recursion tree. The fully expanded tree in
part(d) has height log4n (it has log4n + 1 levels).
23
using case 2
f(n)=ϴ( nlog22logkn)
=>ϴ(n1 logkn)=nlogn =>K=1
T(n)=ϴ( nlog22 log1n)
=>ϴ(nlog2n)
2.6 Worst Case, Average Case and Best Case Analysis:
The worst, average, and best case of an algorithm depends on the size of the user
input value.
2.6.1 Worst Case Analysis:
In the worst-case analysis, calculate the upper limit of the execution time of an
algorithm. It is necessary to know the case which causes the execution of the maximum
number of operations.
2.6.2 Average Case Analysis:
In the average case analysis, take all possible inputs and calculate the computation
time for all inputs. Add up all the calculated values and divide the sum by the total number of
entries.
2.6.3 Best Case Analysis:
In the best case analysis, calculate the lower bound of the execution time of an
algorithm. It is necessary to know the case which causes the execution of the minimum
number of operations.
2.6.4 Example: Worst Case, Average Case and Best Case Analysis:
Merge sort is a sorting algorithm that is trivial to apply and has a time complexity of
O(n∗logn) for all conditions (best case, worst case and average case). This algorithm is based
on the divide and conquers strategy.
The sorting algorithm continuously splits a list into multiple sublists until each sublist
has only one item left. It then merges those sublists into a sorted list.
What is Merge Sort?
Merge Sort, considered to be the most efficient sorting algorithm, works on the
principle 'Divide and Conquer'.
The algorithm can be divided into three parts:
1. Divide - It is the initial stage where the midpoint of the array is found using
mid=start+(end−start)/2
24
2. Conquer - In this step, the array is divided into subarrays, using the midpoint
calculated. The process repeats itself recursively until all elements become single
array elements.
3. Combine - In this step, the subarrays formed are combined in sorted order.
Merge sort repeatedly divides the array into two equally sized parts.
Thus merge sort time complexity depends on the number of division stages.
Step 1 - n×1
Step 2 - n/2×2
Step 3 - n/4×4
Merge Sort time complexity is calculated using time per division stage. Since the
merge process has linear time complexity, for n elements there will be n∗log 2 n division and
merge stages.
Hence, regardless of the arrangement, the time complexity of Merge Sort is O(nlogn)
25
the minimum number of comparisons will be 2n. The worst-case time complexity of merge
sort is O(n∗logn).
2.6.4.3 Analysis of Merge Sort Space Complexity
In merge sort, all elements are copied into an auxiliary array of size N, where N is
the number of elements present in the unsorted array. Hence, the space complexity for
Merge Sort is O(N).
Conclusion
Merge sort is a reliable sorting algorithm that uses a 'divide and conquers' paradigm.
Initially, it divides the problem into sub-problems and solves them individually.
Then, it combines the results of sub-problems to get the solution to the original
problem.
Merge sort is a recursive sorting algorithm.
The recurrence relation of merge sort is T(n)=2T(n/2)+θ(n).
The time complexity of merge sort is
o Best Case: O(n∗logn)
o Average Case: O(n∗logn)
o Worst Case: O(n∗logn)O
The space complexity of merge sort is O(n).
2.7 Sorting In Linear Time
There are several algorithms that can sort n numbers in O(n lg n) time. Merge sort and
heap sort achieve this upper bound in the worst case; quicksort achieves it on average.
Moreover, for each of these algorithms, produce a sequence of n input numbers that causes
the algorithm to run in (n lg n) time.
In these algorithms the sorted order is determined based only on comparisons between
the input elements. These types of sorting algorithms are called as comparison sorts. Any
comparison sort must make (n lg n) comparisons in the worst case to sort a sequence of n
elements. Thus, merge sort and heap sort are asymptotically optimal, and no comparison sort
exists that is faster by more than a constant factor.
2.8 Lower bounds for sorting
Comparison sort, use only comparisons between elements to gain order information
about an input sequence a1, a2, . . . ,an) That is, given two elements ai and aj, perform one of
the tests ai < aj, ai aj, ai = aj, ai aj, or ai > aj to determine their relative order. This sorting
26
not inspects the values of the elements or gain order information about them in any other
way.
Let as assume without loss of generality that all of the input elements are distinct.
Given this assumption, comparisons of the form ai = aj are useless, so assume that no
comparisons of this form are made. Also note that the comparisons ai aj, ai aj, ai > aj, and ai
< aj are all equivalent in that they yield identical information about the relative order of ai and
aj. Therefore assume that all comparisons have the form ai aj.
Figure 2.1
The decision tree for insertion sort operating on three elements. There are 3! = 6
possible permutations of the input elements, so the decision tree must have at least 6
leaves.
2.8.1The decision-tree model
Comparison sorts can be viewed abstractly in terms of decision trees. A decision tree
represents the comparisons performed by a sorting algorithm when it operates on an input of
a given size. Control, data movement, and all other aspects of the algorithm are ignored. The
above Figure shows the decision tree corresponding to the insertion sort algorithm from
operating on an input sequence of three elements.
In a decision tree, each internal node is annotated by ai : aj for some i and j in the
range 1<=i, j <= n, where n is the number of elements in the input sequence. Each leaf is
annotated by a permutation (1), (2), . . . , (n) . The execution of the sorting algorithm
corresponds to tracing a path from the root of the decision tree to a leaf. At each internal
node, a comparison ai <= aj is made. The left subtree then dictates subsequent comparisons
for ai <= aj, and the right subtree dictates subsequent comparisons for ai > aj. When we come
to a leaf, the sorting algorithm has established the ordering a (1) a (2) ... a (n). Each
of the n! permutations on n elements must appear as one of the leaves of the decision tree for
the sorting algorithm to sort properly.
27
2.8.2 A lower bound for the worst case
The length of the longest path from the root of a decision tree to any of its leaves
represents the worst-case number of comparisons the sorting algorithm performs.
Consequently, the worst-case number of comparisons for a comparison sort corresponds to
the height of its decision tree. A lower bound on the heights of decision trees is therefore a
lower bound on the running time of any comparison sort algorithm. The following theorem
establishes such a lower bound.
Theorem 1
Proof: Consider a decision tree of height h that sorts n elements. Since there are n!
permutations of n elements, each permutation representing a distinct sorted order, the tree
must have at least n! leaves. Since a binary tree of height h has no more than 2h leaves, we
have
n! 2h ,
h log(n!) ,
Since the log function is monotonically increasing. From Stirling's approximation, we have
Corollary: Heap sort and merge sort are asymptotically optimal comparison sorts.
Proof: The O(n log n) upper bounds on the running times for heap sort and merge sort match
the (n log n) worst-case lower bound from Theorem 1.
28
2.9 Counting sort
Counting Sort was discovered by Harold Seward in 1954. Counting sort is a linear
sorting algorithm with asymptotic complexity O(n+k). The Counting Sort method is a fast
and reliable sorting algorithm. Counting sort is not a comparison-based algorithm. It avoids
comparisons and takes advantage of the array's O(1) time insertions and deletions.
Counting sort assumes that each of the n input elements is an integer in the range 1
to k, for some integer k. When k = O(n), the sort runs in O(n) time.
Counting sort is a sorting technique that is based on the keys between specific ranges. It
performs sorting by counting objects having distinct key values like hashing. After that, it
performs some arithmetic operations to calculate each object's index position in the output
sequence. Counting sort is not used as a general-purpose sorting algorithm.
Counting sort is effective when range is not greater than number of objects to be
sorted. It can be used to sort the negative input values.
To understand the working of the counting sort algorithm, let's take an unsorted array. It will
be easier to understand the counting sort via an example.
Figure 2.2
1. Find the maximum element from the given array. Let max be the maximum element.
Figure 2.3
2. Now, initialize array of length max + 1 having all 0 elements. This array will be used to
store the count of the elements in the given array.
29
Figure 2.4
3. Now, we have to store the count of each array element at their corresponding index in the
count array.
The count of an element will be stored as - Suppose array element '4' is appeared two
times, so the count of element 4 is 2. Hence, 2 is stored at the 4th position of the count array.
If any element is not present in the array, place 0, i.e. suppose element '3' is not present in the
array, so, 0 will be stored at 3rd position.
Figure 2.5
Figure 2.6
Now, store the cumulative sum of count array elements. It will help to place the elements at
the correct index of the sorted array.
Figure 2.7
Figure 2.8
Figure 2.9
30
4. Now, find the index of each element of the original array
Figure 2.10
After placing element at its place, decrease its count by one. Before placing element 2, its
count was 2, but after placing it at its correct position, the new count for element 2 is 1.
Figure 2.11
Figure 2.12
31
2.9.1 Complexity Analysis of Counting Sort:
Time Complexity: O(N+M), where N and M are the size of inputArray[] and
countArray[] respectively.
o Worst-case: O(N+M).
o Average-case: O(N+M).
o Best-case: O(N+M).
Auxiliary Space: O(N+M), where N and M are the space taken by outputArray[]
and countArray[] respectively.
2.9.2 Advantages of Counting Sort
Counting sort generally performs faster than all comparison-based sorting algorithms,
such as merge sort and quicksort, if the range of input is of the order of the number of
input.
Counting sort is easy to code
Counting sort is a stable algorithm.
Counting sort is not an In-place sorting algorithm, It uses extra space for sorting the
array elements.
32
2.10 Radix sort
Radix sort is the linear sorting algorithm that is used for integers. In Radix sort, there
is digit by digit sorting is performed that is started from the least significant digit to the most
significant digit. It is an efficient sorting algorithm for integers or strings with fixed-size
keys. Radix sort algorithm is a non-comparative sorting algorithm in computer science. It
avoids comparison by creating and categorizing elements based on their radix.
Algorithm
1. radixSort(arr)
2. max = largest element in the given array
3. d = number of digits in the largest element (or, max)
4. Now, create d buckets of size 0 - 9
5. for i -> 0 to d
6. sort the array elements using counting sort (or any stable sort) according to the digits
at the ith place
In a typical computer, which is a sequential random-access machine, radix sort is
sometimes used to sort records of information that are keyed by multiple fields. For example,
we might wish to sort dates by three keys: year, month, and day. We could run a sorting
algorithm with a comparison function that, given two dates, compares years, and if there is a
tie, compares months, and if another tie occurs, compares days. Alternatively, we could sort
the information three times with a stable sort: first on day, next on month, and finally on year.
2.10.1 Working of Radix sort Algorithm
First, to find the largest element (suppose max) from the given array. Suppose 'x' be
the number of digits in max. The 'x' is calculated because it is needed to go through
the significant places of all elements.
After that, go through one by one each significant place. Here, we have to use any
stable sorting algorithm to sort the digits of each significant place.
Figure 2.13
In the given array, the largest element is 736 that have 3 digits in it. So, the loop will
run up to three times (i.e., to the hundreds place). That means three passes are required to
sort the array.
33
Now, first sort the elements on the basis of unit place digits (i.e., x = 0). Here, we are
using the counting sort algorithm to sort the elements.
Pass 1:
In the first pass, the list is sorted on the basis of the digits at 0's place.
Figure 2.14
After the first pass, the array elements are –
Figure 2.15
Pass 2:
In this pass, the list is sorted on the basis of the next significant digits (i.e., digits at 10th
place).
Figure 2.16
After the second pass, the array elements are -
34
Figure 2.17
Pass 3:
In this pass, the list is sorted on the basis of the next significant digits (i.e., digits at 100 th
place).
Figure 2.18
Figure 2.19
35
Worst Case Complexity - It occurs when the array elements are required to be sorted
in reverse order. That means suppose you have to sort the array elements in ascending
order, but its elements are in descending order. The worst-case time complexity of
Radix sort is O(nk).
Radix sort is a non-comparative sorting algorithm that is better than the comparative
sorting algorithms. It has linear time complexity that is better than the comparative
algorithms with complexity O(n logn).
b) Space Complexity
The space complexity of Radix sort is O(n + k). Radix sort employs Counting sort,
which uses auxiliary arrays of sizes n and k, where n is the number of elements in the input
array and k is the largest element among the dth place elements (ones, tens, hundreds, and so
on) of the input array. Hence, the Radix sort has a space complexity of (n+k).
2.11 Bucket Sort
It is a sorting technique that involves dividing elements into various groups, or
buckets. These buckets are formed by uniformly distributing the elements. Once the elements
are divided into buckets, they can be sorted using any other sorting algorithm. Finally, the
sorted elements are gathered together in an ordered fashion.
The basic procedure of performing the bucket sort is given as follows –
First, partition the range into a fixed number of buckets.
Then, toss every element into its appropriate bucket.
After that, sort each bucket individually by applying a sorting algorithm.
And at last, concatenate all the sorted buckets.
Create n empty buckets (Or lists) and do the following for every array element arr[i].
Insert arr[i] into bucket[n*array[i]]
Sort individual buckets using insertion sort.
Concatenate all sorted buckets.
Algorithm
Bucket Sort(A[])
2. n=A.length[A]
36
4. make B[i] an empty list
5. for i=1 to n
End
37
Figure 2.20
Step 2
Now, we insert 0.17 at index ⌊10 ×0.17⌋=1
Figure 2.21
Step 3
Inserting the next element, 0.93 into the output buckets at ⌊10 ×0.93⌋=9
38
Figure 2.22
Step 4
Figure 2.23
39
Step 5
Inserting the next element in the input array, 0.26, at position ⌊10 ×0.26⌋=2
Figure 2.24
Step 6
Here is where it gets tricky. Now, the next element in the input list is 0.72 which
needs to be inserted at index ‘7’ using the formula ⌊10 ×0.72⌋=7. But there’s already a
number in the 7th bucket. So, a link is created from the 7th index to store the new number like
a linked list, as shown below –
Figure 2.25
40
Step 7
Add the remaining numbers to the buckets in the similar manner by creating linked lists from
the desired buckets. But while inserting these elements as lists, we apply insertion sort, i.e.,
compare the two elements and add the minimum value at the front as shown below −
Figure 2.26
Step 8
0.12, 0.17, 0.21, 0.26, 0.28, 0.33, 0.39, 0.72, 0.78, 0.93
Here we discuss the time complexity of bucket sort in best case, average case, and in worst
case and the space complexity of the bucket sort.
1. Time Complexity
41
If we use the insertion sort to sort the bucket elements, the overall complexity
will be linear, i.e., O(n + k), where O(n) is for making the buckets, and O(k) is for
sorting the bucket elements using algorithms with linear time complexity at best case.
The best-case time complexity of bucket sort is O(n + k).
Average Case Complexity –
It occurs when the array elements are in jumbled order that is not properly
ascending and not properly descending. Bucket sort runs in the linear time, even when
the elements are uniformly distributed. The average case time complexity of bucket
sort is O(n + K).
Worst Case Complexity –
In bucket sort, worst case occurs when the elements are of the close range in
the array, because of that, they have to be placed in the same bucket. So, some
buckets have more number of elements than others. The complexity will get worse
when the elements are in the reverse order. The worst-case time complexity of bucket
sort is O(n2).
2. Space Complexity
SAMPLE QUESTIONS:
1. Solve the recurrence relation T(n) = 2T(n/2) + n, where T(1) = 1, using the
substitution method.
2. Use the iterative method to solve the recurrence T(n) = 3T(n/2) + 1, with T(1) = 1.
3. Apply the recursion tree method to analyze the recurrence T(n) = T(n/3) + T(2n/3) +n.
4. Solve the recurrence relation T(n) = 4T(n/2) + n^2, where T(1) = 1, using the master
method.
5. Prove the lower bound for comparison-based sorting algorithms is Ω(n log n) using a
decision tree.
6. Implement the Counting Sort algorithm and demonstrate its application on an array of
integers.
7. Explain the Radix Sort algorithm and its time complexity. Compare it with other
sorting algorithms in terms of performance.
8. Describe the Bucket Sort algorithm and discuss its suitability for sorting floating-
point numbers.
42
9. Analyze the best-case, average-case, and worst-case time complexity of the Bubble
Sort algorithm.
10. Compare the time complexity of QuickSort and MergeSort in the average case.
Explain your findings.
43
UNIT III: Brute Force and Divide-And-Conquer
3.1 Brute Force Algorithm
Brute force is a straightforward, exhaustive algorithmic technique that involves
systematically trying all possible solutions to a problem until the correct one is found. It is a
general problem-solving approach that can be applied to a wide range of problems, but it is
not always the most efficient method, especially for large-scale or complex problems.
How a brute force algorithm works:
1. Generate all possible solutions: The algorithm generates all possible solutions to the
problem. This often involves trying every combination of elements or values within a
given range.
2. Test each solution: For each generated solution, the algorithm tests whether it
satisfies the conditions of the problem or meets the desired criteria.
3. Evaluate results: If a solution is found that satisfies the problem's conditions, the
algorithm stops and returns that solution. If no solution is found, the algorithm may
return a special value indicating failure.
Advantages of a brute-force algorithm
This algorithm finds all the possible solutions, and it also guarantees that it finds the
correct solution to a problem.
This type of algorithm is applicable to a wide range of domains.
It is mainly used for solving simpler and small problems.
It can be considered a comparison benchmark to solve a simple problem and does not
require any particular domain knowledge.
Disadvantages of a brute-force algorithm
It is an inefficient algorithm as it requires solving each and every state.
It is a very slow algorithm to find the correct solution as it solves each state without
considering whether the solution is feasible or not.
The brute force algorithm is neither constructive nor creative as compared to other
algorithms.
3.2 Travelling Salesman Problem:
The Brute Force approach, also known as the Naive Approach, calculates and
compares all possible permutations of routes or paths to determine the shortest unique
solution.
44
To solve the TSP using the Brute-Force approach, you must calculate the total number
of routes and then draw and list all the possible routes. Calculate the distance of each route
and then choose the shortest one—this is the optimal solution.
Problem Statement
The problem states that "What is the shortest possible route that helps the salesman visits
each city precisely once and returns to the origin city, provided the distances between each
pair of cities given?"
Example
Consider City1, City2, City3, City4 and the distances between them as shown below. A
salesman has to start from City 1, travel through all the cities and return to City1. There are
various possible routes, but the problem is to find the shortest possible route.
Figure 3.1
City(1 - 2 - 3 - 4 - 1) 95
City(1 - 2 - 4 - 3 - 1) 80
City(1 - 3 - 2 - 4 - 1) 95
Since we are travelling in a loop (source and destination are the same), paths like (1 - 2 - 3 - 4
- 1) and (1 - 4 - 3 - 2 - 1) are considered identical.
Out of the above three routes, route 2 is optimal. So, this will be the answer.
45
Figure 3.2
46
1. Logistics and transportation: The TSP is used to optimize the routing of delivery
trucks, sales representatives, and service technicians. By finding the shortest route that
visits all necessary locations, TSP can help minimize travel time and reduce
transportation costs.
2. Circuit design: In the field of electronics, TSP is used to optimize the routing of
electrical signals in a printed circuit board, which is a critical step in the design of
electronic circuits.
3. DNA sequencing: In bioinformatics, TSP is used to find the shortest path to sequence
a DNA molecule. DNA sequencing involves finding the order of nucleotides in a
DNA molecule, which can be modeled as a TSP.
4. Network design: TSP is used to optimize the design of communication networks,
such as the placement of cell towers, routing of fiber-optic cables, and design of
wireless mesh networks.
5. Robotics: TSP can be applied in robotics to optimize the path planning of robots to
visit a set of locations in the most efficient manner.
These are just a few examples of the many applications of TSP. The problem is
widely studied and has numerous practical applications in various fields.
3.3 Knapsack Problem
A knapsack problem algorithm is a constructive approach to combinatorial
optimization. The problem is basically about a given set of items, each with a specific weight
and a value. Therefore we needs to determine each item’s number to include in a knapsack so
that the total weight is less than or equal to a given limit. And also, the total value is
maximum.
The knapsack problem is a classic optimization problem where the goal is to
maximize the total value of items included in a knapsack without exceeding its capacity. The
brute-force method involves considering all possible combinations of items and selecting the
one that satisfies the capacity constraint while maximizing the total value.
The Knapsack problem has two categories.
0-1 Knapsack Problem
Fractional Knapsack Problem
3.3.1 0 - 1 Knapsack Problem
Let’s consider values and weights of n different items. One has to put these items in a
knapsack of capacity C such that the knapsack has the maximum value. Also, the sum of
weights of all the items present in the knapsack should not exceed the capacity C. Since it is a
47
0 - 1 knapsack problem; hence, splitting the item is not allowed, i.e., one can never break any
given item, either do not pick it or pick it (0 - 1 property).
3.3.2 Fractional Knapsack Problem
The fractional knapsack problem is similar to the 0 - 1 knapsack problem. The only
difference is one can choose an item partially. Thus, the liberty is given to break any item
then put it in the knapsack, such that the total value of all the items (broken or not broken)
present in the knapsack is maximized.
Given some items, pack the knapsack to get the maximum total value. Each item
has some weight and some value. Total weight that we can carry is no more than
some fixed number W. So we must consider weights of items as well as their
values.
1. Given a knapsack with maximum capacity W, and a set S consisting
of n items
2. Each item i has some weight wi and benefit value vi(all wi and W are
integer values)
3. Problem: How to pack the knapsack to achieve maximum total value
of packed items?
Problem, in other words, is to find
max vi subject to w i W
iS iS
Given n items:
• weights: w1 w2 … w n
• values: v1 v2 … vn
• a knapsack of capacity W
Find most valuable subset of the items that fit into the knapsack
Example 1: Knapsack capacity W=16
Table 3.2.
Item Weight Value
1 2 $20
2 5 $30
3 10 $50
4 5 $10
48
Solution:
Table 3.3.
{1} 2 $20
{2} 5 $30
{3} 10 $50
{4} 5 $10
{1,2} 7 $50
{1,3} 12 $70
{1,4} 7 $30
{2,3} 15 $80
{2,4} 10 $40
{3,4} 15 $60
{1,2,3} 17 not feasible
{1,2,4} 12 $60
{1,3,4} 17 not feasible
{2,3,4} 20 not feasible
{1,2,3,4} 22 not feasible
Example: 2
49
For example consider that there are n people and n jobs. Each person has to be
assigned only one job. When the jth job is assigned to pth person the cost incurred is
represented by C.
C=C[p,j]
Where, p=1,2,3. .... n
J=1,2,3. ..... n
The number of permutations (the number of different assignments to different persons) is n!
The exhaustive search is impractical for large value of n. Let us consider 4 persons (P1,P2,P3
and P4) and 4 jobs(J1,J2,J3 and J4).
Here n=4.Here the number of possible and different types of assignment is 4!
n! = 4!
=4 x 3 x 2 x 1=24
The below table shows the entries representing the assignment costs C[p,j].
Table 3.5.
Job J1 J2 J3 J4
Person
P1 9 2 7 8
P2 6 4 3 7
P3 5 8 1 8
P4 7 6 9 4
Iterations of solving the above assignment problem are given below. Here 4 persons
indicated by P1, P2, P3 and P4; Similarly 4 jobs are indicated by J1, J2, J3 and J4.
Let us consider that the assignments can be grouped into 4 groups.
In the first group J1 is assigned to person P1. The remaining jobs J2, J3 and J4 are
assigned to persons P2, P3 and P4. The number of ways in which these three jobs can be
assigned to three persons is 3!(3!=6).
Group-I
P1 P2 P3 P4
9+4+1+8 = 18
J1 J2 J3 J4
9+4+8+9 = 30
P1 P2 P3 P4
J1 J2 J4 J3
50
9+3+8+4= 24
P1 P2 P3 P4
J1 J3 J2 J4
9+3+8+6= 26
P1 P2 P3 P4
J1 J3 J4 J2
9+7+8+9= 33
P1 P2 P3 P4
J1 J4 J2 J3
9+7+1+6= 23
P1 P2 P3 P4
J1 J4 J3 J2
Group-2
In the second group J2 is assigned to person P1. The remaining jobs J3,J4,J1 are
assigned to persons P2,P3 and P4. The number of ways in which these three jobs can be
assigned to three persons is 3!(3!=6).
P1 P2 P3 P4 2+3+8+7 = 20
J2 J3 J4 J1
2+6+1+4= 13
P1 P2 P3 P4
J2 J1 J3 J4
2+3+5+4 = 14
P1 P2 P3 P4
J2 J3 J1 J4
2+7+1+7= 17
P1 P2 P3 P4
J2 J4 J3 J1
51
2+7+5+9= 23
P1 P2 P3 P4
J2 J4 J1 J3
2+6+8+9= 25
P1 P2 P3 P4
J2 J1 J4 J3
Group-3
In the third group J3 is assigned to person P1. The remaining jobs J2,J4,J1 are
assigned to persons P2,P3 and P4. The number of ways in which these three jobs can be
assigned to three persons is 3!(3!=6).
P1 P2 P3 P4
J3 J4 J1 J2 7+7+5+6 = 25
7+7+8+7 = 29
P1 P2 P3 P4
J3 J4 J2 J1
7+6+8+6= 27
P1 P2 P3 P4
J3 J1 J4 J2
7+4+8+7= 26
P1 P2 P3 P4
J3 J2 J4 J1
7+6+8+4= 25
P1 P2 P3 P4
J3 J1 J2 J4
7+4+5+4= 20
P1 P2 P3 P4
J3 J2 J1 J4
52
Group-4
In the Fourth group J4 is assigned to person P1. The remaining jobs J2, J3, J1 are
assigned to persons P2, P3 and P4. The number of ways in which these three jobs can be
assigned to three persons is 3!(3!=6).
P1 P2 P3 P4
8+6+8+9 = 31
J4 J1 J2 J3
8+6+1+6 = 21
P1 P2 P3 P4
J4 J1 J3 J2
8+4+5+9= 26
P1 P2 P3 P4
J4 J2 J1 J3
8+4+1+7= 20
P1 P2 P3 P4
11
J4 J2 J3 J1
8+3+5+6= 22
P1 P2 P3 P4
J4 J3 J1 J2
8+3+8+7= 26
P1 P2 P3 P4
J3 J3 J2 J1
Table 3.6
Group 1- 1st iteration is lowest 18
Group-II – 2nd iteration is lowest 13
Group-III- 6th iteration is lowest 20
Group-IV- 4th iteration is lowest 20
53
3.4.1 Complexity Analysis
The O(n! * n) time complexity is significant and makes the brute-force method
impractical for larger instances of the assignment problem. The factorial growth in the
number of permutations quickly becomes computationally infeasible as the number of agents
or tasks increases.
3.5 Closest-Pair and Convex-Hull Problems
Let us consider a straight forward approach (Brute Force) to two well-known
problems dealing with a finite set of points in the plane. These problems are very useful in
important applied areas like computational geometry and operations research.
3.5.1 Closest-Pair Problem
The closest-pair problem finds the two closest points in a set of n points. It is the
simplest of a variety of problems in computational geometry that deals with proximity of
points in the plane or higher-dimensional spaces.
Consider the two-dimensional case of the closest-pair problem. The points are specified in a
standard fashion by their (x, y) Cartesian coordinates and that the distance between two
points pi(xi, yi) and pj(xj, yj ) is the standard Euclidean distance.
Euclidean distance d(Pi, Pj) = √[(xi-xj)2 + (yi-yj)2]
The following algorithm computes the distance between each pair of distinct points
and finds a pair with the smallest distance.
ALGORITHM BruteForceClosestPair(P)
//Finds distance between two closest points in the plane by brute force
//Input: A list P of n (n ≥ 2) points p1(x1, y1), . . . , pn(xn, yn)
//Output: The distance between the closest pair of points
d←∞
for i ←1 to n − 1 do
for j ←i + 1 to n do
d ←min(d, sqrt((xi− xj )2 + (yi− yj )2)) //sqrt is square root
return d
The basic operation of the algorithm will be squaring a number. The number of times
it will be executed can be computed as follows:
= 2∑j=i+1n (n-i)
54
= 2n(n-1)/2 ε Θ(n2)
Of course, speeding up the innermost loop of the algorithm could only decrease the
algorithm’s running time by a constant factor, but it cannot improve its asymptotic efficiency
class.
3.5.2 Convex-Hull Problem
3.5.2.1 Convex Set:
A set of points (finite or infinite) in the plane is called convex if for any two points p and q in
the set, the entire line segment with the endpoints at p and q belongs to the set.
All the sets depicted in Figure (3.3) are convex, and so are a straight line, a triangle,
a rectangle, and, more generally, any convex polygon, a circle, and the entire plane.
On the other hand, the sets depicted in Figure (3.4), any finite set of two or more
distinct points, the boundary of any convex polygon, and a circumference are examples of
sets that are not convex.
Take a rubber band and stretch it to include all the nails, and then let it snap into place. The
convex hull is the area bounded by the snapped rubber band as shown in Figure (3.5).
55
3.5.2.2 Convex hull
The convex hull of a set S of points is the smallest convex set containing S. (The
smallest convex hull of S must be a subset of any convex set containing S.)
If S is convex, its convex hull is obviously S itself. If S is a set of two points, its
convex hull is the line segment connecting these points. If S is a set of three points not on the
same line, its convex hull is the triangle with the vertices at the three points given; if the three
points do lie on the same line, the convex hull is the line segment with its endpoints at the
two points that are farthest apart. For an example of the convex hull for a larger set, see
Figure (3.6).
THEOREM
The convex hull of any set S of n>2 points not all on the same line is a convex
polygon with the vertices at some of the points of S. (If all the points do lie on the same line,
the polygon degenerates to a line segment but still with the endpoints at two points of S.)
Figure 3.6 The Convex hull for this set of eight points is the convex polygon with vertices at
p1, p5, p6, p7 and p3
The convex-hull problem is the problem of constructing the convex hull for a given
set S of n points. To solve it, we need to find the points that will serve as the vertices of the
polygon in question. Mathematicians call the vertices of such a polygon “extreme points.” By
definition, an extreme point of a convex set is a point of this set that is not a middle point of
any line segment with endpoints in the set. For example, the extreme points of a triangle are
its three vertices, the extreme points of a circle are all the points of its circumference, and the
extreme points of the convex hull of the set of eight points in Figure 3.6 are p1, p5, p6, p7, and
p3.
Extreme points have several special properties other points of a convex set do not
have. One of them is exploited by the simplex method, a very important algorithm. This
56
algorithm solves linear programming problems, which are problems of finding a minimum
or a maximum of a linear function of n variables subject to linear constraints. We are
interested in extreme points because their identification solves the convex-hull problem.
Actually, to solve this problem completely, we need to know a bit more than just which of n
points of a given set extreme points of the set’s convex hull are: we need to know which pairs
of points need to be connected to form the boundary of the convex hull. Note that this issue
can also be addressed by listing the extreme points in a clockwise or a counter clockwise
order.
How can we solve the convex-hull problem in a brute-force manner? If you do not see
an immediate plan for a frontal attack, do not be dismayed: the convex-hull problem is one
with no obvious algorithmic solution. Nevertheless, there is a simple but inefficient algorithm
that is based on the following observation about line segments making up the boundary of a
convex hull: a line segment connecting two points pi and pj of a set of n points is a part of the
convex hull’s boundary if and only if all the other points of the set lie on the same side of the
straight line through these two points. Repeating this test for every pair of points yields a list
of line segments that make up the convex hull’s boundary.
A few elementary facts from analytical geometry are needed to implement this
algorithm. First, the straight line through two points (x1, y1), (x2, y2) in the coordinate plane
can be defined by the equation
ax + by = c,
where a = y2 − y1, b = x1 − x2, c = x1y2 − y1x2.
Second, such a line divides the plane into two half-planes: for all the points in one of
them, ax + by > c, while for all the points in the other, ax + by < c. (For the points on the line
itself, of course, ax + by = c.) Thus, to check whether certain points lie on the same side of
the line, we can simply check whether the expression ax + by − c has the same sign for each
of these points.
The time efficiency of this algorithm is O (n3): for each of n(n − 1)/2 pairs of distinct
points, we may need to find the sign of ax + by − c for each of the other n − 2 points.
57
3.6 Divide and Conquer Approach
A divide and conquer algorithm works by recursively breaking down a problem into
two or more sub-problems of the same (or related) type (divide), until these become simple
enough to be solved directly (conquer).
O(1) if n is small
T(n) = f1(n) + 2T(n/2) + f2(n)
Advantages of Divide and Conquer
Divide and Conquer tend to successfully solve one of the biggest problems, such as
the Tower of Hanoi, a mathematical puzzle. It is challenging to solve complicated
problems for which you have no basic idea, but with the help of the divide and
conquer approach, it has lessened the effort as it works on dividing the main problem
into two halves and then solve them recursively. This algorithm is much faster than
other algorithms.
It efficiently uses cache memory without occupying much space because it solves
simple sub problems within the cache memory instead of accessing the slower main
memory.
58
It is more proficient than that of its counterpart Brute Force technique.
Since these algorithms inhibit parallelism, it does not involve any modification and is
handled by systems incorporating parallel processing.
Disadvantages of Divide and Conquer
Since most of its algorithms are designed by incorporating recursion, so it necessitates
high memory management.
An explicit stack may overuse the space.
It may even crash the system if the recursion is performed rigorously greater than the
stack present in the CPU.
59
two sequences of integers approaching each other and eventually low will become greater
than high causing termination in a finite number of steps if ‘x’ is not present.
Similarly we have to follow the procedure and find the elements. The number of
element comparisons needed to find each of nine elements is:
Index: 1 2 3 4 5 6 7 8 9
Elements: -15 -6 0 7 9 23 54 82 101
Comparisons: 3 2 3 4 1 3 2 3 4
The time complexity for a successful search is O(log n) and for an unsuccessful
search is Θ(log n).
60
The chosen value is known as the pivot element. Once the array has been rearranged
in this way with respect to the pivot, the very same partitioning is recursively applied to each
of the two subsets. When all the subsets have been partitioned and rearranged, the original
array is sorted.
The function partition( ) makes use of two pointers ‘i’ and ‘j’ which are moved
toward each other in the following fashion:
• Repeatedly increase the pointer ‘i’ until a[i] >= pivot.
• Repeatedly decrease the pointer ‘j’ until a[j] <= pivot.
• If j > i, interchange a[j] with a[i]
• Repeat the steps 1, 2 and 3 till the ‘i’ pointer crosses the ‘j’ pointer. If ‘i’ pointer
crosses ‘j’ pointer, the position for pivot is found and place pivot element in ‘j’
pointer position.
The algorithm terminates when the condition low >= high is satisfied. This condition
will be satisfied only when the array is completely sorted. Choose the first element as the
‘pivot’. So, pivot = x[low]
Algorithm PARTITION(a, m, p)
{
V := a(m); i := m; j :=p; // A (m) is the partition element
do
61
{
loop i := i + 1 until a(i) > v // i moves left to right
loop j := j – 1 until a(j) < v // p moves right to left
if (i < j) then INTERCHANGE(a, i, j)
} while (i > j);
a[m] :=a[j];
a[j] :=V; // the partition element belongs at position P
return j;
}
Algorithm INTERCHANGE(a, i, j)
{
P:=a[i];
a[i] := a[j];
a[j] := p;
}
44 33 11 55 77 90 40 60 99 22 88
Let 44 be the Pivot element and scanning done from right to left
Comparing 44 to the right-side elements, and if right-side elements are smaller than 44, then
swap it. 22 is smaller than 44 so swap them.
22 33 11 55 77 90 40 60 99 44 88
Now comparing 44 to the left side element and the element must be greater than 44 then
swap them. 55 are greater than 44 so swap them.
22 33 11 44 77 90 40 60 99 55 88
Recursively, repeating steps 1 & steps 2 until we get two lists one left from pivot element 44
& one right from pivot element.
22 33 11 40 77 90 44 60 99 55 88
Now, the element on the right side and left side are greater than and smaller than 44
respectively.
62
Now we get two sorted lists:
Figure 3.7
And these sublists are sorted under the same process as above done.
Figure 3.8
Merging Sublists:
Strassen’s is simple Divide and Conquer method to multiply two square matrices.
63
1. Divide matrices A and B in 4 sub-matrices of size N/2 x N/2 as shown in the below
diagram.
2. Calculate following values recursively. ae + bg, af + bh, ce + dg and cf + dh.
Figure 3.10
Strassen’s insight was to find an alternative method for calculating the Cij, requiring
seven (n/2) x (n/2) matrix multiplications and eighteen (n/2) x (n/2) matrix additions and
subtractions:
P = (A11 + A22) (B11 + B22)
Q = (A21 + A22) B11
R = A11 (B12 – B22)
S = A22 (B21 - B11)
T = (A11 + A12) B22
U = (A21 – A11) (B11 + B12)
V = (A12 – A22) (B21 + B22)
C11 = P + S – T + V
C12 = R + T
C21 = Q + S
C22 = P + R - Q + U.
Time complexity of above method is O(n^2.81).
Problem: Multiply given two matrices A and B using Strassen’s approach, where
64
Figure 3.11
Now, let's use Strassen's algorithm to find the product AB. The algorithm involves
dividing the matrices into submatrices and performing seven multiplications recursively. The
following steps outline the process:
Divide matrices A and B into four equal-sized submatrices:
Figure 3.12
Where A11,A12,A21,A22,B11,B12,B21,B22A11,A12,A21,A22,B11,B12,B21,B22
are submatrices.
Figure 3.13
65
Figure 3.14
Now, let's substitute the values and calculate the product AB:
Figure 3.15
Figure 3.16
Step 1 − if it is only one element in the list, consider it already sorted, so return.
Step 2 − divide the list recursively into two halves until it can no more be divided.
Step 3 − merge the smaller lists into new list in sorted order.
66
// a (low : high) is a global array to be sorted.
{
if (low < high)
{
mid := |(low + high)/2| //finds where to split the set
MERGESORT(low, mid) //sort one subset
MERGESORT(mid+1, high) //sort the other subset
MERGE(low, mid, high) // combine the results
}
}
67
}
else
for k := h to mid do
{
b[i] := a[K]; i := i + l;
}
for k := low to high do
a[k] := b[k];
}
Example:
In the following example, we have shown Merge-Sort algorithm step by step. First,
every iteration array is divided into two sub-arrays, until the sub-array contains only one
element. When these sub-arrays cannot be divided further, then merge operations are
performed.
Figure 3.17
SAMPLE QUESTIONS:
68
5. Given a set of points in a plane, implement an algorithm to find the closest pair using
the divide and conquer approach.
6. Discuss the Convex Hull problem with the concept of brute force approach
7. Apply the divide and conquer approach to find the maximum subarray sum in a given
array. Provide the algorithm and analyze its time complexity.
8. Write an iterative binary search algorithm to find the index of a specific element in a
sorted array. Discuss the time complexity of your solution.
9. Implement the Quick Sort algorithm and demonstrate its application on an array of
integers. Discuss the partitioning process at each step.
10. Apply Strassen's algorithm to multiply two 2x2 matrices. Show the step-by-step
process and analyze the time complexity.
11. Implement the Merge Sort algorithm and compare its time complexity with other
sorting algorithms. Discuss its advantages and disadvantages.
69
UNIT IV: Greedy Approach and Dynamic Programming
70
Algorithm Greedy (a, n)
{
Solution : = 0;
for i = 0 to n do
{
x: = select(a);
if feasible(solution, x)
{
Solution: = union(solution , x)
}
return solution;
}
}
Initially, the solution is assigned with zero value. We pass the array and number of
elements in the greedy algorithm. Inside the for loop, we select the element one by one and
checks whether the solution is feasible or not. If the solution is feasible, then we perform the
union.
4.2 Optimal Merge Patterns
Given n sorted files, find an optimal way (i.e., requiring the fewest comparisons or
record moves) to pair wise merge them into one sorted file. It fits ordering paradigm.
Example:
Three sorted files (x1, x2, x3) with lengths (30, 20, 10)
Solution 1: merging x1 and x2 (50 record moves), merging the result with x3 (60
moves)
Totally 110 moves
Solution 2: merging x2 and x3 (30 moves), merging the result with x1 (60 moves)
Totally 90 moves
The solution 2 is better.
“Merge n sorted sequences of different lengths into one sequence while minimizing
reads”. Any two sequences can be merged at a time. At each step, the two shortest sequences
are merged.
Consider three sorted lists L1, L2 and L3 of size 30, 20 and 10 respectively. Two way
merge compares elements of two sorted lists and put them in new sorted list.
If we first merge list L1 and L2, it does 30 + 20 = 50 comparisons and creates a new
array L’ of size 50. L’ and L3 can be merged with 60 + 10 = 70 comparisons that forms a
sorted list L of size 60. Thus total number of comparisons required to merge lists L1, L2 and
L3 would be 50 + 70 = 120.
71
Alternatively, first merging L2 and L3 does 20 + 10 = 30 comparisons, which creates
sorted list L’ of size 30. Now merge L1 and L’, which does 30 + 30 = 60 comparisons to form
a final sorted list L of size 60. Total comparisons required to create L is thus 30 + 60 = 90.
In both the cases, final output is identical but first approach does 120 comparisons
whereas second does only 90. Goal of optimal merge pattern is to find the merging sequence
which results into minimum number of comparisons.
Let S = {s1, s2, …, sn} be the set of sequences to be merged. Greedy approach selects
minimum length sequences si and sj from S. The new set S’ is defined as, S’ = (S – {si, sj}) ∪
{si + sj}. This procedure is repeated until only one sequence is left.
Complexity Analysis
The total running time of this algorithm is O(nlogn).
(i) Fixed length Code: Each letter represented by an equal number of bits. With a fixed
length code, at least 3 bits per character:
72
For example:
a 000
b 001
c 010
d 011
e 100
f 101
(ii) A variable-length code: It can do considerably better than a fixed-length code, by giving
many characters short code words and infrequent character long code words.
For example:
a 0
b 101
c 100
d 111
e 1101
f 1100
73
Let us understand prefix codes with a counter example. Let there be four characters a,
b, c and d, and their corresponding variable length codes be 00, 01, 0 and 1. This coding leads
to ambiguity because code assigned to c is the prefix of codes assigned to a and b. If the
compressed bit stream is 0001, the de-compressed output may be “cccd” or “ccb” or “acd” or
“ab”.
There are mainly two major parts in Huffman Coding
74
4. Repeat steps#2 and #3 until the heap contains only one node. The remaining node is
the root node and the tree is complete.
4.4 Job Sequencing with Deadline
Job scheduling algorithm is applied to schedule the jobs on a single processor to
maximize the profits.
The greedy approach of the job scheduling algorithm states that, “Given ‘n’ number
of jobs with a starting time and ending time, they need to be scheduled in such a way that
maximum profit is received within the maximum deadline”.
Table 4.3.
S. No. 1 2 3 4 5
Jobs J4 J5 J2 J3 J1
75
Deadlines 3 4 2 1 2
Profits 100 80 60 40 20
The maximum deadline, dm, is 4. Therefore, all the tasks must end before 4.
Choose the job with highest profit, J4. It takes up 3 parts of the maximum deadline.
Step 3
The next job with highest profit is J5. But the time taken by J5 is 4, which exceeds the
deadline by 3. Therefore, it cannot be added to the output set.
Step 4
The next job with highest profit is J2. The time taken by J5 is 2, which also exceeds the
deadline by 1. Therefore, it cannot be added to the output set.
Step 5
The next job with higher profit is J3. The time taken by J3 is 1, which does not exceed the
given deadline. Therefore, J3 is added to the output set.
Total Profit: 100 + 40 = 140
Step 6
Since, the maximum deadline is met, the algorithm comes to an end. The output set of jobs
scheduled within the deadline are {J4, J3} with the maximum profit of 140.
Example
Table 4.4.
Sr.No. Feasible Processing Profit value
Solution Sequence
(i) (1, 2) (2, 1) 110
(ii) (1, 3) (1, 3) or (3, 1) 115
(iii) (1, 4) (4, 1) 127 is the optimal one
(iv) (2, 3) (2, 3) 25
(v) (3, 4) (4, 3) 42
(vi) (1) (1) 100
(vii) (2) (2) 10
(viii) (3) (3) 15
(ix) (4) (4) 27
76
4.5 Tree Vertex Splitting
Directed and weighted binary tree.
Consider a network of power line transmission.
The transmission of power from one node to the other results in some loss, such as
drop in voltage.
Each edge is labeled with the loss that occurs (edge weight).
Network may not be able to tolerate losses beyond a certain level.
Place boosters in the nodes to account for the losses.
Definition
Given a network and a loss tolerance level, the tree vertex splitting problem is to
determine the optimal placement of boosters. Place boosters only in the vertices and nowhere
else.
More definitions
Let T = (V, E, w) be a weighted directed tree
V is the set of vertices
E is the set of edges
w is the weight function for the edges
wij is the weight of the edge〈i, j〉∈ E
We say that wij = ∞ if 〈i, j〉∉E
A vertex with in-degree zero is called a source vertex
A vertex with out-degree zero is called a sink vertex
For any path P ∈ T , its delay d(P ) is defined to be the sum of the weights (wij ) of
that path, or
d(P ) = ∑ wij
〈i,j〉∈P
77
Split node is the booster station
Tree vertex splitting problem is to identify a set X ⊆ V of minimum cardinality
(minimum number of booster stations) for which d(T /X) ≤ δ for some specified
tolerance limit δ
o TVSP has a solution only if the maximum edge weight is ≤ δ
Given a weighted tree T = (V, E, w) and a tolerance limit δ, any X ⊆ V is a feasible
solution if d(T/X) ≤ δ
Given an X, we can compute d(T /X) in O(|V |) time
A trivial way of solving TVSP is to compute d(T /X) for every X ⊆ V, leading
to a possible 2|V | computations
Greedy solution for TVSP
– We want to minimize the number of booster stations (X)
– For each node u ∈ V, compute the maximum delay d(u) from u to any other node in
its subtree
– If u has a parent v such that d(u) + w(v, u) > δ, split u and set d(u) to zero
– Computation proceeds from leaves to root
– Delay for each leaf node is zero
– The delay for each node v is computed from the delay for the set of its children C(v)
d(v) = max {d(u) + w(v, u)}
u∈C(v)
If d(v) > δ, split v
– The above algorithm computes the delay by visiting each node using post-order
traversal.
Example:
Figure 4.1
If d (u)>= δ than place the booster.
d (7)= max{0+w(4,7)}=1
d (8)=max{0+w(4,8)}=4
d (9)= max{0+ w(6,9)}=2
d (10)= max{0+w(6,10)}=3 d(5)=max{0+e(3.3)}=1
d (4)= max{1+w(2,4), 4+w(2,4)}=max{1+2,4+3}=6> δ ->booster
d (6)=max{2+w(3,6),3+w(3,6)}=max{2+3,3+3}=6> δ->booster
d (2)=max{6+w(1,2)}=max{6+4)=10> δ->booster
79
d (3)=max{1+w(1,3), 6+w(1,3)}=max{3,8}=8> δ ->booster
Note: No need to find tolerance value for node 1 because from source only power is
transmitting.
The strategy can be used when the process of obtaining a solution of a problem can be
viewed as a sequence of decisions. The problems of this type can be solved by taking an
optimal sequence of decisions. An optimal sequence of decisions is found by taking one
decision at a time and never making an erroneous decision. In Dynamic Programming, an
optimal sequence of decisions is arrived at by using the principle of optimality. The principle
of optimality states that whatever be the initial state and decision, the remaining decisions
must constitute an optimal decision sequence with regard to the state resulting form the first
decision.
80
A fundamental difference between the greedy strategy and dynamic programming is
that in the greedy strategy only one decision sequence is generated, wherever in the dynamic
programming, a number of them may be generated. Dynamic programming technique
guarantees the optimal solution for a problem whereas greedy method never gives such
guarantee.
81
Figure 4.2
To evaluate Sum(6, 3, 8), we need to evaluate Sum(6, 2, 7) which can recursively written as
following:
Sum(6, 2, 7) = Sum(6, 1, 6) + Sum(6, 1, 5) + Sum(6, 1, 4) + Sum(6, 1, 3) +
Sum(6,1, 2) + Sum(6, 1, 1)
82
efficiently. The fundamental idea behind dynamic programming is to break down a complex
problem into smaller, overlapping subproblems and solve each subproblem only once, storing
its solution for future reference. This "memoization" technique eliminates redundant
calculations and significantly speeds up the overall process.
4.8.1 Optimal Substructure
A key concept in the dynamic programming approach is "optimal substructure." It
means that the optimal solution to a larger problem can be obtained by combining the optimal
solutions of its smaller subproblems. For OBSTs, the optimal substructure property is crucial
because it allows us to find the best arrangement of nodes in a subtree and then reuse this
solution when constructing the overall tree.
An Optimal Binary Search Tree (OBST), also known as a Weighted Binary Search
Tree, is a binary search tree that minimizes the expected search cost. In a binary search tree,
the search cost is the number of comparisons required to search for a given key.
In an OBST, each node is assigned a weight that represents the probability of the key
being searched for. The sum of all the weights in the tree is 1.0. The expected search cost of a
node is the sum of the product of its depth and weight, and the expected search cost of its
children.
To construct an OBST, we start with a sorted list of keys and their probabilities. We
then build a table that contains the expected search cost for all possible sub-trees of the
original list. We can use dynamic programming to fill in this table efficiently. Finally, we use
this table to construct the OBST.
The time complexity of constructing an OBST is O(n^3), where n is the number of
keys. However, with some optimizations, we can reduce the time complexity to O(n^2). Once
the OBST is constructed, the time complexity of searching for a key is O(log n), the same as
for a regular binary search tree.
The OBST is a useful data structure in applications where the keys have different
probabilities of being searched for. It can be used to improve the efficiency of searching and
retrieval operations in databases, compilers, and other computer programs.
Given a sorted array key [0.. n-1] of search keys and an array freq[0.. n-1] of
frequency counts, where freq[i] is the number of searches for keys[i]. Construct a binary
search tree of all keys such that the total cost of all the searches is as small as possible.
Let us first define the cost of a BST. The cost of a BST node is the level of that node
multiplied by its frequency. The level of the root is 1.
83
Example:
10 12
\ /
12 10
I II
10 12 20 10 20
\ / \ / \ /
12 10 20 12 20 10
\ / / \
20 10 12 12
I II III IV V
This can be divided into 2 parts one is the freq[r]+sum of frequencies of all elements from i
to j except r. The term freq[r] is added because it is going to be root and that means level of 1,
so freq[r]*1=freq[r]. Now the actual part comes, we are adding the frequencies of remaining
84
elements because as we take r as root then all the elements other than that are going 1 level
down than that is calculated in the subproblem. Let me put it in a more clear way, for
calculating optcost(i,j) we assume that the r is taken as root and calculate min of opt(i,r-
1)+opt(r+1,j) for all i<=r<=j. Here for every subproblem we are choosing one node as a root.
But in reality the level of subproblem root and all its descendant nodes will be 1 greater than
the level of the parent problem root. Therefore the frequency of all the nodes except r should
be added which accounts to the descend in their level compared to level assumed in
subproblem.
Consider the below table, which contains the keys and frequencies.
Figure 4.3
Figure 4.4
85
When i = 4, j=4, then j-i = 0
Now to calculate the cost, we will consider only the jth value.
The cost of c[0,1] is 4 (The key is 10, and the cost corresponding to key 10 is 4).
The cost of c[1,2] is 2 (The key is 20, and the cost corresponding to key 20 is 2).
The cost of c[2,3] is 6 (The key is 30, and the cost corresponding to key 30 is 6)
The cost of c[3,4] is 3 (The key is 40, and the cost corresponding to key 40 is 3)
Figure 4.5
86
When i=0 and j=2, then keys 10 and 20. There are two possible trees that can be made out
from these two keys shown below:
Figure 4.6
Figure 4.7
When i=1 and j=3, then keys 20 and 30. There are two possible trees that can be made
out from these two keys shown below:
87
In the second binary tree, cost would be: 1*3 + 2*6 = 15
The minimum cost is 12, therefore, c[2,4] = 12
Figure 4.8
When i=0, j=3 then we will consider three keys, i.e., 10, 20, and 30.
The following are the trees that can be made if 10 is considered as a root node.
Figure 4.9
In the above tree, 10 is the root node, 20 is the right child of node 10, and 30 is the right child
of node 20.
88
Figure 4.10
In the above tree, 10 is the root node, 30 is the right child of node 10, and 20 is the left child
of node 20.
Figure 4.11
In the above tree, 20 is the root node, 30 is the right child of node 20, and 10 is the left child
of node 20.
The following are the trees that can be created if 30 is considered as the root node.
Figure 4.12
89
In the above tree, 30 is the root node, 20 is the left child of node 30, and 10 is the left child of
node 20.
Figure 4.13
In the above tree, 30 is the root node, 10 is the left child of node 30 and 20 is the right child
of node 10.
Therefore, the minimum cost is 20 which is the 3rd root. So, c[0,3] is equal to 20.
When i=1 and j=4 then we will consider the keys 20, 30, 40
= min{12, 5, 10} + 11
Figure 4.14
Now we will calculate the values when j-i = 4
90
In this case, we will consider four keys, i.e., 10, 20, 30 and 40. The frequencies of 10,
20, 30 and 40 are 4, 2, 6 and 3 respectively.
w[0, 4] = 4 + 2 + 6 + 3 = 15
= min{4 + 12} + 15
= 16 + 15 = 31
= min {8 + 3} + 15
= 26
= min{20 + 0} + 15
= 35
In the above cases, we have observed that 26 is the minimum cost; therefore, c[0,4] is equal
to 26
91
Figure 4.15
The optimal binary tree can be created as:
Figure 4.16
Figure 4.17
92
SAMPLE QUESTIONS:
1. Given a set of files with different sizes, implement an algorithm to find the optimal
way to merge them using the greedy approach.
2. Construct a Huffman tree for a set of characters with their respective frequencies.
Encode and decode a sample string using the generated Huffman codes.
3. Solve a job sequencing problem with deadlines and profits using the greedy approach.
Show the order of job execution to maximize profits.
4. Implement an algorithm to split a tree into two subtrees to minimize the maximum
height difference between them.
5. Given a dice with 'm' faces and 'n' throws, implement a dynamic programming
algorithm to find the number of ways to get a specific sum 'x.'
6. Given a set of keys and their probabilities, construct an optimal binary search tree
using dynamic programming. Calculate the expected search cost.
93
UNIT V: Backtracking and Branch and Bound
Solution space: Tuples that satisfy the explicit constraints define a solution space.
Problem state: The solution space can be organized into a tree. Each node in the tree
defines a problem state.
State-space: All paths from the root to other nodes define the state-space of the
problem.
Solution states: Solution states are those states leading to a tuple in the solution
space.
Answer nodes are those solution states leading to an answer-tuple (i.e. tuples which
satisfy implicit constraints).
LIVE NODE: A node which has been generated and all of whose children are not yet
been generated.
E-NODE (Node being expanded): The live node whose children are currently being
generated.
DEAD NODE: A node that is either not to be expanded further, or for which all of its
children have been generated
94
DEPTH FIRST NODE GENERATION: In this, as soon as a new child C of the
current E-node R is generated, C will become the new E-node.
ALGORITHM Back Tracking (v1,...,vi)
{
IF (v1,...,vi) is a solution THEN RETURN (v1,...,vi)
FOR each v DO
IF (v1,...,vi,v) is acceptable vector THEN
sol = try(v1,...,vi,v)
IF sol != ( ) THEN RETURN sol
END
END
RETURN ( )
}
If Si is the domain of vi, then S1 × ... × Sm is the solution space of the problem. The
validity criteria used in checking for acceptable vectors determines what portion of that space
needs to be searched, and so it also determines the resources required by the algorithm.
If two queens are placed at positions (i,j) and (k,l). They are on the same diagonal only if
i-j=k-l ……………….(1) or
i+j=k+l ……………….(2).
From (1) and (2) implies
j-l=i-k and
j-l=k-i
Two queens lie on the same diagonal iff
|j-l|=|i-k|
95
Algorithm
Let's go through the steps below to understand how this algorithm of solving the 8 queens
problem using backtracking works:
Step 1: Traverse all the rows in one column at a time and try to place the queen in
that position.
Step 2: After coming to a new square in the left column, traverse to its left horizontal
direction to see if any queen is already placed in that row or not. If a queen is found,
then move to other rows to search for a possible position for the queen.
Step 3: Like step 2, check the upper and lower left diagonals. We do not check the
right side because it's impossible to find a queen on that side of the board yet.
Step 4: If the process succeeds, i.e. a queen is not found, mark the position as '1' and
move ahead.
Step 5: Recursively use the above-listed steps to reach the last column. Print the
solution matrix if a queen is successfully placed in the last column.
Step 6: Backtrack to find other solutions after printing one possible solution.
Time Complexity: O(N!), For the first column we will have N choices, then for the next
column, we will have N-1 choices, and so on. Therefore the total time taken will be N*(N-
1)*(N-2)...., which makes the time complexity to be O(N!).
Space Complexity: O(N^2), we can see that the initial space needed is N^2, then N^2-1,
then N^2-2 and so on. Thus making the space complexity N^2N, but we don’t need all the
N^2 options each time. Therefore, the overall space complexity is O(N^2).
Figure 5.1
96
Algorithm place(k,i)
{
for j:=1 to k-1 do
if ((x[j]=i) or (abs(x[j]-i) = abs(j-k)) then
return false;
return true;
}
Algorithm nqueens(k,n)
{
for i:=1 to n do
{
if (place(k,i) then
{
X[k]:=i;
if (k=n) then
write(x[i:n);
else
nqueens(k+1,n);
}
}
}
Figure 5.2
These are two possible solutions from the entire solution set for the 8 queen problem.
97
5.3 Hamiltonian Circuit or Cycle
The term Hamiltonian comes from William Hamiltonian, who invented (a not very
successful) board game he termed the "icosian game", which was about finding Hamiltonian
cycles on the dodecahedron graph (and possibly its subgraphs).
Hamiltonian Cycle or Circuit in a graph G is a cycle that visits every vertex of G exactly
once and returns to the starting vertex.
If graph contains a Hamiltonian cycle, it is called Hamiltonian graph otherwise it is
non-Hamiltonian.
Finding a Hamiltonian Cycle in a graph is a well-known NP-complete problem, which
means that there’s no known efficient algorithm to solve it for all types of graphs.
However, it can be solved for small or specific types of graphs.
The Hamiltonian Cycle problem has practical applications in various fields, such as
logistics, network design, and computer science.
5.3.1 Hamiltonian Path
Hamiltonian Path in a graph G is a path that visits every vertex of G exactly once
and Hamiltonian Path doesn’t have to return to the starting vertex. It’s an open path.
Similar to the Hamiltonian Cycle problem, finding a Hamiltonian Path in a general
graph is also NP-complete and can be challenging. However, it is often an easier
problem than finding a Hamiltonian Cycle.
Hamiltonian Paths have applications in various fields, such as finding optimal routes
in transportation networks, circuit design, and graph theory research.
Problems Statement: Given an undirected graph, the task is to determine whether the graph
contains a Hamiltonian Circuit/Cycle or not. If it contains, then prints the path.
Example:
Figure 5.3
98
Generate all possible configurations of vertices and print a configuration that satisfies
the given constraints. There will be n! (n factorial) configurations. So the overall Time
Complexity of this approach will be O(N!).
5.3.2 Hamiltonian Circuit / Cycle using Backtracking Algorithm:
Create an empty path array and add vertex 0 to it. Add other vertices, starting from the
vertex 1. Before adding a vertex, check for whether it is adjacent to the previously added
vertex and not already added. If we find such a vertex, we add the vertex as part of the
solution. If we do not find a vertex then we return false.
Let’s find out the Hamiltonian cycle for the following graph:
Figure 5.4
When base case reach (i.e. total no of node traversed == V (total vertex)):
o As node 2 and node 0 are not neighbours of each other so return from it.
Figure 5.5
99
Starting from start node 0 calling DFS
As cycle is not found in path {0, 3, 1, 4, 2}. So, return from node 2, node 4.
Figure 5.6
Figure 5.7
Figure 5.8
100
Now, explore other options for node 3.
Figure 5.9
In the Hamiltonian path {0,3,4,2,1,0} we get cycle as node 1 is the neighbour of node
0.
So print this cyclic path.
This is our Hamiltonian Circuit / Cycle.
101
LIFO branch and bound search
LC (Least Cost) branch and bound search / LCBB search
Now we delete an element from queue i.e. node 2, next and node 2 becomes E-node and
generates its children and places those live nodes in queue.
Next, delete an element from queue and considering it as E-node. i.e. E-node =3 and its
children are generated. 7, 8 are children of 3 and these live nodes are killed by bounding
functions. So we will not include in queue.
Again delete an element from Q. considering it as E-node, generate the children of node 4.
Node 9 is generated and killed by bounding function.
102
Again delete an element from Q. generate children of node 5, i.e. nodes 10 and 11 are
generated and killed by bounding function, last node in the queue is 6.
The child of node 6 is 12 and it satisfies the conditions of the problem which is the answer
node, so search terminates.
103
node 12 since its cost is 1 i.e. minimum. More over node 12 is the answer node. So we
terminate search process.
Figure 5.13
Lb= the sum of the smallest elements in each of the matrix’s rows.
For the instance here, this sum is 2 + 3 + 1 + 4 = 10.
The lower-bound value for the root, denoted lb, is 10. The nodes on the first level of the tree
correspond to selections of an element in the first row of the matrix, i.e., a job for person a.
104
Figure 5.14
The most promising of them is node 2 because it has the smallest lower bound value.
We branch out from that node first by considering the three different ways of selecting an
element from the second row and not in the second column the three different jobs that can be
assigned to person b.
Figure 5.15
105
Figure 5.16
5.6 Traveling Salesman Problem:
TSP includes as a salesperson who has to visit a number of cities during a tour and the
condition is to visit all the cities exactly once and return back to the same city where the
person started.
Basic steps:
Let G=(V,E) be a direct graph defining an instance of the TSP.
1. This graph is first represented by a cost matrix where
cij = the cost of edge , if there is a path between vertex i and vertex j
cij = ∞ , if there is no path
2. Convert cost matrix into reduced matrix i.e.,every row and column should contain at least
one zero entry.
3. Cost of the reduced matrix is the sum of elements that are subtracted from rows and
columns of cost matrix to make it reduced.
4. Make the state space tree for reduced matrix.
5. To find the next E-node, find the least cost valued node by calculating the reduced cost
matrix with every node.
6. If (i, j) edge is to be included, then there are three conditions to accomplish this task:
1. Change all entries in row i and column j of A to ∞.
2. set A [j, 1] =∞
3. Reduce all rows and columns in resulting matrix except for rows and columns
containing ∞.
106
7. Calculate the cost of the matrix where
cost = L + cost (i, j) + r
where L = cost of original reduced cost matrix and
r= new reduced cost matrix
8. Repeat the above steps for all the nodes until all the nodes are generated and we get a path.
If there are n cities and cost of travelling from one city to another city is given. A sales person
has to start from any one of the city and has to visit all the cities exactly once and has to
return to the starting place with shortest distance or minimum cost.
Figure 5.17 State space Tree for the travelling salesperson problem with n=4 and i0 = i4 = 1
i.e. the graph contains 4 nodes and start and end nodes are node 1.
107
To use least cost branch and bound to search the travelling sales person state space
tree, we must define a cost function c(x) and two other functions ĉ(x) and û(x) such that
ĉ(x)<= c(x)<= û(x)
Cost matrix:
Figure 5.18
Solution
A row is said to be reduced iff it contains at least one 0 and all remaining entries are non-
negative.
A column is said to be reduced iff it contains at least one 0 and all remaining entries are non-
negative.
A matrix is reduced iff every row and column is reduced.
Figure 5.19
108
Figure 5.20
Total amount subtracted = r + c = 21 + 4 = 25
ĉ(1)=25
25 will be the minimum cost to travel from node 1 as source and destination and nodes
2,3,4,5 as intermediate nodes. So node 1 is root node and it is E-node. As there is a possibility
of selecting path in either of nodes 2 or 3 or 4 or 5 it generates node 2, node 3, node 4 and
node 5 as live nodes.
Now find minimum cost of nodes 2,3,4 and 5.i.e. ĉ (2), ĉ (3), ĉ (4) and ĉ (5)
Consider the path (1,2) node 2: The reduced cost matrix may be obtained as follows.
1) Change all entries in row i and column j of A to ∞ . This prevents any more edges leaving
vertex i or entering vertex j.
2) Set A(j,1) to ∞ . This prevents the use of edge <j,1>.
3) Reduce all rows and columns in the resulting matrix except for rows and columns
containing only ∞.
Change all entries of first row and second column of reduced matrix to ∞.
Assign A[2,1]= ∞ .
Reduce row and reduce column
109
We get the following reduced cost matrix:
Figure 5.22
The reduced cost matrix after applying row reduction and column reduction i.e. ⇒r =0
ĉ(2)= ĉ(1) + A(1,2) + r = 25 + 10 + 0 = 35
Consider the path (1,3) node 3: Change all entries of first row and third column of reduced
matrix to ∞ and set A(3,1) to ∞ .
Figure 5.23
Applying row reduction and column reduction. ⇒ r = 11 +0=11
ĉ(3)= ĉ(1) + A(1,3) + r = 25 + 17 + 11 = 53
Consider the path (1,4) node 4 : Changing all entries of first row and fourth column to ∞
and set A(4,1) to ∞ .
Figure 5.24
Applying row reduction and column reduction, ⇒r =0
ĉ(4)= ĉ(1) + A(1,4) + r = 25 + 0 + 0 = 25
Consider the path (1,5) node 5: Changing all entries of first row and fifth column to ∞ and
set A(5,1) to ∞ . Reduce row and columns.
110
Figure 5.25
Applying row reduction and column reduction, ⇒ r=5
ĉ(5)= ĉ(1) + A(1,5) + r = 25 + 1 + 5 = 31
Figure 5.26
ĉ(2)= 35, ĉ(3) = 53, ĉ(4) = 25 and ĉ(5) = 31
Since the minimum cost is 25, select node 4.
The matrix obtained for path (1,4) is considered as reduced cost matrix.
Figure 5.27
Now node 4 becomes E-node. Its children 6,7, and 8 are generated. The cost values are
calculated for nodes 6,7 and 8.
111
Figure 5.28 Part of a state space tree
Consider the path(1,4,2) node 6: Change all entries of fourth row and second column of
reduced matrix A to ∞ and set A(2,1) to ∞.
Figure 5.29
Apply row reduction and column reduction ⇒r =0
Consider the path(1,4,3) node 7: Change all entries of fourth row and third column of
reduced matrix A to ∞ and set A(3,1) to ∞
Figure 5.30
Apply row reduction and column reduction ⇒r=2+11=13
112
Consider the path(1,4,5) node 8: Change all entries of fourth row and fifth column of
reduced matrix A to ∞ and set A(5,1) to ∞
Figure 5.31
Apply row reduction and column reduction ⇒r=11+0=11
Consider the path(1,4,2,3) node 9: Change all entries of second row and third column to ∞
and set A(3,1) to ∞
Figure 5.33
Apply row reduction and column reduction ⇒r=2+11=13
ĉ(3)= ĉ(2) +A(2,3) + r = 28+11+13=52
113
Consider the path (1,4,2,5) node 10: Change all entries of second row and fifth column to ∞
and set A(5,1) to ∞
Figure 5.34
Apply row reduction and column reduction ⇒r=0
ĉ(3)= ĉ(5) +A(5,3) + r = 28+0+0=28
Since minimum cost is 28, select node 5.
The matrix obtained for path (2,5) is considered as reduced cost matrix.
Consider the path (1,4,2,5,3) node 11: Change all entries of fifth row and third column to ∞
and set A(3,1) to ∞
Figure 5.35
Apply row reduction and column reduction ⇒ r=0
ĉ(3)= ĉ(5) +A(5,3) + r = 28+0+0=28
114
The path is 1-4-2-5-3-1
Minimum cost=10+6+2+7+3=28
LCBB terminates with 1,4,2,5,3,1 as the shortest length tour.
The c(x) is the cost function for answer node x, which lies between two functions
called lower and upper bounds for the cost function c(x). The search begins at the root node.
Initially we compute the lower and upper bound at root node called ĉ(1) and û(1). Consider
the first variable x1 to take a decision. The x1 takes values either 0 or 1. Compute the lower
and upper bounds in each case of variable. These are the nodes at the first level. Select the
node whose cost is minimum i.e.
c(x)=min{c(lchild(x), c(rchild(x))}
c(x)=min{ ĉ(2),ĉ(3)}
115
The problem can be solved by making a sequence of decisions on the variables
x1,x2,…,xn level wise. A decision on the variable xi involves determining which of the values
0 or 1 is to be assigned, to it by defining c(x) recursively.
The path from root to the leaf node whose height is maximum is selected and is the
solution space for the knapsack problem.
5.7.1 Problem1) LCBB.
Consider the knapsack instance n=4, (p1,p2,p3,p4)=(10,10,12,18),
(W1,W2,W3,W4)=(2,4,6,9), m=15. Let us trace the working of an LCBB search.
Solution: Let us use the fixed tuple formulation.
The search begins with root node as E-node.
cp - current profit
cw- current weight
k – k number of decisions
m - Capacity of knapsack
Algorithm Ubound(cp,cw,k,m)
{
b := cp;
c := cw;
for i:= k+1 to n do
{
if ( c + w[i] <= m ) then
{
c := c + w[i];
b := b - p[i];
}
}
return b;
}
Function U(.) for Knapsack problem
116
Figure 5.37 LC Branch and Bound tree
Figure 5.38 shows the Upper bound function call for each node
Node 1 Ubound(0,0,0,15)
i=1 i=2 i=3 i=4
c=2 c=6 c=12
b=-10 b=-20 b=-32
U(1)=-32
ĉ(1)= -32+3/9 x 18= -38
To calculate lower bound we allow fractions
117
U(1)=-32
ĉ(2)= -32+3/9 x 18= -38
To calculate lower bound we allow fractions
Node 3 Ubound(0,0,0,15)
i=2 i=3 i=4
c=4 c=10
b=-10 b=-22
U(3)=-22
ĉ(3)= -22+5/9 x 18= -32
To calculate lower bound we allow fractions
Node 4 Ubound(-20,6,2,15)
i=3 i=4
c=12
b=-32
U(4)=-32
ĉ(4)= -32+3/9 x 18= -38
To calculate lower bound we allow fractions
Node 5 Ubound(-10,2,2,15)
i=3 i=4
c=8
b=-22
U(5)=-22
ĉ(5)= -22+7/9 x 18= -36
To calculate lower bound we allow fractions
Node 6 Ubound(-32,12,3,15)
i=4
U(6)=-32
ĉ(6)= -32+3/9 x 18= -38
To calculate lower bound we allow fractions
Node 7 Ubound(-20,6,3,15)
i=4
c=15
b=-38
U(7)=-38
ĉ(7)= -38
Node 8 Ubound(-38,15,4,15)
U(8)=-38
ĉ(8)= -38
Node 8 Ubound(-20,6,4,15)
U(1)=-20
118
ĉ(1)= -20
p1x1+p2x2+p3x3+p4x4
10x1+10=1+12x0+18x1
=38
We need to consider all n items.
The tuple size is n
5.7.2 0/1 Knapsack Problem
Consider the instance: M = 15, n = 4, (P1, P2, P3, P4) = (10, 10, 12, 18) and (w1, w2, w3,
w4) = ( 2, 4, 6, 9).
0/1 knapsack problem can be solved by using branch and bound technique. In this
problem we will calculate lower bound and upper bound for each node. Place first item in
knapsack. Remaining weight of knapsack is 15 – 2 = 13. Place next item w2 in knapsack and
the remaining weight of knapsack is 13 – 4 = 9. Place next item w3 in knapsack then the
remaining weight of knapsack is 9 – 6 = 3. No fractions are allowed in calculation of upper
bound so w4 cannot be placed in knapsack.
Profit = P1 + P2 + P3 = 10 + 10 + 12
So, Upper bound = 32
To calculate lower bound we can place w4 in knapsack since fractions are allowed in
calculation of lower bound.
Lower bound = 10 + 10 + 12 + (3/9 X 18) = 32 + 6 = 38
Knapsack problem is maximization problem but branch and bound technique is
applicable for only minimization problems. In order to convert maximization problem into
minimization problem we have to take negative sign for upper bound and lower bound.
Therefore, Upper bound (U) = -32
Lower bound (L) = -38
119
We choose the path, which has minimum difference of upper bound and lower bound.
If the difference is equal then we choose the path by comparing upper bounds and we discard
node with maximum upper bound.
Figure 5.39
Now we will calculate upper bound and lower bound for nodes 2, 3.
For node 2, x1= 1, means we should place first item in the knapsack.
U = 10 + 10 + 12 = 32, make it as -32
L = 10 + 10 + 12 + (3/9) x 18 = 32 + 6 = 38, make it as -3
For node 3, x1 = 0, means we should not place first item in the knapsack.
U = 10 + 12 = 22, make it as -22
L = 10 + 12 +(5/9) x 18 = 10 + 12 + 10 = 32, make it as -32
Next, we will calculate difference of upper bound and lower bound for nodes 2, 3
For node 2, U – L = -32 + 38 = 6
For node 3, U – L = -22 + 32 = 10
Figure 5.40
Now we will calculate lower bound and upper bound of node 4 and 5. Calculate difference of
lower and upper bound of nodes 4 and 5
For node 4, U – L = -32 + 38 = 6
For node 5, U – L = -22 + 36 = 14
120
Figure 5.41
Now we will calculate lower bound and upper bound of node 8 and 9. Calculate difference of
lower and upper bound of nodes 8 and 9
Figure 5.42
Now we will calculate lower bound and upper bound of node 4 and 5. Calculate difference of
lower and upper bound of nodes 4 and 5.
121
Consider the path from 1 -> 2 -> 4 -> 7 -> 8
X1 = 1
X2 = 1
X3 = 0
X4 = 1
The solution for 0/1 Knapsack problem is (x1, x2, x3, x4) = (1, 1, 0, 1)
Maximum profit is:
ƩPi xi = 10 x 1 + 10 x 1 + 12 x 0 + 18 x 1
= 10 + 10 + 18 = 38.
A problem is NP-Hard if it obeys Property 2 above and need not obey Property 1.
Therefore, a problem is NP-complete if it is both NP and NP-hard.
NP-complete problem, any of a class of computational problems for which no
efficient solution algorithm has been found. Many significant computer-science problems
belong to this class—e.g., the traveling salesman problem, satisfiability problems, and graph-
covering problems.
So-called easy, or tractable, problems can be solved by computer algorithms that run
in polynomial time; i.e., for a problem of size n, the time or number of steps needed to find
the solution is a polynomial function of n. Algorithms for solving hard, or intractable,
problems, on the other hand, require times that are exponential functions of the problem size
n. Polynomial-time algorithms are considered to be efficient, while exponential-time
122
algorithms are considered inefficient, because the execution times of the latter grow much
more rapidly as the problem size increases.
5.9 Clique problem:
Let G be a non-directed graph consisting of a set of vertices V and set of edges E. A
maximal complete sub graph of a graph G is a clique. The size of the clique is the number of
vertices in it. The max clique problem is an optimization problem that has to determine
whether graph has a clique of size atleast k for some integer k.
A clique is a subset of vertices of an undirected graph G such that every two distinct
vertices in the clique are adjacent; that is, its induced subgraph is complete. Cliques are one
of the basic concepts of graph theory and are used in many other mathematical problems and
constructions on graphs.
The task of finding whether there is a clique of a given size in a graph (the clique
problem) is NP complete, but despite this hardness result, many algorithms for finding
cliques have been studied.
A maximal clique is a clique that cannot be extended by including one more adjacent
vertex, that is, a clique which does not exist exclusively within the vertex set of a larger
clique.
A maximum clique of a graph, G, is a clique, such that there is no clique with more
vertices. Moreover, the clique number ω(G) of a graph G is the number of vertices in a
maximum clique in G.
Figure 5.43
The clique problem is the computational problem of finding cliques in a graph. It has
several different formulations depending on which cliques, and what information about the
cliques,shouldbefound.
Common formulations of the clique problem include finding a maximum clique,
123
finding a maximum weight clique in a weighted graph, listing all maximal cliques, and
solving the decision problem of testing whether a graph contains a clique larger than a given
size.
5.9.1Finding a single maximal clique
A single maximal clique can be found by a straightforward greedy algorithm.
Starting with an arbitrary clique (for instance, any single vertex or even the empty set), grow
the current clique one vertex at a time by looping through the graph's remaining vertices. For
each vertex v that this loop examines, add v to the clique if it is adjacent to every vertex that
is already in the clique, and discard v otherwise. This algorithm runs in linear time. Because
of the ease of finding maximal cliques, and their potential small size, more attention has been
given to the much harder algorithmic problem of finding a maximum or otherwise large
clique than has been given to the problem of finding a single maximal clique.
5.9.1.1 Cliques of fixed size
One can test whether a graph G contains a k-vertex clique, and find any such clique that it
contains, using a brute force algorithm. This algorithm examines each subgraph with k
vertices and checks to see whether it forms a clique. It takes time O(nkk2), as expressed using
big O notation. This is because there are O(nk) subgraphs to check, each of which has O(k2)
edges whose presence in G needs to be checked. Thus, the problem may be solved in
polynomial time whenever k is a fixed constant. However, when k does not have a fixed
value, but instead may vary as part of the input to the problem, the time is exponential.
5.10 Vertex Cover Problem:
124
Figure 5.44
A vertex cover of an undirected graph is a subset of its vertices such that for every
edge (u, v) of the graph, either ‘u’ or ‘v’ is in the vertex cover. Although the name is Vertex
Cover, the set covers all edges of the given graph. Given an undirected graph, the vertex
cover problem is to find minimum size vertex cover.
Vertex Cover Problem is a known NP Complete problem, i.e., there is no polynomial-
time solution for this unless P = NP. There are approximate polynomial-time algorithms to
solve the problem.
Figure 5.45
Consider all the subset of vertices one by one and find out whether it covers all edges
of the graph. For eg. in a graph consisting only 3 vertices the set consisting of the
combination of vertices are:{0,1,2,{0,1},{0,2},{1,2},{0,1,2}} .
Using each element of this set check whether these vertices cover all the edges of the
graph. Hence update the optimal answer. And hence print the subset having minimum
number of vertices which also covers all the edges of the graph.
125
3. Do the following while E is not empty.
a) Pick an arbitrary edge (u,v) from set E and add ‘u’ and ‘v’ to result.
b) Remove all edges from E which are either incident on u or v.
4. Return result.
Figure 5.46
VC = {b, c, d, e, f, g}
SAMPLE QUESTIONS:
1. Explain the general method of Branch and Bound.
2. Explain the properties of LC-search
3. Explain the method or reduction to solve TSP problem using Branch and Bound
4. Solve the following instance of travelling sales person problem using LCBB.
126
5. Draw the portion of the state space tree generated by LCBB for the knapsack instance
n=5, (p1,p2,p3,p4,p5)=(10,15,6,8,4), (w1,w2,w3,w4,w5)= 4,6,3,4,2) and m=12
Apply branch and bound to the above 0/1 knapsack problem and explain.
6. Explain the principles of FIFO branch and Bound.
7. Draw the portion of the state space tree generated by FIFO branch and bound for the
following instances. N=4, m=15, (p1,p2,p3,p4)=(10,10,12,18),
(w1,w2,w3,w4)=(12,4,6,9).
127