NIE Institute of Technology Department of Computer Science & Engg And Department of Information science & Engg ALGORITHMS LABORATORY MANUAL For 5 th SEM CSE & ISE Sub Code: 06CSL58 Introduction Analysis of algorithms Analysis is one of the important step in problem solving. Here we estimate time and space for a given problem. Once we have these estimations, select an algorithm which is more efficient in terms of time and space. Time Efficiency: Indicates how fast the algorithm runs. Space Efficiency: Indicates how much extra memory is required for the algorithm .
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
NIE Institute of Technology
Department of Computer Science & EnggAnd
Department of Information science & Engg
ALGORITHMS LABORATORY MANUALFor 5th SEM CSE & ISESub Code: 06CSL58
Introduction
Analysis of algorithms
Analysis is one of the important step in problem solving. Here we estimate time and
space for a given problem. Once we have these estimations, select an algorithm which is more
efficient in terms of time and space.
Time Efficiency: Indicates how fast the algorithm runs.
Space Efficiency: Indicates how much extra memory is required for the algorithm .
The most important characteristics of any algorithm are it’s simplicity and generality.
Simplicity: Simple algorithms are easy to understand, debug and easy to program.
Generality: It should be a generic solution to the given problem.
Design an algorithm
After the problem is clearly stated and the type of algorithm and data structure are
selected, the solution to solve the problem should be specified. One or more algorithms may be
correct to solve a given problem. But effectiveness may be different from solution to solution.
Algorithm
An algorithm is a technique to solve a problem systematically. It takes a set of input and
produces a desired output. Now algorithm can be defined as an unambiguous an ordered
sequence of steps that are carried to get a solution to a problem. Pictorial representation of an
algorithm is as shown below .
Notion of an algorithm
Linear Search
In computer science, linear search is a search algorithm, also known as sequential
search, that is suitable for searching a list of data for a particular value.
It operates by checking every element of a list one at a time in sequence until a match is
found. Linear search runs in O(n). If the data are distributed randomly, the expected number of
comparisons that will be necessary is: where n is the number of elements in the list and k is the
number of times that the value being searched for appears in the list. The best case is that the
value is equal to the first element tested, in which case only 1 comparison is needed. The worst
case is that the value is not in the list (or it appears only once at the end of the list), in which case
n comparisons are needed.
The simplicity of the linear search means that if just a few elements are to be searched it
is less trouble than more complex methods that require preparation such as sorting the list to be
searched or more complex data structures, especially when entries may be subject to frequent
revision. Another possibility is when certain values are much more likely to be searched for than
others and it can be arranged that such values will be amongst the first considered in the list.
The following pseudo code describes the linear search technique.
For each item in the list:
Check to see if the item you're looking for matches the item in the list.
If it matches.
Return the location where you found it (the index).
If it does not match.
Continue searching until you reach the end of the list.
If we get here, we know the item does not exist in the list. Return -1.
Binary Search Algorithm (or binary chop)
Binary search is a technique for locating a particular value in a sorted list. The method
makes progressively better guesses, and closes in on the location of the sought value by selecting
the middle element in the span (which, because the list is in sorted order, is the median value),
comparing its value to the target value, and determining if it is greater than, less than, or equal to
the target value. A guessed index whose value turns out to be too high becomes the new upper
bound of the span, and if its value is too low that index becomes the new lower bound. A binary
search is an example of a divide and conquer search algorithm.
The following pseudo code describes the Binary search technique
• Implement Recursive Binary search and Linear search and determine the time required to search an element. Repeat the experiment for different values of n, the number of elements in the list to be searched and plot a graph of the time taken versus n.
Heap Sort
Heapsort (method) is a comparison-based sorting algorithm, and is part of the selection
sort family. The heap sort works as its name suggests - it begins by building a heap out of the
data set, and then removing the largest item and placing it at the end of the sorted array. After
removing the largest item, it reconstructs the heap and removes the largest remaining item and
places it in the next open position from the end of the sorted array. This is repeated until there are
no items left in the heap and the sorted array is full. Elementary implementations require two
arrays - one to hold the heap and the other to hold the sorted elements.
Heapsort uses two heap operations: insertion and root deletion. Each extraction places an
element in the last empty location of the array. The remaining prefix of the array stores the
unsorted elements.
• Sort a given set of elements using the Heap sort method and determine the time required to sort the elements. Repeat the experiment for different values of n, the number of elements in the list to be sorted and plot a graph of the time taken versus n.
public: void heapify(int a[], int n);void heapsort(int a[],int n);
};
void heap::heapify(int a[],int n){
int i,j,v,heap,k;for(i=n/2;i>0;i--){
k=i;v=a[i];
heap=0;while(!heap&&(2*k)<=n){
j=2*k;if(j<n){
if(a[j]<a[j+1]){
j=j+1;}
}if(v>=a[j])
heap=1;else{
a[k]=a[j];k=j;
}}a[k]=v;
}}
void heap::heapsort(int a[],int n){
int temp,i,j; heapify(a,n); for(i=1;i<=n;i++) {
for(j=i+1;j<=n;j++){
if(a[i]>a[j]){
temp=a[i];a[i]=a[j];a[j]=temp;
}}
}}
void main(){
clock_t st, et;heap h;clrscr();cout<<"Enter the value of N:\n"<<endl;cin>>n;cout<<"Enter the array element:\n"<<endl;for(i=1;i<=n;i++)//cin>>a[i];a[i]=rand()%100;
cout<<"The array element genearted are:\n"<<endl;for(i=1;i<=n;i++)cout<<"\t"<<a[i];
cout<<"\nThe sorted elements are:\n"<<endl;for(i=1;i<=n;i++)cout<<"\t"<<a[i];
cout<<"\nBegin time="<<st<<endl;cout<<"End time="<<et<<endl;cout<<"Total number of clock ticks="<<(et-st)<<endl;cout<<"Total time required="<<(et-st)/CLK_TCK<<endl;
getch();}
Merge sort
Merge sort is an O(n log n) comparison-based sorting algorithm. In most implementations it is
stable, meaning that it preserves the input order of equal elements in the sorted output. It is an
example of the divide and conquer algorithmic paradigm. It was invented by John von Neumann
in 1945.
Conceptually, a merge sort works as follows:
• If the list is of length 0 or 1, then it is already sorted. Otherwise:
• Divide the unsorted list into two sublists of about half the size.
• Sort each sublist recursively by re-applying merge sort.
• Merge the two sublists back into one sorted list.
Merge sort incorporates two main ideas to improve its runtime:
• A small list will take fewer steps to sort than a large list.
• Fewer steps are required to construct a sorted list from two sorted lists than two unsorted
lists. For example, you only have to traverse each list once if they're already sorted (see
the merge function below for an example implementation).
Example: Using merge sort to sort a list of integers contained in an array:
Suppose we have an array A with n indices ranging from A0 to An − 1. We apply merge sort to
A(A0..Ac − 1) and A(Ac..An − 1) where c is the integer part of n / 2. When the two halves are
returned they will have been sorted. They can now be merged together to form a sorted array.
• Sort a given set of elements using Merge sort method and determine the time required to sort the elements. Repeat the experiment for different values of n, the number of elements in the list to be sorted and plot a graph of the time taken versus n.
cout<<"\nBegin time="<<st<<endl;cout<<"End time="<<et<<endl;cout<<"Total number of clock ticks="<<(et-st)<<endl;cout<<"Total time required="<<(et-st)/CLK_TCK<<endl;
getch();}
Selection sort
Selection sort is a sorting algorithm, specifically an in-place comparison sort. It has O(n2)
complexity, making it inefficient on large lists, and generally performs worse than the similar
insertion sort. Selection sort is noted for its simplicity, and also has performance advantages over
more complicated algorithms in certain situations.
The algorithm works as follows:
• Find the minimum value in the list
• Swap it with the value in the first position
• Repeat the steps above for the remainder of the list (starting at the second position and
advancing each time)
Effectively, we divide the list into two parts: the sublist of items already sorted, which we build
up from left to right and is found at the beginning, and the sublist of items remaining to be
sorted, occupying the remainder of the array.
• a Sort a given set of elements using Selection sort and determine the time required to sort elements. Repeat the experiment for different values of n, the number of elements in the list to be sorted and plot a graph of the time taken versus n.
cout<<"\nBegin time="<<st<<endl;cout<<"End time="<<et<<endl;cout<<"Total number of clock ticks="<<(et-st)<<endl;cout<<"Total time required="<<(et-st)/CLK_TCK<<endl;
getch();}
The knapsack problem or rucksack problem
The knap sack problem is a problem in combinatorial optimization: Given a set of items,
each with a weight and a value, determine the number of each item to include in a collection so
that the total weight is less than a given limit and the total value is as large as possible. It derives
its name from the problem faced by someone who is constrained by a fixed-size knapsack and
must fill it with the most useful items.
The problem often arises in resource allocation with financial constraints. A similar
problem also appears in combinatorics, complexity theory, cryptography and applied
mathematics.
0-1 knapsack problem
A dynamic programming solution for the 0-1 knapsack problem also runs in pseudo-
polynomial time. Assume w1, ..., wn, W are strictly positive integers. Define A(j, Y) to be the
maximum value that can be attained with weight less than or equal to Y using items up to j.
We can define A(j,Y) recursively as follows:
• A(0, Y) = 0
• A(j, 0) = 0
• A(j, Y) = A(j − 1, Y) if wj > Y
• A(j, Y) = max { A(j − 1, Y), pj + A(j − 1, Y − wj) } if wj ≤ Y.
The solution can then be found by calculating A(n, W). To do this efficiently we can use a table
to store previous computations. This solution will therefore run in O(nW) time and O(nW) space,
though with some slight modifications we can reduce the space complexity to O(W).
• Implement 0/1 Knapsack problem using dynamic programming
cout<<"\n Enter the number of nodes:";cin>>n;cout<<"\n Enter the cost matrix:\n";cout<<"\nEnter 0->diagonal elements and 99->No direct edge cost\n";for(i=1;i<=n;i++){
Enter the number of nodes: 3Enter the cost matrix:Enter 0->diagonal elements and 99->No direct edge cost:0 10 3020 0 1520 5 0Enter the source vertex: 1PATH DISTANCE(1,1)---------> 0(1,2)---------> 10(1,3)---------> 25----------------------------------------------------------------------*/
Quick Sort
Quicksort is a well-known sorting algorithm developed by C. A. R. Hoare that, on
average, makes Θ(nlogn) (big O notation) comparisons to sort n items. However, in the worst
case, it makes Θ(n2) comparisons. Typically, quicksort is significantly faster in practice than
other Θ(nlogn) algorithms, because its inner loop can be efficiently implemented on most
architectures, and in most real-world data, it is possible to make design choices which minimize
the probability of requiring quadratic time.
Quicksort is a comparison sort and, in efficient implementations, is not a stable sort.
Quicksort sorts by employing a divide and conquer strategy to divide a list into two sub-lists.
Full example of quicksort on a random set of numbers. The boxed element is the pivot. It is
always chosen as the last element of the partition.
The steps are:
• Pick an element, called a pivot, from the list.
• Reorder the list so that all elements which are less than the pivot come before the pivot
and so that all elements greater than the pivot come after it (equal values can go either
way). After this partitioning, the pivot is in its final position. This is called the partition
operation.
• Recursively sort the sub-list of lesser elements and the sub-list of greater elements.
The base case of the recursion are lists of size zero or one, which are always sorted. In simple
pseudocode, the algorithm might be expressed as this:
function quicksort(array)
var list less, greater
if length(array) ≤ 1
return array
select and remove a pivot value pivot from array
for each x in array if x ≤ pivot then append x to less else append x to greater return concatenate(quicksort(less), pivot, quicksort(greater))
• Sort a given set of elements using Quick sort method and determine the time required sort the elements. Repeat the experiment for different values of n, the number of elements in the list to be sorted and plot a graph of the time taken versus n.
void main(){ int n; clock_t s,e; clrscr(); cout<<"\nEnter the no. of elements:\n "; cin>>n; s=clock(); quicksort a1(n); cout<<"\nEnter the elements of the array:\n"; a1.getarray(); cout<<"\nEntered elements of the array:\n"; a1.putarray(); cout<<"\nThe sorted array is:\n"; a1.qsort(0,n-1); e=clock(); a1.putarray(); cout<<"\nTime taken for sort\n"<<(e-s)/CLK_TCK; getch();}
Kruskal’s Algorithm
Kruskal's algorithm is an algorithm in graph theory that finds a minimum spanning tree for a
connected weighted graph. This means it finds a subset of the edges that forms a tree that
includes every vertex, where the total weight of all the edges in the tree is minimized. If the
graph is not connected, then it finds a minimum spanning forest (a minimum spanning tree for
each connected component). Kruskal's algorithm is an example of a greedy algorithm. This
algorithm first appeared in Proceedings of the American Mathematical Society, pp. 48–50 in
1956, and was written by Joseph Kruskal
It works as follows:
• create a forest F (a set of trees), where each vertex in the graph is a separate tree
• create a set S containing all the edges in the graph
• while S is nonempty
• remove an edge with minimum weight from S
• if that edge connects two different trees, then add it to the forest, combining two
trees into a single tree
• otherwise discard that edge.
At the termination of the algorithm, the forest has only one component and forms a minimum
spanning tree of the graph.
• Find Minimum Cost Spanning Tree of a given undirected graph using Kruskal’s algorithm.
#include<iostream.h>#include<conio.h>
int cost[10][10],root[10],min,edge=1,mincost=0;int i,j,a,b,u,v,n;
class MCST{
public: void kruskal();};
void MCST::kruskal(){
cout<<"Spanning Trees is:\n";while(edge<n){ for(i=1,min=999;i<=n;i++)
Backtracking is a refinement of the brute force approach, which systematically searches
for a solution to a problem among all available options. It does so by assuming that the solutions
are represented by vectors (v1, ..., vm) of values and by traversing, in a depth first manner, the
domains of the vectors until the solutions are found.
The objective of this problem is to find as many subsets as possible whose individual sum
equals some constant value.
• Find a subset of a given set S = {sl,s2,.....,sn} of n positive integers whose sum is equal to a given positive integer d. For example, if S= {1, 2, 5, 6, 8} and d = 9 there are two solutions{1,2,6}and{1,8}.A suitable message is to be displayed if the given problem instance doesn't have a solution.
int i,k,m,n;m=strlen(p);n=strlen(t);shift_table(p,m);i=m-1;while(i<=n-1){
k=0;while(k<=m-1 && p[m-1-k]==t[i-k])k++;if(k==m)
return i-m+1;elsei+=table[t[i]];
}return -1;
}
void horse::read(){
cout<<"\n Enter the text:\n\t";gets(t);cout<<"\n Enter the pattern:\n\t";gets(p);
flag=horspool(t,p);
if(flag==-1)cout<<"\n Pattern string not found";elsecout<<"\n Pattern string found at position= "<<flag+1<<"\n";getch();
}
void main(){
clrscr();horse h;h.read();
}****************************************************************************** /* OUTPUT Run 1: Enter the text:
ferrari rules
Enter the pattern: fiat
No match
Run 2: Enter the text:
jim_saw_me_in_the_barber_shop
Enter the pattern: barber
Match found at position 19 */*******************************************************************************
Binomial coefficient
The Binomial coefficients C(n, k) is the number of ways of choosing a subset of k
elements from a set of n elements. It arises in probability and statistics, e.g., the
probability of flipping a biased coin (with probability p of heads) n times and getting
exactly k heads is C(n, k) pk (1-p)k. One formula for computing binomial coefficients is
C(n, k) = n! / (k! (n-k)!). This formula is not so amenable to direct computation because
the intermediate results may overflow, even if the final answer does not. For example
C(100, 15) = 253338471349988640 fits in a 64-bit long, but the binary representation of
100! is 525 bits long. Pascal's identity expresses C(n, k) in terms of smaller binomial
coefficients:
To understand why Pascal's identity is valid, consider some arbitrary element x. To choose k of n elements, we either select element x (in which case we still need to choose k-1 of the remaining n-1 elements), or we don't select element x (in which case we still need to choose k of the remaining n-1 elements).
The code for calculating binomial coefficients is shown below
long binomial(int n, int k)
{
if (k == 0) return 1;
if (n == 0) return 0;
return binomial(n-1, k) + binomial(n-1, k-1);
b. Find the Binomial Co-efficient using Dynamic Programming.
#include<iostream.h>#include<conio.h>
int c[20][20],n,k;
class bino{ public: int min(int a,int b);
void binomial(int n,int k); void bino::read();
};
int bino::min(int a,int b){
if(a<b)return a;
elsereturn b;
}
void bino::binomial(int n,int k){
int i,j;cout<<"\n The table of co-efficients are...\n";for(i=0;i<=n;i++){
for(j=0;j<=min(i,k);j++){
if(j==0 || j==i)c[i][j]=1;
elsec[i][j]=c[i-1][j]+c[i-1][j-1];
// cout<<"\t"<<c[i][j];}cout<<"\n";
}cout<<"\n The binomial co-efficient = "<<c[n][k];
}
void bino::read(){
cout<<"\n Enter n:";cin>>n;cout<<"\n Enter k:";cin>>k;if(k<0 || n<0 || n<k)
cout<<"\n Invalid entry";else
binomial(n,k);getch();
}
void main(){
clrscr();bino b;b.read();
}******************************************************************************* /* OUTPUT RUN 1: Enter n:5
Prim's algorithm is an algorithm in graph theory that finds a minimum spanning tree for
a connected weighted graph. This means it finds a subset of the edges that forms a tree that
includes every vertex, where the total weight of all the edges in the tree is minimized. The
algorithm was developed in 1930 by Czech mathematician Vojtěch Jarník and later
independently by computer scientist Robert C. Prim in 1957 and rediscovered by Edsger Dijkstra
in 1959. Therefore it is sometimes called the DJP algorithm, the Jarník algorithm, or the
Prim-Jarník algorithm.
The algorithm continuously increases the size of a tree starting with a single vertex until it spans
all the vertices.
• Input: A connected weighted graph with vertices V and edges E.
• Initialize: Vnew = {x}, where x is an arbitrary node (starting point) from V, Enew = {}
• Repeat until Vnew = V:
• Choose edge (u,v) with minimal weight such that u is in Vnew and v is not (if there
are multiple edges with the same weight, choose arbitrarily but consistently)
• Add v to Vnew, add (u, v) to Enew
• Output: Vnew and Enew describe a minimal spanning tree
• Find Minimum Cost Spanning Tree of a given undirected graph using Prim’s algorithm.
#include<iostream.h>#include<conio.h>
int i,j,count=1,source,curr,cost[10][10],r[10],s[10],sum=0;int visit[100],d[100],n;
class prims{
public: void read(); int min(int a,int b); int getcur(); void MCST();
};int prims::min(int a,int b){
if(a<=b)return a;
elsereturn b;
}
int prims::getcur(){
int i=1,curr;while(visit[i]==1)
i++;curr=i;for(i=curr+1;i<=n;i++)
if(d[i]<d[curr] && visit[i]==0)curr=i;
return curr;}
void prims::read(){
cout<<"\n Enter the number of nodes:";cin>>n;cout<<"\n Enter the cost matrix:\n";cout<<"\nEnter 0->diagonal elements and 99->No direct edge cost\n";for(i=1;i<=n;i++){
Enter the number of nodes: 5Enter the cost matrix:Enter 0->diagonal elements and 99->No direct edge cost0 1 3 99 51 0 4 6 993 4 0 2 699 6 2 0 995 99 6 99 0
tclose t;clrscr();cout<<"\n enter the no. of nodes:";cin>>n;cout<<"enter the adjacent matrix:";t.read(n,a);t.warshall(n,a,p);cout<<"The Transitive closure(path matrix)\n";t.write(n,p);getch();
public: int min(int a , int b); void floyd(int n, int cost[10][10], int d[10][10]); void read(int n, int a[10][10]); void write(int n, int a[10][10]);
};
int floy::min(int a , int b){
return a<b?a:b;}
void floy::floyd(int n, int cost[10][10], int d[10][10]){
for(i=0;i<n;i++)for(j=0;j<n;j++)
d[i][j]=cost[i][j];
for(k=0;k<n;k++){
for(i=0;i<n;i++){
for(j=0;j<n;j++){
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);}
}}
}
void floy::read(int n, int a[10][10]){
for(i=0;i<n;i++) for(j=0;j<n;j++)
cin>>a[i][j];}
void floy::write(int n, int a[10][10]){ for(i=0;i<n;i++)
{ for(j=0;j<n;j++) {
cout<<"\t"<<a[i][j]; } cout<<"\n";
}}
void main(){
clrscr();floy f;cout<<"Enter the number of nodes:\n";cin>>n;cout<<"\nEnter the cost adjacency matrix:\n";cout<<"Enter0->Self loop 99->No direct Edge cost:\n\n";f.read(n,cost);f.floyd(n,cost,d);cout<<"\nSolution to all pairs shortest path problem is:\n";f.write(n,d);getch();