Brute Force and Divide-Conquer Algorithms
Brute Force and Divide-Conquer Algorithms
1. BRUTE FORCE
Brute force approach is not an important algorithm design strategy for the
following reasons:
First, unlike some of the other strategies, brute force is applicable to a very
wide varietyof problems. It’s used for many elementary but algorithmic tasks
such as computing the sum of n numbers, finding the largest element in a list
and so on.
Second, for some problem it yields reasonable algorithms of at least some
practical valuewith no limitation on instance size.
CLOSEST-PAIR PROBLEM
The closest-pair problem calls for finding 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.
An air- traffic controller might be interested in two closest planes as the most
probable collision candidates. A regional postal service manager might need a
solution to the closest pair problem to find candidate post-office locations to
be closed.
For numerical data, this metric is usually the Euclidean distance; for text and
other non numerical data, metrics such as the Hamming distance are used. A
bottom-up algorithm begins with each element as a separate cluster and
merges them into successively larger clusters by combining the closest pair of
clusters.
Pseudocode below computes the distance between the two closest points;
getting the closest points themselves requires just a trivial modification.
ALGORITHM BruteForceClosestPair(P )
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 is computing the square root. In the age
of electronic calculators with a square-root button, one might be led to believe
that computing the square root is as simple an operation as, say, addition or
multiplication.
Of course, it is not. For starters, even for most integers, square roots are
irrational numbers that therefore can be found only approximately.
The trick is to realize that can simply ignore the square-root function and
compare the values (xi− xj )2 + (yi− yj )2 themselves.
Then the basic operation of the algorithm will be squaring a number. The
number of times it will be executed can be computed as follows: Of course,
speeding up the innermost loop of the algorithm could only decrease the
algorithm’s running time by a constant but it cannot improve its asymptotic
efficiency class.
CONVEX-HULL PROBLEM
Finally, convex hulls are important for solving many optimization problems,
because their extreme points provide a limited set of solution candidates.
A set of points (finite or infinite) in the plane is called convex if for any two
point’s 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 are convex, and so are a straight line,
a triangle, a rectangle, and, more generally, any convex polygon,1 a circle, and
the entire plane.
On the other hand, the sets, 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. Now we are ready for the notion of the convex hull.
Intuitively, the convex hull of a set of n points in the plane is the smallest
convex polygon that contains all of them either inside or on its boundary
Imagine that the points in question are represented by nails driven into a
large sheet of plywood representing the plane. Take a rubber band and
stretch it to include all the nails, then let it snap into place.
The convex hull is the area bounded by the snapped rubber band A formal
definition of the convex hull that is applicable to arbitrary sets, including sets
of points that happen to lie on the same line, follows.
The convex hull of a set S of points is the smallest convex set containing S.
(The “smallest” requirement means that the 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.
EXHAUSTIVE SEARCH
The problem asks to find the shortest tour through a given set of n cities that
visits each city exactly once before returning to the city where it started.
The problem can be stated as the problem of finding the shortest Hamiltonian
circuit of the graph-which is a weighted graph, with the graph’s vertices
representing the cities and the edge weights specifying the distance.
Hamiltonian circuit is defined as a cycle that passes thru all the vertices of
the graph exactly once.
The Hamiltonian circuit can also be defined as a sequence of n+1 adjacent
vertices vi0, vi1,… Vin-1, vi0, where the first vertex of the sequence is the
same as the last one while all other n-1 verticesare distinct.
Obtain the tours by generating all the permutations of n-1 intermediate cities,
compute the tour lengths, and find the shortest among them.
Consider the condition that the vertex B precedes C then, the total no of
permutations will be (n-1)! /2, which is impractical except for small values of
n. On the other hand, if the starting vertex is not considered for a single vertex,
the number of permutations will be even large for n values.
The problem states that: given n items of known weights w1,w 2, … wn and
values v1, v2,…, vn and a knapsack of capacity w, find the most valuable subset
of the items that fit into the knapsack.
Eg consider a transport plane that has to deliver the most valuable set of
items to a remote location withoutexceeding its capacity.
Example:
W=10, w1,w2, w3, w4 = { 7,3,4,5 } and v1,v2, v3, v4 = { 42,12,40,25 }
This problem considers all the subsets of the set of n items given, computing
the total weight of each subset in order to identify feasible subsets (i.e., the
one with the total weight not exceeding the knapsack capacity) and finding
the largest among the values, which is an optimal solution.
Thus, for both TSP and knapsack, exhaustive search leads to algorithms that
are inefficient on every input. These two problems are the best-known
examples of NP- hard problems.
No polynomial- time algorithm is known for any NP-hard problem. The two
methods Backtracking and Branch & bound enable us to solve this problem in
less than exponential time.
4. ASSIGNMENT PROBLEM
The problem is: given n people who need to be assigned to execute n jobs,
one person per job. The cost if the ith person is assigned to the jth job is a
known quantity c[i,j] for each pair i,j=1,2,….,n. The problem is to find an
assignment with the smallest total cost.
From the problem, obtain a cost matrix, C. The problem calls for a selection of
one element in each row of the matrix so that all selected elements are in
different columns and the total sum of the selected elements is the smallest
possible.
Describe feasible solutions to the assignment problem as n- tuples <j1 , j2, ….,
jn> in which the ith component indicates the column n of the element
selected in the i th row. i.e.,
The job number assigned to the ith person. For eg <2,3,4,1> indicates a
feasible assignment of person 1 to job2, person 2 to job 3, person3 to job 4
and person 4 to job 1.
Based on number of permutations, the general case for this problem is n!,
which is impractical except for small instances. There is an efficient algorithm
for this problem called the Hungarian method.
Divide
If q is the half-way point between p and r, then we can split the
subarray A[p..r] into two arrays A[p..q] and A[q+1, r].
Conquer
In the conquer step, we try to sort both the subarrays A[p..q] and A[q+1, r]. If
we haven't yet reached the base case, we again divide both these subarrays
and try to sort them.
Combine
When the conquer step reaches the base step and we get two sorted
subarrays A[p..q] and A[q+1, r] for array A[p..r], we combine the results by
creating a sorted array A[p..r] from two sorted subarrays A[p..q] and A[q+1,
r].
Explain the method performing Multiplication of two large integers.
Explain how Divide conquer method can be used for the same. [Part
B MAY/JUNE 2016]
Look at two approaches to multiplying big numbers: the grade school method
and the divide and conquer method.
This technique takes quadratic time, which is insufficient for big numbers. Let’s
look into a more convenient method of multiplication.
There are two ways to perform large integer multiplication using divide and
conquer. The first method – we call dumb method – does not improve the
running time. Second method – we call clever approach – performs better then
the traditional approach for integer multiplication.
Dumb Approach:
Let us represent number D as D = dn-1dn-2. . . d2d1d0. Each digit di is the ith least
significant digit in number D. Value of each position is given by,
d0 = 100 position
d1 = 101 position
d2 = 102 position
.
.
dn – 1 = 10n – 1 position.
According to position value,
45 = 4*101 + 5*100
23 = 2*101 + 3*100
For any real values a, b, c and d,
(a + b)*(c + d) = (a*c + a*d + b*c + b*d)
So, 45 * 23 = (4*101 + 5*100) * (2*101 + 3*100)
= (4 *2) *102 + (4*3 + 5*2) *101 + (5*3) *100
= 800 + 220 + 15
= 1035
Let’s derive formula to multiply two numbers of two digits each. Consider C = A
* B. If we consider A = a1a0 and B = b1b0, then
C = A * B = c2102 + c1101 + c0100
Where,
c2 = a1 * b1
c1 = a1* b0 + a0* b1
c0 = a0 * b0
This method does four multiplications, same as conventional method. This is as
dumb as grade school multiplication.
Example: Multiply 2345 with 678 using divide and conquer approach.
Solution:
Size of both operands must be even, so pad zero to the multiplier.
A = 2345 and B = 0678
A = a1a0 = 2345, hence a1 = 23 and a0 = 45
B = b1b0 = 0678, hence b1 = 06 and b0 = 78
C = A * B =c2104 + c1102 + c0100
c2 = a1 * b1
= 23 * 06
= 138
c1 = a1*b0 + a0*b1
= (23*78) + (45 * 06)
= 2064
c0 = a0 * b0
=(45 * 78)
= 3510
C = c2102 + c1101 + c0100
= 138*104 + 2064*102 + 3510
= 15, 89, 910
Clever Approach:
First approach:
c2 = a1 * b1
c1 = a1*b0 + a0*b1 … (1)
c0 = a0*b0
Second approach:
Generalization:
C = c210n + c110n/2 + c0
Where, c2 = a1 * b1
c0 = a0*b0
c1 = (a1 + a0) * (b1 + b0) – c2 +c0)
Strassen has used some formulas for multiplying the two 2*2 dimension
matrices where the number of multiplications is seven, additions and
subtractions are is eighteen, and in brute force algorithm, there is eight
number of multiplications and four addition.
When the order n of matrix reaches infinity, the utility of Strassen’s
formula is shown by its asymptotic superiority. For example, let us
consider two matrices A and B of n*n dimension, where n is a power of
two. It can be observed that we can have four submatrices of order n/2 *
n/2 from A, B, and their product C where C is the resultant matrix
of A and B.
Following are the formulae that are to be used for matrix multiplication.
1. D1 = (a11 + a22) * (b11 + b22)
2. D2 = (a21 + a22)*b11
3. D3 = (b12 – b22)*a11
4. D4 = (b21 – b11)*a22
5. D5 = (a11 + a12)*b22
6. D6 = (a21 – a11) * (b11 + b12)
7. D7 = (a12 – a22) * (b21 + b22)
C00= d1 + d4 – d5 + d7
C01 = d3 + d5
C10 = d2 + d4
C11 = d1 + d3 – d2 – d6
Here, C00, C01, C10, and C11 are the elements of the 2*2 matrix.
Algorithm for Strassen’s matrix multiplication
begin
If n = threshold then compute
C = x * y is a conventional matrix.
Else
Partition a into four sub matrices a00, a01, a10, a11.
Partition b into four sub matrices b00, b01, b10, b11.
Strass ( n/2, a00 + a11, b00 + b11, d1)
Strass ( n/2, a10 + a11, b00, d2)
Strass ( n/2, a00, b01 – b11, d3)
Strass ( n/2, a11, b10 – b00, d4)
Strass ( n/2, a00 + a01, b11, d5)
Strass (n/2, a10 – a00, b00 + b11, d6)
Strass (n/2, a01 – a11, b10 + b11, d7)
C = d1+d4-d5+d7 d3+d5
d2+d4 d1+d3-d2-d6
end if
return (C)
end.
#include <stdio.h>
int main( )
{
int a[2][2],b[2][2],c[2][2],i,j;
int m1,m2,m3,m4,m5,m6,m7;
// Here we are scanning and printing the first matrix
printf("Enter the 4 elements of first matrix: ");
for(i=0;i<2;i++)
for(j=0;j<2;j++)
scanf("%d",&a[i][j]);
// Here we are scanning and printing the second matrix
printf("Enter the 4 elements of second matrix: ");
for(i=0;i<2;i++)
for(j=0;j<2;j++)
scanf("%d",&b[i][j]);
c[0][0]=m1+m4-m5+m7;
c[0][1]=m3+m5;
c[1][0]=m2+m4;
c[1][1]=m1-m2+m3+m6;
// As we got the value of the elements, we now print them
printf("\n After performing multiplication \n");
for(i=0;i<2;i++){
printf("\n");
for(j=0;j<2;j++)
printf("%d\t",c[i][j]);
}
return 0;
}
This is all about Strassen’s matrix multiplication.
7. Closest Pair of Points using Divide and Conquer algorithm
An array of n points in the plane, and the problem is to find out the closest
pair of points in the array.
This problem arises in a number of applications.
For example, in air-traffic control, you may want to monitor planes that come
too close together, since this may indicate a possible collision.
Recall the following formula for distance between two points p and q.
The Brute force solution is O (n^2), compute the distance between each pair
and return the smallest.
Using Divide and Conquer strategy, Calculate the smallest distance in
O(n Log n) time and also O (n x (Log n)2) time.
Algorithm
Output: The smallest distance between two points in the given array.
1) Find the middle point in the sorted array; we can take P [n/2] as middle
point.
2) Divide the given array in two halves. The first subarray contains points
from P[0] to P[n/2]. The second subarray contains points from P[n/2+1] to
P[n-1].
3) Recursively find the smallest distances in both subarrays. Let the distances
be dl and dr. Find the minimum of dl and dr. Let the minimum be d.
4) From the above 3 steps, we have an upper bound d of minimum distance.
Now we need to consider the pairs such that one point in pair is from the left
half and the other is from the right half.
Consider the vertical line passing through P[n/2] and find all points whose x
coordinate is closer than d to the middle vertical line. Build an array strip [ ]
of all such points.
5) Sort the array strip [ ] according to y coordinates. This step is O(n Log n). It
can be optimized to O ( n) by recursively sorting and merging.
6) Find the smallest distance in strip [ ]. From the first look, it seems to be a
O(n2) step, but it is actually O(n). It can be proved geometrically that for every
point in the strip.
7) Finally return the minimum of d and distance calculated in the above step
(step 6)
Time Complexity of (n (log n)2)
The above algorithm divides all points in two sets and recursively calls for
two sets.
After dividing, it finds the strip in O(n) time, sorts the strip in O(n Log n) time
and finally finds the closest points in strip in O(n) time.
The above algorithm divides all points in two sets and recursively calls for
two sets.
Also, it takes O(n) time to divide the Py array around the mid vertical line.
Convex hull is the smallest region covering given set of points. Polygon is
called convex polygon if the angle between any of its two adjacent edges is
always less than 1800. Otherwise, it is called a concave polygon. Complex
polygons are self-intersecting polygons.
The convex hull of the set of points Q is the convex polygon P that
encompasses all of the points given. The problem of finding the smallest
polygon P such that all the points of set Q are either on the boundary of P or
inside P is known as the convex hull problem. The convex hull of the points in
question is seen in following figure. The vertex of a polygon is a point shared
by two neighboring edges.
There exist multiple approaches to solve convex hull problem. In this article,
we will discuss how to solve it using divide and conquer approach.
Sort all of the points by their X coordinates. The tie is broken by ranking
points according to their Y coordinate.
Determine two extreme points A and B, where A represents the leftmost
point and B represents the rightmost point. A and B would be the convex
hull’s vertices. Lines AB and BA should be added to the solution set.
Find the point C that is the farthest away from line AB.
Calculate the convex hull of the points on the line AC’s right and left
sides. Remove line AB from the original solution set and replace it with
AC and CB.
Process the points on the right side of line BA in the same way.
Find the convex hull of the points on the left and right of the line
connecting the two farthest points of that specific convex hull
recursively.
Algorithm for finding convex hull using divide and conquer strategy is
provided below:
Finding two farthest points from the sorted list takes O(1) time.
Dividing points into two halves S1 and S2 take O(1) time by joining A and B.
In the average case, S1 and S2 contain half of the points. So, recursively
computing the convex hull of A and B takes T(n/2) each.
Merging of two convex hulls is done in linear time O(n), by finding the
orthogonally farthest point.
= 2T(n/2) + n … (1)
= 22 T(n/22) + 2n
After k substitutions,
Division of array creates binary tree, which has height log2n, so let us consider
that k grows up to log2n,
k = log2n ⇒ n = 2k
T(n) = O(n.log2n)
Problem: Find the convex hull for a given set of points using divide and
conquer approach
Solution: Step 1: According to the algorithm, find left most and rightmost
points from the set P and label them as A and B. Label all the points on the right
of AB as S1 and all the points on the right of BA as S2.
Solution = {AB, BA}
Step 3 : FindHull(X1, A, C)
Find point D orthogonally farthest from line AC
Step 4 : FindHull(X2, C, B)
But X1 and X2 sets are empty, so algorithm returns Now we will explore the
points in S2, on the right-hand side of the line BA
The lowest area rectangle that encloses a polygon has at least one side flush
with the polygon’s convex hull, and hence the hull is computed in the first step
of minimum rectangle methods. Finding the smallest three-dimensional box
enclosing an item is also dependent on the 3D-convex hull.
But X1 and X2 sets are empty, so algorithm returns. And no more recursive calls
are left. So polygon with edges (AD, DC, CE, EB, BF, FG, GA) is the convex hull of
given points.
Smallest Box: Convex hull helps to determine the minimum size required to
put object in in. It is useful to determine box size for the object. Finding the
smallest three-dimensional box enclosing an item is also dependent on the 3D-
convex hull.
Shape Analysis: Convex hull of object is useful to analyze the shape of object.
Topological Sort-
Topological Sort is a linear ordering of the vertices in such a way that if
there is an edge in the DAG going from vertex ‘u’ to vertex ‘v’, then ‘u’ comes
before ‘v’ in the ordering.
It is important to note that-
Topological Sorting is possible if and only if the graph is a Directed
Acyclic Graph.
There may exist multiple different topological orderings for a given
directed acyclic graph.
Problem-01:
Find the number of different topological orderings possible for the given
graph-
Solution-
The topological orderings of the above graph are found in the following
steps-
Step-01:
Step-02:
Step-04:
There are two vertices with the least in-degree. So, following 2 cases are
possible-
In case-01,
Remove vertex-C and its associated edges.
Then, update the in-degree of other vertices.
In case-02,
Remove vertex-D and its associated edges.
Then, update the in-degree of other vertices.
Step-05:
Now, the above two cases are continued separately in the similar manner.
In case-01,
Remove vertex-D since it has the least in-degree.
Then, remove the remaining vertex-E.
In case-02,
Remove vertex-C since it has the least in-degree.
Then, remove the remaining vertex-E.
Conclusion-
Problem-02:
Find the number of different topological orderings possible for the given
graph-
Solution-
The topological orderings of the above graph are found in the following
steps-
Step-01:
Write in-degree of each vertex-
Step-02:
Vertex-1 has the least in-degree.
So, remove vertex-1 and its associated edges.
Now, update the in-degree of other vertices.
Step-03:
There are two vertices with the least in-degree. So, following 2 cases are
possible-
In case-01,
Remove vertex-2 and its associated edges.
Then, update the in-degree of other vertices.
In case-02,
Remove vertex-3 and its associated edges.
Then, update the in-degree of other vertices.
Step-04:
Now, the above two cases are continued separately in the similar manner.
In case-01,
Remove vertex-3 since it has the least in-degree.
Then, update the in-degree of other vertices.
In case-02,
Remove vertex-2 since it has the least in-degree.
Then, update the in-degree of other vertices.
Step-05:
In case-01,
Remove vertex-4 since it has the least in-degree.
Then, update the in-degree of other vertices.
In case-02,
Remove vertex-4 since it has the least in-degree.
Then, update the in-degree of other vertices.
Step-06:
In case-01,
There are 2 vertices with the least in-degree.
So, 2 cases are possible.
Any of the two vertices may be taken first.
Conclusion-
[Link]
for i ← 0 to n − 2 do
if A[i] = A[i + 1]
return false
return true
The running time of this algorithm is the sum of the time spent
on sorting and the time spent on checking consecutive
elements.
Since the former requires at least n log n comparisons and the
latter needs no more than n − 1 comparisons, it is the sorting
part that will determine the overall efficiency of the algorithm.
So, if we use a quadratic sorting algorithm here, the entire
algorithm will not be more
efficient than the brute-force one.
But if we use a good sorting algorithm, such as mergesort, with
worst-case efficiency in ѳ(n log n), the worst-case efficiency of
the entire presorting-based algorithm will be also in ѳ(n log n):
The total cost is
T(n) = Tsort(n) + Tscan(n) ε Θ(n log n) + Θ(n) = Θ(n log n)
The first tree is a heap. The second one is not a heap, because the
tree’s shape property is violated. And the third one is not a heap,
because the parental dominance fails for the node with key 5.
Note that key values in a heap are ordered top down; i.e., a sequence of
values on any path
from the root to a leaf is decreasing (nonincreasing, if equal keys are
allowed).
There is no left-to-right order in key values; i.e., there is no relationship
among key values for nodes either on the same level of the tree or, in
will be
The second is top-down heap construction algorithm
It constructs a heap by successive insertions of a new key into a
previously constructed heap.
To insert a new key K into a heap, First attach a new node with
key K in it after the last leaf of the existing heap. Then sift K up
to its appropriate place in the new heap as follows.
Compare K with its parent’s key: if the latter is greater than or
equal to K, stop (the
structure is a heap); otherwise, swap these two keys and
compare K with its new parent.
This swapping continues until K is not greater than its last
parent or it reaches the root (illustrated in Figure 6.12).
This insertion operation cannot require more key comparisons
than the heap’s height.
Since the height of a heap with n nodes is about log2 n, the time
efficiency of insertion is in O(log n).
Maximum Key Deletion from a heap
Step 1 Exchange the root’s key with the last key K of the heap.
Step 2 Decrease the heap’s size by 1.
Step 3 “Heapify” the smaller tree by sifting K down the tree exactly in
the same way we did it in the bottom-up heap construction algorithm.
That is, verify the parental dominance for K: if it holds, we are done; if
not, swap K with the larger of its children and repeat this operation
until the parental dominance condition holds for K in its new position.
The efficiency of deletion is determined by the number of key
comparisons needed to “heapify” the tree after the swap has been
made and the size of the tree is decreased by 1. Since this cannot
require more key comparisons than twice the heap’s height, the time
efficiency of deletion is in O(log n) as well.
Heapsort
An interesting sorting algorithm discovered by J. W. J. Williams .
This is a two-stagealgorithm that works as follows.
Stage 1 (heap construction): Construct a heap for a given array.
Stage 2 (maximum deletions): Apply the root-deletion operation n − 1
times to the remaining heap.
As a result, the array elements are eliminated in decreasing
order.
But since under the array implementation of heaps an element
being deleted is placed last, the resulting array will be exactly
the original array sorted in increasing order.
What is a heap?
A heap is a complete binary tree, and the binary tree is a tree in which the
node can have the utmost two children. A complete binary tree is a binary
tree in which all the levels except the last level, i.e., leaf node, should be
completely filled, and all the nodes should be left-justified.
Heapsort is the in-place sorting algorithm. Now, let's see the algorithm of
heap sort.
Algorithm
1. HeapSort(arr)
2. BuildMaxHeap(arr)
3. for i = length(arr) to 2
4. swap arr[1] with arr[i]
5. heap_size[arr] = heap_size[arr] ? 1
6. MaxHeapify(arr,1)
7. End
BuildMaxHeap(arr)
BuildMaxHeap(arr)
1. heap_size(arr) = length(arr)
2. for i = length(arr)/2 to 1
3. MaxHeapify(arr,i)
4. End
MaxHeapify(arr,i)
1. MaxHeapify(arr,i)
2. L = left(i)
3. R = right(i)
4. if L ? heap_size[arr] and arr[L] > arr[i]
5. largest = L
6. else
7. largest = i
8. if R ? heap_size[arr] and arr[R] > arr[largest]
9. largest = R
10. if largest != i
11. swap arr[i] with arr[largest]
12. MaxHeapify(arr,largest)
13. End
Now let's see the working of heap sort in detail by using an example. To
understand it more clearly, let's take an unsorted array and try to sort it
using heap sort. It will make the explanation clearer and easier.
First, we have to construct a heap from the given array and convert it into
max heap.
After converting the given heap into max heap, the array elements are -
Next, we have to delete the root element (89) from the max heap. To delete
this node, we have to swap it with the last node, i.e. (11). After deleting the
root element, we again have to heapify it to convert it into max heap.
After swapping the array element 89 with 11, and converting the heap into
max-heap, the elements of array are -
In the next step, again, we have to delete the root element (81) from the
max heap. To delete this node, we have to swap it with the last node,
i.e. (54). After deleting the root element, we again have to heapify it to
convert it into max heap.
After swapping the array element 81 with 54 and converting the heap into
max-heap, the elements of array are -
In the next step, we have to delete the root element (76) from the max heap
again. To delete this node, we have to swap it with the last node,
i.e. (9). After deleting the root element, we again have to heapify it to
convert it into max heap.
After swapping the array element 76 with 9 and converting the heap into
max-heap, the elements of array are -
In the next step, again we have to delete the root element (54) from the
max heap. To delete this node, we have to swap it with the last node,
i.e. (14). After deleting the root element, we again have to heapify it to
convert it into max heap.
After swapping the array element 54 with 14 and converting the heap into
max-heap, the elements of array are -
In the next step, again we have to delete the root element (22) from the
max heap. To delete this node, we have to swap it with the last node,
i.e. (11). After deleting the root element, we again have to heapify it to
convert it into max heap.
After swapping the array element 22 with 11 and converting the heap into
max-heap, the elements of array are -
In the next step, again we have to delete the root element (14) from the
max heap. To delete this node, we have to swap it with the last node,
i.e. (9). After deleting the root element, we again have to heapify it to
convert it into max heap.
After swapping the array element 14 with 9 and converting the heap into
max-heap, the elements of array are -
In the next step, again we have to delete the root element (11) from the
max heap. To delete this node, we have to swap it with the last node,
i.e. (9). After deleting the root element, we again have to heapify it to
convert it into max heap.
After swapping the array element 11 with 9, the elements of array are -
Now, heap has only one element left. After deleting it, heap will be empty.
Implementation of Heapsort
Program: Write a program to implement heap sort in C language.
1. #include <stdio.h>
2. /* function to heapify a subtree. Here 'i' is the
3. index of root node in array a[], and 'n' is the size of heap. */
4. void heapify(int a[], int n, int i)
5. {
6. int largest = i; // Initialize largest as root
7. int left = 2 * i + 1; // left child
8. int right = 2 * i + 2; // right child
9. // If left child is larger than root
10. if (left < n && a[left] > a[largest])
11. largest = left;
12. // If right child is larger than root
13. if (right < n && a[right] > a[largest])
14. largest = right;
15. // If root is not largest
16. if (largest != i) {
17. // swap a[i] with a[largest]
18. int temp = a[i];
19. a[i] = a[largest];
20. a[largest] = temp;
21.
22. heapify(a, n, largest);
23. }
24. }
25. /*Function to implement the heap sort*/
26. void heapSort(int a[], int n)
27. {
28. for (int i = n / 2 - 1; i >= 0; i--)
29. heapify(a, n, i);
30. // One by one extract an element from heap
31. for (int i = n - 1; i >= 0; i--) {
32. /* Move current root element to end*/
33. // swap a[0] with a[i]
34. int temp = a[0];
35. a[0] = a[i];
36. a[i] = temp;
37. heapify(a, i, 0);
38. }
39. }
40. /* function to print the array elements */
41. void printArr(int arr[], int n)
42. {
43. for (int i = 0; i < n; ++i)
44. {
45. printf("%d", arr[i]);
46. printf(" ");
47. }
48. }
49. int main()
50. {
51. int a[] = {48, 10, 23, 43, 28, 26, 1};
52. int n = sizeof(a) / sizeof(a[0]);
53. printf("Before sorting array elements are - \n");
54. printArr(a, n);
55. heapSort(a, n);
56. printf("\nAfter sorting array elements are - \n");
57. printArr(a, n);
58. return 0;
59. }
Output
Divide
If q is the half-way point between p and r, then we can split the
subarray A[p..r] into two arrays A[p..q] and A[q+1, r].
Conquer
In the conquer step, we try to sort both the subarrays A[p..q] and A[q+1, r]. If
we haven't yet reached the base case, we again divide both these subarrays
and try to sort them.
Combine
When the conquer step reaches the base step and we get two sorted
subarrays A[p..q] and A[q+1, r] for array A[p..r], we combine the results by
creating a sorted array A[p..r] from two sorted subarrays A[p..q] and A[q+1,
r].
Binary Search
What is Search?
Search is a process of finding a value in a list of values. In other words,
searching is the process of locating given value position in a list of values.
Binary search is an efficient algorithm for finding an item from a sorted list of
items. It works by repeatedly dividing in half the portion of the list that could
contain the item, until you’ve narrowed down the possible locations to just one.
The binary search algorithm can be used with only a sorted list of elements.
That means the binary search is used only with a list of elements that are
already arranged in an order.
The binary search cannot be used for a list of elements arranged in random
order.
This search process starts comparing the search element with the middle
element in the list. If both are matched, then the result is "element found".
If the search element is smaller, then we repeat the same process for the
left sublist of the middle element.
If the search element is larger, then we repeat the same process for the
right sublist of the middle element.
We repeat this process until we find the search element in the list or until
we left with a sublist of only one element. And if that element also doesn't
match with the search element, then the result is "Element not found in the
list".
Binary search algorithm finds a given element in a list of elements
with O(log n) time complexity where n is total number of elements in the
list.
Example 1
To understand the working of the Binary search algorithm, let's take a sorted
array. It will be easy to understand the working of Binary search with an
example.
o Iterative method
o Recursive method
The recursive methods of binary search follow the divide and conquer
approach.
We have to use the below formula to calculate the mid of the array -
beg = 0
end = 8
Now, the element to search is found. So algorithm will return the index of the
element matched.
Implementation of Binary Search Algorithm using C Programming Language
#include<stdio.h>
#include<conio.h>
void main()
{
int first, last, middle, size, i, s Element, list[100];
clrscr ( );
printf("Enter the size of the list: ");
scanf("%d",&size);
printf("Enter %d integer values in Ascending order\n", size);
for (i = 0; i < size; i++)
scanf("%d",&list[i]);
printf("Enter value to be search: ");
scanf("%d", &sElement);
first = 0;
last = size - 1;
middle = (first+last)/2;
while (first <= last) {
if (list[middle] < sElement)
first = middle + 1;
else if (list[middle] == sElement)
{
printf("Element found at index %d.\n",middle);
break;
}
else
last = middle - 1;
middle = (first + last)/2;
}
if (first > last)
printf("Element Not found in the list.");
getch();
}
Complexity Analysis of Binary Search
Best Case:
In binary search, the key is initially compared to the array’s middle element. If
the key is in the center of the array, the algorithm only does one comparison,
regardless of the size of the array. As a result, the algorithm’s best-case
running time is T(n) = 1.
Worst Case:
Every iteration the binary search, search space is decreased by half, allowing
for maximum log2n array divisions. If the key is at the leaf of the tree or it is
not present at all, then the algorithm does log2n comparisons, which is
maximum. The number of comparisons increases in logarithmic proportion
to the amount of the input. As a result, the algorithm’s worst-case running
time would be T(n) = O(log2 n).
The problem size is reduced by a factor of two after each iteration, and the
method does one comparison.
Solution to this recurrence leads to same running time, i.e. O(log2n). Detail
derivation is discussed here:
In every iteration, the binary search does one comparison and creates a new
problem of size n/2. So recurrence equation of binary search is given as,
T(n) = 1, if n = 1
Only one comparison is needed when there is only one element in the array.
That’s the trivial case. This is the boundary condition for recurrence. Let us
solve this by iterative approach,
T(n/4) = T(n/8) + 1
T(n) = T(n/23) + 3
After k iterations,
T(n) = T(nk) + k
Binary tree created by binary search can have maximum height log2n
So, k = log2n ⇒ n = 2k
T(n) = T(2k/2k) + k
= T(1) + k
T(n) = 1 + k = 1 + log2n
T(n) = O(log2n)
Average Case:
The average case for binary search occurs when the key element is neither in
the middle nor at the leaf level of the search tree. On average, it does half of
the log2 n comparisons, which will turn out as T (n) = O(log2 n).
The complexity of linear search and binary search for all three cases is
compared in the following table.
In the best case, the element is In the best case, the element is in
in the first position of the array. the middle of the array.
Merge sort is the sorting technique that follows the divide and conquers
approach. Merge sort is similar to the quick sort algorithm as it uses the
divide and conquers approach to sort the elements.
It is one of the most popular and efficient sorting algorithm. It divides
the given list into two equal halves, calls itself for the two halves and
then merges the two sorted halves.
The sub-lists are divided again and again into halves until the list cannot
be divided further.
Then we combine the pair of one element lists into two-element lists,
sorting them in the process.
The sorted two-element pairs are merged into the four-element lists,
and so on until we get the sorted list.
Algorithm
In the following algorithm, arr is the given array, beg is the starting element,
and end is the last element of the array.
The important part of the merge sort is the MERGE function. This function
performs the merging of two sorted sub-arrays that
are A[beg…mid] and A[mid+1…end], to build one sorted array A[beg…end].
So, the inputs of the MERGE function are A[ ], beg, mid, and end.
To understand the working of the merge sort algorithm, let's take an unsorted
array. It will be easier to understand the merge sort via an example1.
According to the merge sort, first divide the given array into two equal halves.
Merge sort keeps dividing the list into equal parts until it cannot be further
divided.
As there are eight elements in the given array, so it is divided into two arrays
of size 4.
Now, again divide these two arrays into halves. As they are of size 4, so divide
them into new arrays of size 2.
Now, again divide these arrays to get the atomic value that cannot be further
divided.
Now, combine them in the same manner they were broken.
In combining, first compare the element of each array and then combine them
into another array in sorted order.
So, first compare 12 and 31, both are in sorted positions. Then compare 25
and 8, and in the list of two values, put 8 first followed by 25.
Then compare 32 and 17, sort them and put 17 first followed by 32. After that,
compare 40 and 42, and place them sequentially.
In the next iteration of combining, now compare the arrays with two data
values and merge them into an array of found values in sorted order.
Now, there is a final merging of the arrays. After the final merging of above
arrays, the array will look like -
Complexity Analysis
On each recursive call, the list is divided into two sublists, and problem
size reduces by half. After sorting two sublists of size (n / 2), combine
procedure takes n comparisons to form the sorted list of size n.
T(n) = 0, if n = 1
D(n) is cost of division, which is constant, i.e. O(1) as the list splitting is
done simply by computing the middle of the array
C(n) is cost of conquer, which is linear, i.e. O(n) as two sub lists each of size
n/2 are merged into bigger sorted array of size n with the help of
maximum n comparisons
Substitution method
Master method
Substitution method:
= 22 T(n/22) + 2n
.
After k substitutions,
Division of array creates binary tree, which has height log2n, so let us consider
that k grows up to log2n,
k = log2n ⇒ n = 2k
Master method:
T(n) = O(ndlog2n)
= O(nlog2n)
Whether the list is already sorted, inverse sorted or randomly shuffled, all
three steps must be performed.
Merge sort cannot detect if the list is sorted. So numbers of comparisons are
the same for all three cases.
Time Complexity of Merge Sort
The quick sort algorithm attempts to separate the list of elements into two
parts and then sort each part recursively. That means it use divide and
conquer strategy.
In quick sort, the partition of the list is performed based on the element
called pivot. Here pivot element is one of the elements in the list.
The list is divided into two partitions such that "all elements to the left of
pivot are smaller than the pivot and all elements to the right of pivot are
greater than or equal to the pivot".
Step 1 - Consider the first element of the list as pivot (i.e., Element at
first position in the list).
Step 2 - Define two variables i and j. Set i and j to first and last elements
of the list respectively.
Step 3 - Increment i until list[i] > pivot then stop.
Step 4 - Decrement j until list[j] < pivot then stop.
Step 5 - If i < j then exchange list[i] and list[j].
Step 6 - Repeat steps 3,4 & 5 until i > j.
Step 7 - Exchange the pivot element with list[j] element.
To sort an unsorted list with 'n' number of elements, need to make ((n-1)+(n-
2)+(n-3)+......+1) = (n (n-1))/2 number of comparisons in the worst case.
To find the location of an element that splits the array into two parts, O(n)
operations are required.
This is because every element in the array is compared to the partitioning
element.
After the division, each section is examined separately.
If the array is split approximately in half (which is not usually), then there
will be log2n splits.
T(n) = 0, if n = 1
D(n) is cost of division, which is constant, i.e. O(1) as the list splitting is
done simply by computing the middle of the array
C(n) is cost of conquer, which is linear, i.e. O(n) as two sub lists each of size
n/2 are merged into bigger sorted array of size n with the help of
maximum n comparisons
We will derive the time complexity of merge sort using two methods:
Substitution method
Master method
Substitution method:
= 22 T(n/22) + 2n
After k substitutions,
k = log2n ⇒ n = 2k
Worst Case-