DESIGN AND ANALYSIS OF ALGORITHMS
DESIGN AND ANALYSIS OF ALGORITHMS
PROGRAMME: IT
Lecturer’s Contacts:
Telephone No. - 0707691430
Email address: - [email protected]
Purpose of the Course: To introduce the classic algorithms in various domains, and techniques for designing
efficient algorithms
Expected Learning Outcomes: At the end of this course the learner shall be able to;
i. Demonstrate knowledge of classic algorithms in various domains, and techniques for designing
efficient algorithms
iv. Demonstrate knowledge of the correctness of algorithms using inductive proofs and invariants.
Course Content
Complexity theory; asymptotic analysis of upper and average complexity: least, average and worst analysis
of algorithms; amortized analysis: big ‘O’ and little ‘o’ notation: time and space trade–off in algorithm: NP-
completeness; approximation algorithms; parallel algorithm, cryptography algorithms, distributed
algorithms, use of recurrence relations to analysis recursive algorithm, brute-force algorithm, backtracking,
branching and bound heuristics, pattern matching and string/text algorithms, greedy algorithm, divide and
conquer algorithm, floyd’s algorithm, dijkstra’s and floyd’s algorithm, minimum spanning tree algorithm
selection sorting, insertion sorting, merge sort, radix sort, quick sort, heap sort, merge sort
Practicum / Comments
Sub Topic Hrs. Course Text Readings Assignment(s)
Lab Practicals (by Lecturer)
Practicum / Comments
Sub Topic Hrs. Course Text Readings Assignment(s)
Lab Practicals (by Lecturer)
Bruit force
algorithm
Practicum / Comments
Sub Topic Hrs. Course Text Readings Assignment(s)
Lab Practicals (by Lecturer)
Practicum / Comments
Sub Topic Hrs. Course Text Readings Assignment(s)
Lab Practicals (by Lecturer)
Practicum / Comments
Sub Topic Hrs. Course Text Readings Assignment(s)
Lab Practicals (by Lecturer)
Practicum / Comments
Sub Topic Hrs. Course Text Readings Assignment(s)
Lab Practicals (by Lecturer)
Practicum /
Course Comments
Sub Topic Hrs. Readings Lab Assignment(s)
Text (by Lecturer)
Practicals
Practicum / Comments
Sub Topic Hrs. Course Text Readings Assignment(s)
Lab Practicals (by Lecturer)
Practicum / Comments
Sub Topic Hrs. Course Text Readings Assignment(s)
Lab Practicals (by Lecturer)
An algorithm can be defined as a well-defined computational procedure that takes some values,
or the set of values, as an input and produces some value, or the set of values, as an output. An
algorithm is thus a sequence of computational steps that transform the input into output.
Worst-case: f (n) defined by the maximum number of steps taken on any instance of size n.
Best-case: f (n) defined by the minimum number of steps taken on any instance of size n.
Average case: f (n) defined by the average number of steps taken on any instance of size n
ALGORITHM VS PROGRAM.
A finite set of instructions that specifies a sequence of operations to be carried out to solve a
specific problem of a class of problem is called an algorithm.
On the other hand, the Program doesn't have to satisfy the finiteness condition. For example,
we can think of an operating system that continues in a "wait" loop until more jobs are
entered. Such a program doesn't terminate unless the system crashes.
a. Correctness: It should produce the output according to the requirement of the algorithm
b. Finiteness: Algorithm must complete after a finite number of instructions have been
executed. An Absence of Ambiguity: Each step must be defined, having only one
interpretation.
c. Definition of Sequence: Each step must have a unique defined preceding and succeeding
step. The first step and the last step must be noted.
d. Input/output: Number and classification of needed inputs and results must be stated.
Feasibility: It must be feasible to execute each instruction.
e. Flexibility: It should also be possible to make changes in the algorithm without putting
so much effort on it.
f. Efficient - Efficiency is always measured in terms of time and space requires
implementing the algorithm, so the algorithm uses a little running time and memory
space as possible within the limits of acceptable development time.
g. Independent: An algorithm should focus on what are inputs, outputs and how to derive
output without knowing the language it is defined. Therefore, we can say that the
algorithm is independent of language.
One of the most important aspects of algorithm design is creating an algorithm that is efficient.
A recursive algorithm calls itself with smaller input values and returns the result for the current
input by carrying out basic operations on the returned value for the smaller input. Generally, if a
problem can be solved by applying solutions to smaller versions of the same problem, and the
smaller versions shrink to readily solvable instances, then the problem can be solved using a
recursive algorithm.
int factorial(int n)
if (n == 0)
return 1;
b) Backtracking Algorithm
Backtracking Algorithm tries each possibility until they find the right one. It is a depth-
first search of the set of possible solution. During the search, if an alternative doesn't work,
then backtrack to the choice point, the place which presented different alternatives, and
tries the next alternative.
Backtracking Algorithm
Backtrack(x)
if x is not a solution
return false
if x is a new solution
add to list of solutions
backtrack(expand x)
A space state tree is a tree representing all the possible states (solution or nonsolution) of the
problem from the root as an initial state to the leaf as a terminal state
It is a top-down approach. The algorithms which follow the divide & conquer techniques involve
three steps:
Combine the solution of the sub problems (top level) into a solution of the whole original
problem
Again, divide each subpart recursively into two halves until you get individual elements.
If any problem can be divided into sub problems, which in turn are divided into smaller sub
problems, and if there are overlapping among these sub problems, then the solutions to these sub
problems can be saved for future reference. In this way, efficiency of the CPU can be enhanced.
This method of solving a solution is referred to as dynamic programming.
Let's find the fibonacci sequence upto 5th term. A fibonacci series is the sequence of numbers in
which each number is the sum of the two preceding ones. For example, 0,1,1, 2, 3. Here, each
number is the sum of the two preceding numbers.
Algorithm
1. If n <= 1, return 1.
3. The third term is sum of 0 (from step 1) and 1(from step 2), which is 1.
4. The fourth term is the sum of the third term (from step 3) and second term (from step 2)
i.e. 1 + 1 = 2.
5. The fifth term is the sum of the fourth term (from step 4) and third term (from step 3)
i.e. 2 + 1 = 3.
Hence, we have the sequence 0,1,1, 2, 3. Here, we have used the results of the previous steps as
shown below. This is called a dynamic programming approach.
F(0) = 0
F(1) = 1
E. GREEDY ALGORITHMS
A greedy algorithm is an approach for solving a problem by selecting the best option available at
the moment. It doesn't worry whether the current best result will bring the overall optimal result.
The algorithm never reverses the earlier decision even if the choice is wrong. It works in a top-
down approach.
This algorithm may not produce the best result for all the problems. It's because it always goes
for the local best choice to produce the global best result.
For example, suppose we want to find the longest path in the graph below from root to leaf.
Let's use the greedy algorithm here.
1. Let's start with the root node 20. The weight of the right child is 3 and the weight of the left
child is 2.
2. Our problem is to find the largest path. And, the optimal solution at the moment is 3. So, the
greedy algorithm will choose 3.
3. Finally the weight of an only child of 3 is 1. This gives us our final result 20 + 3 + 1 = 24.
However, it is not the optimal solution. There is another path that carries more weight (20 + 2 +
10 = 32) as shown in the image below.
A branch and bound algorithm consist of stepwise enumeration of possible candidate solutions
by exploring the entire search space. With all the possible solutions, we first build a rooted
decision tree. The root node represents the entire search space:
A straightforward method of solving a problem that rely on sheer computing power and trying
every possibility rather than advanced techniques to improve efficiency.
A Brute Force Algorithm is the straightforward approach to a problem i.e., the first approach
that comes to our mind on seeing the problem
An example in computer science is the traveling salesman problem (TSP). Suppose a salesman
needs to visit 10 cities across the country. How does one determine the order in which those
cities should be visited such that the total distance traveled is minimized?
The brute force solution is simply to calculate the total distance for every possible route and then
select the shortest one. This is not particularly efficient because it is possible to eliminate many
possible routes through clever algorithms.
ALGORITHM ANALYSIS
Analysis of Algorithms is the area of computer science that provides tools to analyze the
efficiency (time and space) of different methods of solutions. Algorithm is analyzed to to
discover its characteristics in order to evaluate its suitability for various applications
Time Complexity: Running time of a program as a function of the size of the input.
Space Complexity: Some forms of analysis could be done based on how much space an
algorithm needs to complete its task.
1. Asymptotic Notations
2. Master theorem
1. Asymptotic Notations
Asymptotic analysis refers to computing the running time of any operation in mathematical units
of computation. For example, the running time of one operation is computed as f(n). This means
the first operation running time will increase linearly with the increase in n and the running time
of the second operation will increase exponentially when n increases.
Asymptotic Notations
Following are the commonly used asymptotic notations to calculate the running time complexity
of an algorithm.
Big Oh Notation, Ο
The notation Ο(n) is the formal way to express the upper bound of an algorithm's running time. It
measures the worst case time complexity or the longest amount of time an algorithm can possibly
take to complete.
NB: Consider function f(n) as time complexity of an algorithm and g(n) is the most
significant term. If f(n) <= C g(n) for all n >= n0, C > 0 and n0 >= 1. Then we can represent
f(n) as O(g(n)).
f(n) = O(g(n))
Omega Notation, Ω
The notation Ω(n) is the formal way to express the lower bound of an algorithm's running time. It
measures the best case time complexity or the best amount of time an algorithm can possibly
take to complete.
In above graph after a particular input value n0, always C g(n) is less than f(n) which indicates
the algorithm's lower bound.
NB: Consider function f(n) as time complexity of an algorithm and g(n) is the most
significant term. If f(n) >= C g(n) for all n >= n0, C > 0 and n0 >= 1. Then we can represent
f(n) as Ω(g(n)).
f(n) = Ω(g(n))
Theta Notation, θ
In above graph after a particular input value n0, always C1 g(n) is less than f(n) and C2 g(n) is
greater than f(n) which indicates the algorithm's average bound.
NB: Consider function f(n) as time complexity of an algorithm and g(n) is the most
significant term. If C1 g(n) <= f(n) <= C2 g(n) for all n >= n0, C1 > 0, C2 > 0 and n0 >= 1.
Then we can represent f(n) as Θ(g(n)).
f(n) = Θ(g(n))
NB
O (log N) basically means time goes up linearly while the n goes up exponentially. So if it takes
1 second to compute 10 elements, it will take 2 seconds to compute 100 elements, 3 seconds to
compute 1000 elements, and so on. It is O (log n) when we do divide and conquer type of
algorithms (average case)
0(1) It means the running time of an algorithm is a constant. It takes a constant time, like 14
nanoseconds, or three minutes no matter the amount of data in the set. –(best case)
The master method is a formula for solving recurrence relations of the form:
where,
n = size of input
n/b = size of each sub problem. All sub problems are assumed to have the same
size.
f(n) = cost of the work done outside the recursive call, which includes the cost of
dividing the problem and cost of merging the solutions
Here, a ≥ 1 and b > 1 are constants, and f(n) is an asymptotically positive function.
CASE 1
If the cost of solving the sub-problem at each level is nearly equal, then the
value of f(n) will be nlogb a. Thus, the time complexity will be f(n) times the
total number of levels ie. nlogb a * log n
A search algorithm is an algorithm which solves a search problem. Search algorithms work to
retrieve information stored within some data.
There are two prominent search strategies are extensively used to find a specific item on a list
i. Linear Search
ii. Binary Search
1. Linear Search
In linear search a sequential search is made over all items one by one. Every item is checked and
if a match is found then that particular item is returned, otherwise the search continues till the
end of the data collection.
It examines each element until it finds a match, starting at the beginning of the data set, until the
end. The search is finished and terminated once the target element is located. If it finds no match,
the algorithm must terminate its execution and return an appropriate result. The linear search
algorithm is easy to implement and efficient in two scenarios:
Linear search is mostly used to search an unordered list in which the items are not sorted
Linear Search ( Array Arr, Value a ) // Arr is the name of the array, and a is the searched
element.
Step 4: Set i to i + 1
Consider an array of size 7 with elements 13, 9, 21, 15, 39, 19, and 27 that starts with 0 and ends
with size minus one, 6.
Search element = 39
Step 1: The searched element 39 is compared to the first element of an array, which is 13.
Step 3: Now, search element 39 is compared with the third element, which is 21.
Again, both the elements are not matching, you move onto the next following element.
Step 4; Next, search element 39 is compared with the fourth element, which is 15.
Step 5: Next, search element 39 is compared with the fifth element 39.
A perfect match is found, you stop comparing any further elements and terminate the Linear
Search Algorithm and display the element found at location 4.
Followed by the practical implementation, you will move on to the complexity of the linear
search algorithm.
You have three different complexities faced while performing Linear Search Algorithm, they are
mentioned as follows.
1. Best Case
2. Worst Case
3. Average Case
The element being searched may be at the last position in the array or not at all.
Thus, in the worst-case scenario, the linear search algorithm performs 0(n) operations.
When the element to be searched is in the middle of the array, the average case of the Linear
Search Algorithm is 0(n).
The linear search algorithm takes up no extra space; its space complexity is 0(n) for an array of n
elements.
A linear complexity– 0(n) denotes the number of iterations, calculations or steps needed at
most (worst case), for the algorithm to reach its end-state, n being the objects given at the start of
the algorithm. If the array has 10 items, the function calls System.out.println 10 times. If it has
1,000 items, it calls it 1,000 times.
Linear search is easy to implement and effective when the array contains only a few
elements.
Linear Search is also efficient when the search is performed to fetch a single search in an
unordered-List.
Binary search is the search technique which works efficiently on the sorted lists. Hence, in order
to search an element into some list by using binary search technique, we must ensure that the list
is sorted.
Binary search follows divide and conquer approach in which, the list is divided into two halves
and the item is compared with the middle element of the list. If the match is found then, the
location of middle element is returned otherwise, we search into either of the halves depending
upon the result produced through the match.
The midpoint is found by adding the lowest position to the highest position and dividing by
2.
8/2 = 4
NOTE - if the answer is a decimal, round up. For example, 3.5 becomes 4. An alternative is
to round down, but be consistent.
7 is less than 11, so the bottom half of the list - including the midpoint - is discarded.
14 is greater than 11, so the top half of the list (including the midpoint) is discarded.
Check at position 6.
Time Complexity
Best Case Complexity - In Binary search, best case occurs when the element to search is found
in first comparison, i.e., when the first middle element itself is the element to be searched. The
best-case time complexity of Binary search is O(1)
Worst Case Complexity - In Binary search, the worst case occurs, when we have to keep
reducing the search space till it has only one element. The worst-case time complexity of Binary
search is O(logn).
It eliminates half of the list from further searching by using the result of each comparison.
It indicates whether the element being searched is before or after the current position in
the list.
For large lists of data, it works significantly better than linear search.
The interaction of binary search with memory hierarchy i.e. caching is poor.
The importance of sorting lies in the fact that data searching can be optimized to a very high
level, if data is stored in a sorted manner. Sorting is also used to represent data in more readable
formats. Following are some of the examples of sorting in real-life scenarios:
Telephone Directory – The telephone directory stores the telephone numbers of people
sorted by their names, so that the names can be searched easily.
Dictionary – The dictionary stores words in an alphabetical order so that searching of any
word becomes easy.
Sorting algorithms that do not requires extra space for comparison are known as in place
sorting while Sorting algorithms that requires extra space for comparison are known as in out
of place sorting.
A non-adaptive algorithm is one which does not take into account the elements which are already
sorted. They try to force every single element to be re-ordered to confirm their sortedness.
A logarithmic complexity- O (log N) basically means time goes up linearly while the n goes up
exponentially. To perform operation of N elements, it often takes the logarithmic base as 2. So if
it takes 1 second to compute 10 elements, it will take 2 seconds to compute 100 elements, 3
seconds to compute 1000 elements, and so on. It is O (log n) when we do divide and conquer
type of algorithms (average case),
Logarithmic time complexities usually apply to algorithms that divide problems in half
every time
Constant Complexity - 0(1) It means the running time of an algorithm is a constant. It takes a
constant time, like 14 nanoseconds, or three minutes no matter the amount of data in the set. –
(best case)
For instance, if a function takes the same time to process ten elements and 1 million items,
then we say that it has a constant growth rate or O(1)
A linear complexity– 0(n) denotes the number of iterations, calculations or steps needed at
most (worst case), for the algorithm to reach its end-state, n being the objects given at the start of
the algorithm. If the array has 10 items, the function calls System.out.println 10 times. If it has
1,000 items, it calls it 1,000 times.
Linear time complexity O(n) means that the algorithms take proportionally longer to
complete as the input grows.
O(n2) - Quadratic Complexity - If our array has n items, our outer loop runs n times and our
inner loop runs n times for each iteration of the outer loop, giving us n2 total calls
to System.out.println. Thus this function runs in O(n2) time (or quadratic time). If the array has
10 items, we have to print 100 times. If it has 1,000 items, we have to print 1,000,000 times.
A function with a quadratic time complexity has a growth rate of n2. If the input is size 2, it
will do four operations. If the input is size 8, it will take 64, and so on.
Sorting items in a collection using bubble sort, insertion sort, or selection sort.
O(n3) - Cubic Complexity: For N inputs data size, it execute the order of N3 steps on N element
to solve a given problem
For example, if there exist 100 elements, it is going to execute 1000000 steps
Linearithmic time complexity it’s slightly slower than a linear algorithm. 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 10000 steps for solving a given
problem
O(2n) - Exponential Complexity: The algorithm takes twice as long for every new element
added, so even small increases in n dramatically increase the running time.
Exponential (base 2) running time means that the calculations performed by an algorithm
double every time as the input grows.
Fibonacci.
This sorting algorithm is comparison-based algorithm in which each pair of adjacent elements
is compared and the elements are swapped if they are not in order. This algorithm is not suitable
for large data sets as its average and worst case complexity are of O(n2) where n is the number
of items.
In each pass, bubble sort compares the adjacent elements of the array.
It then swaps the two elements if they are in the wrong order.
In each pass, bubble sort places the next largest element to its proper position.
begin BubbleSort(list)
Example
We take an unsorted array for our example. Bubble sort takes Ο(n2) time so we're
keeping it short and precise.
Bubble sort starts with very first two elements, comparing them to check which one is
greater.
In this case, value 33 is greater than 14, so it is already in sorted locations. Next, we
compare 33 with 27.
Next we compare 33 and 35. We find that both are in already sorted positions.
We swap these values. We find that we have reached the end of the array. After one
iteration, the array should look like this −
To be precise, we are now showing how an array should look like after each iteration.
After the second iteration, it should look like this −
Notice that after each iteration, at least one value moves at the end.
Following are the Time and Space complexity for the Bubble Sort algorithm.
Worst Case Time Complexity [ Big-O ]: O(n2) - element to compare with all the other
elements )n*n
It is an in-place sorting algorithm i.e. it modifies elements of the original array to sort the given
array.
Insertion sort is a sorting algorithm in which the elements are transferred one at a time to the
right position. In other words, an insertion sort helps in building the final sorted list, one item at
a time, with the movement of higher-ranked elements. This algorithm is not suitable for large
data sets as its average and worst case complexity are of Ο(n2), where n is the number of items
It finds that both 14 and 33 are already in ascending order. For now, 14 is in sorted sub- list.
By now we have 14 and 27 in the sorted sub-list. Next, it compares 33 with 10.
So we swap them.
We swap them again. By the end of third iteration, we have a sorted sub-list of 4 items.
This process goes on until all the unsorted values are covered in a sorted sub-list. Now we shall
see some programming aspects of insertion sort.
Algorithm
Step 1- Start
Step 8 – Stop
Even though insertion sort is efficient, still, if we provide an already sorted array to the insertion
sort algorithm, it will still execute the outer for loop, thereby requiring n steps to sort an already
sorted array of n elements, which makes its best case time complexity a linear function of n.
Wherein for an unsorted array, it takes for an element to compare with all the other elements
which mean every n element compared with all other n elements. Thus, making it for n x n, i.e.,
n2 comparisons.
Space Complexity
Constant Complexity - 0(1) It takes a constant space, no matter the elements in that array
3. Selection Sorting
The selection sort algorithm sorts an array by repeatedly finding the minimum element
(considering ascending order) from unsorted part and putting it at the beginning. The algorithm
maintains two sub arrays in a given array.
This algorithm is not suitable for large data sets as its average and worst case complexities are of
O(n2), where n is the number of items.
EXAMPLE
So we replace 14 with 10. After one iteration 10, which happens to be the minimum value in the
list, appears in the first position of the sorted list.
For the second position, where 33 is residing, we start scanning the rest of the list in a linear
manner.
We find that 14 is the second lowest value in the list and it should appear at the second place. We
swap these values.
The same process is applied to the rest of the items in the array. Following is a pictorial depiction
of the entire sorting process −
Algorithm
Time Complexity
Worst Case Time Complexity [ Big-O ]: O(n2) - The worst case is the case when the
array is already sorted (with one swap) but the smallest element is the last element.
The cost in this case is that at each step, a swap is done. This is because the smallest
element will always be the last element and the swapped element which is kept at the end
will be the second smallest element that is the smallest element of the new unsorted sub-
array. Hence, the worst case has:
N * (N+1) / 2 comparisons
N swaps
Best Case Time Complexity [Big-omega]: O(n) - The best case is the case when the array
is already sorted. For example, if the sorted number as a1, a2, ..., aN, then:a1, a2, a3, ..., aN will
be the best case for our particular implementation of Selection Sort.
In terms of Space Complexity, Selection Sort is optimal as the memory requirements remain
same for every input.
4. Merge Sort
Example
We know that merge sort first divides the whole array iteratively into equal halves unless the
atomic values are achieved. We see here that an array of 8 items is divided into two arrays of size
4.
This does not change the sequence of appearance of items in the original. Now we divide these
two arrays into halves.
We further divide these arrays and we achieve atomic value which can no more be divided.
Now, we combine them in exactly the same manner as they were broken down. Please note the
color codes given to these lists.
After the final merging, the list should look like this −
Algorithm
Step 1: Start
Step 3 − divide the list recursively into two halves until it can no more be divided.
Step 4 − merge the smaller lists into new list in sorted order.
Step 5: stop
Time complexity of Merge Sort is θ(nLogn) in all 3 cases (worst, average and best) as merge
sort always divides the array into two halves and takes linear time to merge two halves.
At each level merge sort has to perform N operation. The height of any tree is given by log
N.
Time complexity is given by operation at each level * the height of the tree
=n log n
SPACE COMPLEXITY
It takes O (N) space as we divide the array and store it into them where the total space consumed
in making the entire array and merging back into one array is the total number of elements
present in it.
5. QUICK SORT
Quicksort is another sorting algorithm which uses Divide and Conquer for its implementation.
Quicksort first chooses a pivot and then partition the array around this pivot. In the partitioning
process, all the elements smaller than the pivot are put on one side of the pivot and all the
elements larger than it on the other side.
Pivot element can be any element from the array; it can be the first element, the last element or
any random element.
Quick sort partitions an array and then calls itself recursively twice to sort the two resulting sub
arrays. This algorithm is quite efficient for large-sized data sets as its average and worst case
complexity are of O(nlogn), where n is the number of items.
For example: In the array {52, 37, 63, 14, 17, 8, 6, 25}, we take 25 as pivot. So after the first
pass, the list will be changed like this.
{6 8 17 14 25 63 37 52}
Hence after the first pass, pivot will be set at its position, with all the elements smaller to it on its
left and all the elements larger than to its right. Now 6 8 17 14 and 63 37 52 are considered as
two separate sun arrays, and same recursive logic will be applied on them, and we will keep
doing this until the complete array is sorted.
Step 2 − Take two variables to point left and right of the list excluding pivot
Step 7 − if both step 5 and step 6 does not match swap left and right
Step 8 − if left ≥ right, the point where they met is new pivot
Example
In the given array, we consider the leftmost element as pivot. So, in this case, a[left] = 24,
a[right] = 27 and a[pivot] = 24.
Since, pivot is at left, so algorithm starts from right and move towards left.
Because, a[pivot] > a[right], so, algorithm will swap a[pivot] with a[right], and pivot moves to
right, as -
Now, a[left] = 19, a[right] = 24, and a[pivot] = 24. Since, pivot is at right, so algorithm starts
from left and moves to right.
Now, a[left] = 29, a[right] = 24, and a[pivot] = 24. As a[pivot] < a[left], so, swap a[pivot] and
a[left], now pivot is at left, i.e. –
Since, pivot is at left, so algorithm starts from right, and move to left. Now, a[left] = 24, a[right]
= 29, and a[pivot] = 24. As a[pivot] < a[right], so algorithm moves one position to left, as –
Now, a[pivot] = 24, a[left] = 24, and a[right] = 24. So, pivot, left and right are pointing the same
element. It represents the termination of procedure.
Element 24, which is the pivot element is placed at its exact position.
Elements that are right side of element 24 are greater than it, and the elements that are left side of
element 24 are smaller than it.
Now, in a similar manner, quick sort algorithm is separately applied to the left and right sub-
arrays. After sorting gets done, the array will be –
o Best Case Complexity - In Quicksort, the best-case occurs when the pivot element is the
middle element or near to the middle element. The best-case time complexity of quicksort
is O(n*logn).
o Average Case Complexity - It occurs when the array elements are in jumbled order that
is not properly ascending and not properly descending. The average case time complexity
of quicksort is O(n*logn).
o Worst Case Complexity - In quick sort, worst case occurs when the pivot element is
either greatest or smallest element. Suppose, if the pivot element is always the last
element of the array, the worst case would occur when the given array is sorted already in
ascending or descending order. The worst-case time complexity of quicksort is O(n2).
Space Complexity
Binary Tree is a special data structure used for data storage purposes. A binary tree has a special
condition that each node can have a maximum of two children. A binary tree has the benefits of
both an ordered array and a linked list as search is as quick as in a sorted array and insertion or
deletion operation are as fast as in linked list
Important Terms
Tree Terminology-
The first node from where the tree originates is called as a root node.
In a tree with n number of nodes, there are exactly (n-1) number of edges.
The node which has a branch from it to any other node is called as a parent node.
In other words, the node which has one or more children is called as a parent node.
5. Siblings-
In other words, nodes with the same parent are sibling nodes.
Degree of a tree is the highest degree of a node among all the nodes in the tree.
7. Internal Node-
The node which has at least one child is called as an internal node.
The node which does not have any child is called as a leaf node.
9. Level-
The level count starts with 0 and increments by 1 at each level or step.
Total number of edges that lies on the longest path from any leaf node to a particular
node is called as height of that node.
11. Depth-
Total number of edges from root node to a particular node is called as depth of that
node.
Depth of a tree is the total number of edges from root node to a leaf node in the longest
path.
Huffman coding tree or Huffman tree is a full binary tree in which each leaf of
the tree corresponds to a letter in the given alphabet. Define the weighted path length of a
leaf to be its weight times its depth.
The basic operations that can be performed on a binary search tree data structure, are the
following −
Insert Operation
The very first insertion creates the tree. Afterwards, whenever an element is to be inserted, first
locate its proper location. Start searching from the root node, then if the data is less than the key
value, search for the empty location in the left sub tree and insert the data. Otherwise, search for
the empty location in the right sub tree and insert the data.
Algorithm
If root is NULL
then create root node
return
If root exists then
compare the data with node.data
while until insertion position is located
If data is greater than node.data
goto right subtree
else
Search Operation
Whenever an element is to be searched, start searching from the root node, then if the data is less
than the key value, search for the element in the left sub tree. Otherwise, search for the element
in the right sub tree. Follow the same algorithm for each node.
Algorithm
Traversal
Traversal is a process to visit all the nodes of a tree and may print their values too. Because, all
nodes are connected via edges (links) we always start from the root (head) node. That is, we
cannot randomly access a node in a tree. There are three ways which we use to traverse a tree −
In-order Traversal
Pre-order Traversal
Post-order Traversal
In-order Traversal
If a binary tree is traversed in-order, the output will produce sorted key values in an ascending
order.
D→B→E→A→F→C→G
Algorithm
Pre-order Traversal
In this traversal method, the root node is visited first, then the left subtree and finally the right
subtree.
A→B→D→E→C→F→G
Algorithm
Post-order Traversal
In this traversal method, the root node is visited last, hence the name. First we traverse the left
subtree, then the right subtree and finally the root node.
D→E→B→F→G→C→A
Algorithm
A graph is a pictorial representation of a set of objects where some pairs of objects are connected
by links. The interconnected objects are represented by points termed as vertices, and the links
that connect the vertices are called edges.
Graph Terminology
Path
A path can be defined as the sequence of nodes that are followed in order to reach some terminal
node V from the initial node U.
Closed Path
A path will be called as closed path if the initial node is same as terminal node. A path will be
closed path if V0=VN.
Simple Path
If all the nodes of the graph are distinct with an exception V0=VN, then such path P is called as
closed simple path.
Cycle
A cycle can be defined as the path which has no repeated edges or vertices except the first and
last vertices.
Connected Graph
A connected graph is the one in which some path exists between every two vertices (u, v) in V.
There are no isolated nodes in connected graph.
Complete Graph
A complete graph is the one in which every node is connected with all other nodes. A complete
graph contain n(n-1)/2 edges where n is the number of nodes in the graph.
Weighted Graph
In a weighted graph, each edge is assigned with some data such as length or weight. The weight
of an edge e can be given as w(e) which must be a positive (+) value indicating the cost of
traversing the edge.
Digraph
Loop
An edge that is associated with the similar end points can be called as Loop.
Adjacent Nodes
If two nodes u and v are connected via an edge e, then the nodes u and v are called as neighbours
or adjacent nodes.
A degree of a node is the number of edges that are connected with that node. A node with degree
0 is called as isolated node.
Graph representation
Traversing the graph means examining all the nodes and vertices of the graph. Graph traversal
means visiting every vertex and edge exactly once in a well-defined order. While using certain
graph algorithms, you must ensure that each vertex of the graph is visited exactly once. The
order in which the vertices are visited are important and may depend upon the algorithm or
question that you are solving.
During a traversal, it is important that you track which vertices have been visited. The most
common way of tracking vertices is to mark them. There are two standard methods.
The Breadth First Search (BFS) traversal is an algorithm, which is used to visit all of the nodes
of a given graph. In this traversal algorithm one node is selected and then all of the adjacent
nodes are visited one by one. After completing all of the adjacent vertices, it moves further to
check another vertices and checks its adjacent vertices again.
Rule 1 − Visit the adjacent unvisited vertex. Mark it as visited. Display it. Insert it in a
queue.
Rule 2 − If no adjacent vertex is found, remove the first vertex from the queue.
A standard BFS implementation puts each vertex of the graph into one of two categories:
1. Visited
2. Not Visited
The purpose of the algorithm is to mark each vertex as visited while avoiding cycles.
1. Start by putting any one of the graph's vertices at the back of a queue.
2. Take the front item of the queue and add it to the visited list.
3. Create a list of that vertex's adjacent nodes. Add the ones which aren't in the visited list to
the back of the queue.
We start from vertex 0, the BFS algorithm starts by putting it in the Visited list and putting all its
adjacent vertices in the stack.
Next, we visit the element at the front of queue i.e. 1 and go to its adjacent nodes. Since 0 has
already been visited, we visit 2 instead.
The time complexity of the BFS algorithm is represented in the form of O(V + E), where V is the
number of nodes and E is the number of edges.
The Depth First Search (DFS) is a graph traversal algorithm. In this algorithm one starting vertex
is given, and when an adjacent vertex is found, it moves to that adjacent vertex first and try to
traverse in the same manner. It uses a stack to remember to get the next vertex to start a search,
when a dead end occurs in any iteration.
Depth first Search or Depth first traversal is a recursive algorithm for searching all the vertices of
a graph or tree data structure. Traversal means visiting all the nodes of a graph.
A standard DFS implementation puts each vertex of the graph into one of two categories:
1. Visited
2. Not Visited
The purpose of the algorithm is to mark each vertex as visited while avoiding cycles.
2. Take the top item of the stack and add it to the visited list.
3. Create a list of that vertex's adjacent nodes. Add the ones which aren't in the visited list to
the top of the stack.
Next, we visit the element at the top of stack i.e. 1 and go to its adjacent nodes. Since 0 has
already been visited, we visit 2 instead.
The time complexity of the DFS algorithm is represented in the form of O(V + E), where V is the
number of nodes and E is the number of edges.
Floyd-Warshall Algorithm
A weighted graph is a graph in which each edge has a numerical value associated with it.
Floyd-Warshall algorithm is used to find all pair shortest path problem from a given weighted
graph. As a result of this algorithm, it will generate a matrix, which will represent the minimum
distance from any node to all other nodes in the graph.
This algorithm follows the dynamic programming approach to find the shortest paths.
Floyd-Warshall Algorithm
Step 1: Initialize the shortest paths between any 2 vertices with Infinity.
Step 2: Find all pair shortest paths that use 0 intermediate vertices, then find the shortest paths
that use 1 intermediate vertex and so on.Until using all N vertices as intermediate nodes.
Step 3: Minimize the shortest paths between any 2 pairs in the previous operation.
Step 4: For any 2 vertices (i,j) , one should actually minimize the distances between this pair
using the first K nodes, so the shortest path will be: min(dist[i][k]+dist[k][j],dist[i][j]).
dist[i][k] represents the shortest path that only uses the first K vertices, dist[k][j] represents the
shortest path between the pair k,j. As the shortest path will be a concatenation of the shortest
path from i to k, then from k to j.
Follow the steps below to find the shortest path between all the pairs of vertices.
1. Create a matrix A0 of dimension n*n where n is the number of vertices. The row and the
column are indexed as i and j respectively. i and j are the vertices of the graph.
Each cell A[i][j] is filled with the distance from the ith vertex to the jth vertex. If there is
no path from ith vertex to jth vertex, the cell is left as infinity.
Make a matrix A0 which stores the information about the minimum distance of path
between the direct paths for every pair of vertices. If there is no edge between two
vertices
In this step, we use Ao matrix and find the shortest path via 1 as an intermediate node.
Here all the path that belongs to 1 remain unchanged in the matrix A1.
Now, create a matrix A1 using matrix A0. The elements in the first column and the first
row are left as they are. The remaining cells are filled in the following way.
Let k be the intermediate vertex in the shortest path from source to destination. In this
step, k is the first vertex. A[i][j] is filled with (A[i][k] + A[k][j]) if (A[i][j] > A[i][k] +
A[k][j]).
That is, if the direct distance from the source to the destination is greater than the path
through the vertex k, then the cell is filled with A[i][k] + A[k][j].
Step-3
In this step, we use A1 matrix and find the shortest path via 2 as an intermediate node.
Here all the path that belongs to 2 remain unchanged in the matrix A2.
Similarly, A2 is created using A1. The elements in the second column and the second row
are left as they are.
Step-4
In this step, we use A2 matrix and find the shortest path via 3 as an intermediate node.
Here all the path that belongs to 3 remain unchanged in the matrix A3
Step-5
In this step, we use A3 matrix and find the shortest path via 4 as an intermediate node. Here all
the path that belongs to 4 remain unchanged in the matrix A4.
Time Complexity
There are three loops. Each loop has constant complexities. So, the time complexity of the
Floyd-Warshall algorithm is O(n3).
Space Complexity