HMR INSTITUTE OF TECHNOLOGY & MANAGEMENT Hamidpur, Delhi-110036 (An iso 9001:2008 certified, AICTE approved & GGSIP university affiliated institute) ALGORITHM DESIGN AND ANALYSIS LAB Submitted To: Submitted By: Mr D K Mishra Nitin Tyagi Assistant Professor (41113303118) (IT Department) (Btech(IT)) HMR INSTITUTE OF TECHNOLOGY AND MANAGEMENT HAMIDPUR, DELHI 110036 Affiliated to GURU GOBIND SINGH INDRAPRASTHA UNIVERSITY Sector - 16C Dwarka, Delhi - 110075, India 2018-2022 HMR INSTITUTE OF TECHNOLOGY & MANAGEMENT Hamidpur, Delhi-110036 (An iso 9001:2008 certified, AICTE approved & GGSIP university affiliated institute) INDEX S.No. 1 Experiment Description A. Find the minimum element from the given array containing 5 elements. Page Experiment Submission Remarks No. Date Date 1 21-08-2020 28-08-2020 WAP of Bubble sort and analyse its complexity. 5 28-08-2020 04-09-2020 WAP of Selection sort and analyse its complexity. 8 04-09-2020 11-09-2020 WAP of Merge sort and analyse its complexity. 11 11-09-2020 18-09-2020 WAP of quick sort and analyse its complexity 14 18-09-2020 23-10-2020 WAP to implement Strassen’s Algorithm for matrix multiplication and analyze its time complexity. 18 23-10-2020 06-11-2020 WAP To implement the Longest Common Subsequence problem and analyse its time complexity. 22 06-11-2020 27-11-2020 WAP to imp[lement Dijkstra’s algorithm and analyse its complexity 26 27-11-2020 04-12-2020 B. Arrange the given three numbers in ascending order. 2 3 4 5 6 7 8 Experiment - 1(a) Aim :Find the minimum element from the given array containing 5 elements. DESCRIPTION AND ALGORITHM :In this problem firstly, we have to take 5 elements of an array from the user as an input. After that, by using a certain algorithm, we have to find the minimum element from the array elements given by the user. Following are the steps involved in this problem: 1. 2. 3. 4. 5. Declare an array of size 5 Take input from user to fill array elements Declare a min variable and initialize it with the first element of the array. Repeat step 4 till the whole array is traversed. Check if any element of the array is smaller than min , if yes then initialize min with that value. 6. Print the value of the min variable. ANALYSIS :Time complexity of a given problem is O(1) (order of 1) as the value of n(size of an array) is fixed i.e constant . Program :#include<iostream> using namespace std; int find_min(int a[],int n){ int MIN=a[0]; for(int i=1;i<n;i++) if(a[i]<MIN) MIN=a[i]; return MIN; } int main(){ int a[5]; cout<<"enter 5 elements of an array"<<endl; for(int i=0;i<5;i++) cin>>a[i]; cout<<"minimum element of an array is "<<find_min(a,5); return 0; } Output :- Experiment - 1(b) Aim :Arrange the given three numbers in ascending order. DESCRIPTION AND ALGORITHM :In this problem firstly, we have to take 3 elements of an array from the user as an input. After that, by using a certain algorithm, we have to arrange those elements in the ascending order. Following are the steps involved in this problem: 1. 2. 3. 4. Declare an array of size 3. Take input from the user to fill array elements. Traverse the whole array to arrange every element in ascending order. Print the value of elements in the array. ANALYSIS :. Time complexity of a given problem is O(1) (order of 1) as the value of n(size of an array) is fixed i.e constant Program :#include<iostream> using namespace std; void arrange(int a[],int n){ for(int i=0;i<n;++i) for(int j=i+1;j<n;++j) if(a[i]>a[j]) a[i]=a[i]+a[j]-(a[j]=a[i]); } int main(){ int a[3]; cout<<"enter three elements of an array"<<endl; for(int i=0;i<3;i++) cin>>a[i]; arrange(a,3); cout<<"elements in ascending orders are"<<endl; for(int i=0;i<3;i++) cout<<a[i]<<" "; return 0; } Output :- Experiment - 2 Aim :To implement following sorting algorithms and analyse its time complexity (i) Bubble Sort (ii) Selection Sort (iii) Merge Sort (iv) Quick Sort (v) Insertion Sort DESCRIPTION AND ALGORITHM :Bubble sort is a simple sorting algorithm. This sorting algorithm is a 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 Ο(n2) where n is the number of items. Following are the steps involved in bubble sort(for sorting a given array in ascending order): 1. Starting with the first element(index = 0), compare the current element with the next element of the array. 2. If the current element is greater than the next element of the array, swap them. 3. If the current element is less than the next element, move to the next element. Repeat Step 1. To optimize our bubble sort algorithm, we can introduce a flag to monitor whether elements are getting swapped inside the inner for loop. Hence, in the inner for loop, we check whether swapping of elements is taking place or not, everytime. If for a particular iteration, no swapping took place, it means the array has been sorted and we can jump out of the for loop, instead of executing all the iterations. ANALYSIS :Here, the numbers of comparisons are 1 + 2 + 3 +...+ (n - 1) = n(n - 1)/2 = O(n^2) In this algorithm, the number of comparison is irrespective of the data set, i.e. whether the provided input elements are in sorted order or in reverse order or at random. ● ● ● Worst Case Time Complexity [ Big-O ]: Best Case Time Complexity [Big-omega]: Average Time Complexity [Big-theta]: Program :#include <iostream> O(n^2) O(n) O(n^2) using namespace std; int bubblesort(int arr[],int n){ int i,j; for (i=0;i<n-1;i++){ int flag=0; for(j=0;j<n-i-1;j++) { if(arr[j]>arr[j+1]) { int temp=arr[j]; arr[j]=arr[j+1]; arr[j+1]= temp; flag=1; } } if(flag==0){ break; } } return 0; } int main() { int n; cout<<"enter a size of an array"<<endl; cin>>n; int arr[10]; cout<<"enter an array elements"<<endl; for(int i=0;i<n;i++){ cin>>arr[i]; } cout<<"Array before bubble sort: "<<endl; for(int i=0;i<n;i++){ cout<<arr[i]<<" "; } int ans=bubblesort(arr,n); cout<<endl<<"Array after bubble sort: "<<endl; for(int i=0;i<n;i++){ cout<<arr[i]<<" "; } } Output :- Selection sort DESCRIPTION AND ALGORITHM :Selection sort is conceptually the most simplest sorting algorithm. This algorithm will first find the smallest element in the array and swap it with the element in the first position, then it will find the second smallest element and swap it with the element in the second position, and it will keep on doing this until the entire array is sorted. It is called selection sort because it repeatedly selects the next-smallest element and swaps it into the right place Following are the steps involved in selection sort(for sorting a given array in ascending order): 1. Starting from the first element, we search the smallest element in the array, and replace it with the element in the first position. 2. We then move on to the second position, and look for the smallest element present in the subarray, starting from index 1, till the last index. 3. We replace the element at the second position in the original array, or we can say at the first position in the subarray, with the second smallest element. 4. This is repeated, until the array is completely sorted. ANALYSIS :First loop :- T ( n ) = T ( n - 1 )+ n Second loop :- T ( n ) = T ( n - 1 )+ n T (n )= 2 [T ( n - 1 )+ n ] T(n) = T(n-1) + n T(n-1) = T(n-2) + n T(n-2) = T(n-3) + n … T(n) = T(n-3) + n + n + n So by back substitution, we get n + n + n + n + … - a total of n times Hence the resultant complexity is O(n^2) ● ● ● Best case :- Ω(n^2) Average Case :- θ(n^2) Worst Case :- O(n^2) Program :#include <iostream> using namespace std; void swap(int *xp, int *yp) { int temp = *xp; *xp = *yp; *yp = temp; } void selectionSort(int arr[], int n) { int i, j, min; for (i = 0; i < n-1; i++) { min = i; for (j = i+1; j < n; j++) if (arr[j] < arr[min]) min = j; swap(&arr[min], &arr[i]); } } int main() { int a; cout<<"enter size of an array"<<endl; cin>>a; int arr[10]; cout<<"enter elements of an array"<<endl; for(int i=0;i<a;i++){ cin>>arr[i]; } cout<<"Array before Selection sort: "<<endl; for(int i=0;i<a;i++){ cout<<arr[i]<<" "; } selectionSort(arr,a); cout<<endl<<"Array after Selection sort: "<<endl; for(int i=0;i<a;i++){ cout<<arr[i]<<" "; } } OUTPUT :- Experiment - 4 Aim :- WAP of merge sort and analyze its complexity. DESCRIPTION AND ALGORITHM :Merge Sort is a Divide and Conquer algorithm.It divides input array in two halves, calls itself for the two halves and then merges the two sorted halves. It takes advantage of the ease of merging already sorted lists into a new sorted list. It starts by comparing every two elements (i.e. 1 with 2, then 3 with 4...) and swapping them if the first should come after the second. It then merges each of the resulting lists of two into lists of four, then merges those lists of four, and so on; until at last two lists are merged into the final sorted list. Of the algorithms described here, this is the first that scales well to very large lists. Merge sort works as follows: 1. Divide the unsorted list into two sublists of about half the size. 2. Sort each of the two sub lists 3. Merge the two sorted sublists back into one sorted list. ANALYSIS :mergesort(l,h) { if ( l <h ){ mid=( l+h )/2 ; ------------ > 1 mergesort ( l, mid ) ; ------------ > T( n/2 ) mergesort ( mid+1, h ) ; ----------- > T( n/2 ) merge ( l, mid, h) ; -----------> ( n ) } Which implies T( n )= 2 T( n/2 ) + n Therefore, T ( n ) = { 1 ,n>=1 { 2 T ( n/2 ) , n>1 By master theorem :a = 2, b = 2, logba = log22 =1 therefore, f( n ) = n , nk = n1 => k=1 so, O(nlogn) Note :- You can use iterative method or tree method to solve this equation ● ● ● Best Case :- Ω( nlogn ) Average Case :- θ(n log(n)) Worst Case :- O(n log(n)) Program :#include<iostream> using namespace std; void swapping(int &a, int &b){ int temp; temp = a; a = b; b = temp; } void display(int *array, int size){ for(int i = 0; i<size; i++) cout << array[i] << " "; cout << endl; } void merge(int *array, int l, int m, int r){ int i, j, k, nl, nr; nl = m-l+1; nr = r-m; int larr[nl], rarr[nr]; for(i = 0; i<nl; i++) larr[i] = array[l+i]; for(j = 0; j<nr; j++) rarr[j] = array[m+1+j]; i = 0; j = 0; k = l; while(i < nl && j<nr){ if(larr[i] <= rarr[j]){ array[k] = larr[i]; i++; } else{ array[k] = rarr[j]; j++; } k++; } while(i<nl){ array[k] = larr[i]; i++; k++; } while(j<nr){ array[k] = rarr[j]; j++; k++; } } void mergeSort(int *array, int l, int r){ if(l < r){ int m = l+(r-l)/2; mergeSort(array, l, m); mergeSort(array, m+1, r); merge(array, l, m, r); } } int main(){ int n; cout << "Enter the number of elements: "; cin >> n; int arr[n]; cout << "Enter elements:" <<endl; for(int i = 0; i<n; i++) { cin >> arr[i]; } cout << "Array before Sorting: "; display(arr, n); mergeSort(arr, 0, n-1); cout << "Array after Sorting: "; display(arr, n); } Output :- HMR INSTITUTE OF TECHNOLOGY & MANAGEMENT Hamidpur, Delhi-110036 (An iso 9001:2008 certified, AICTE approved & GGSIP university affiliated institute) Experiment - 5 Aim :WAP of quick sort and analyze its complexity. DESCRIPTION AND ALGORITHM :- Quick Sort is also based on the concept of Divide and Conquer, just like merge sort. But in quick sort all the heavy lifting(major work) is done while dividing the array into subarrays, while in case of merge sort, all the real work happens during merging the subarrays. In case of quick sort, the combine step does absolutely nothing. It is also called partition-exchange sort. This algorithm divides the list into three main parts: ● Elements less than the Pivot element ● Pivot element(Central element) ● Elements greater than the pivot element Pivot element can be any element from the array, it can be the first element, the last element or any random element. Following are the steps involved in quick sort algorithm: 1. After selecting an element as pivot, we divide the array for the first time. 2. In quick sort, we call this partitioning. It is not simple breaking down of array into 2 subarrays, but in case of partitioning, the array elements are so positioned that all the elements smaller than the pivot will be on the left side of the pivot and all the elements greater than the pivot will be on the right side of it. 3. And the pivot element will be at its final sorted position. 4. The elements to the left and right, may not be sorted. 5. Then we pick subarrays, elements on the left of pivot and elements on the right of pivot, and we perform partitioning on them by choosing a pivot in the subarrays. ANALYSIS :- ● Worst Case :N............................................................................................................. cn n-1 .............................................................................................. c ( n-1 ) 1 1 n-2 .............................................................................. c ( n-2 ) 14 1 n-3 ........................................................... c ( n-3 ) . . . 2 2c 0 …………… 0 1 T(n) = cn+ c( n-1 ) + c( n-2 ) + c( n-3 )+ ............ 0 T(n) = c ( n + n-1 + n-2 + n-3 + ......... ) T(n) = c(n*n – (1+2+3+ ..... ) ) T(n) = c (n2 – (n(n+1)/2 )) T(n) = c(n(n+1)/2) Therefore , worst case time complexity is O(n2 ) ● Best Case :n n/2 n/2 n/4 1 1 n/4 1 1 1 T(n) = 2 T (n/2) + c ................................................ (1) T(n/2)= 2 T(n/4) + c ............................................. ( 2 ) T(n/4) = 2 T (n/8) + c .......................................... ( 3 ) From the above equations the recurrence relation will be:T( n/k ) = kT( n/2k ) + c .......................................... ( 4 ) When n=2k our T( n/2k ) becomes 1 Therefore , n=2k n/4 1 n/4 1 1 Taking log on both sides we get logn = klog2 k=logn / log2 k= logn after substituting value of k in equation 4 we get time complexity at best case as O( nlogn ) Program:#include<stdio.h> void quicksort(int number[25],int first,int last){ int i, j, pivot, temp; if(first<last){ pivot=first; i=first; j=last; while(i<j){ while(number[i]<=number[pivot]&&i<last) i++; while(number[j]>number[pivot]) j--; if(i<j){ temp=number[i]; number[i]=number[j]; number[j]=temp; } } temp=number[pivot]; number[pivot]=number[j]; number[j]=temp; quicksort(number,first,j-1); quicksort(number,j+1,last); } } int main(){ int i, count, number[25]; printf("How many elements are u going to enter?: "); scanf("%d",&count); printf("Enter %d elements: ", count); for(i=0;i<count;i++) scanf("%d",&number[i]); quicksort(number,0,count-1); printf("Order of Sorted elements: "); for(i=0;i<count;i++) printf(" %d",number[i]); return 0; } Output :- PRACTICAL - 3 Aim :WAP to implement Strassen’s algorithm for matrix multiplication and analyse its time complexity. DESCRIPTION AND ALGORITHM :Strassen’s Matrix multiplication can be performed only on square matrices where n is a power of 2. Order of both of the matrices are n × n. Divide X, Y and Z into four (n/2)×(n/2) matrices as represented below − Using Strassen’s Algorithm compute the following − M1 = ( A + C ) × ( E + F ) M2 = ( B + D ) × ( G + H ) M3 = ( A – D ) × ( E + H ) M4 = A × ( F – H ) M5 = ( C + D ) × ( E ) M6 = ( A + B ) × ( H ) M7 = D × ( G – E ) Then, I = M2 + M3 − M6 − M7 J = M4 + M6 K = M5 + M7 L = M1 − M3 − M4 − M5 ANALYSIS :- Using this recurrence relation and applying Master’s Theorem , we get T(n)= Hence, the complexity of Strassen’s matrix multiplication algorithm is O Program:using namespace std; #include<iostream> int main() { int a[2][2],b[2][2],c[2][2]; int p1,p2,p3,p4,p5,p6,p7,i,j; cout<<"Matrix Multiplication Strassen's method \n"; cout<<"Enter the elements of 2x2 Matrix 1:\n"; for(i=0;i<2;i++) { for(j=0;j<2;j++) { cin>>a[i][j]; } } cout<<"Enter the elements of 2x2 Matrix 2:\n"; for(i=0;i<2;i++) { for(j=0;j<2;j++) { cin>>b[i][j]; } } cout<<"\nFirst matrix is:\n"; for(i=0;i<2;i++) { for(j=0;j<2;j++) { cout<<a[i][j]; } O ( nlog7) ( nlog7). cout<<"\n"; } cout<<"\nSecond matrix is\n"; for(i=0;i<2;i++) { for(j=0;j<2;j++) { cout<<b[i][j]; } cout<<"\n"; } p1= (a[0][0] + a[1][1])*(b[0][0]+b[1][1]); ● p2= (a[1][0]+a[1][1])*b[0][0]; ● p3= a[0][0]*(b[0][1]-b[1][1]); ● p4= a[1][1]*(b[1][0]-b[0][0]); ● p5= (a[0][0]+a[0][1])*b[1][1]; ● p6= (a[1][0]-a[0][0])*(b[0][0]+b[0][1]); ● p7= (a[0][1]-a[1][1])*(b[1][0]+b[1][1]); ● c[0][0]=p1+p4-p5+p7; ● c[0][1]=p3+p5; ● c[1][0]=p2+p4; ● c[1][1]=p1-p2+p3+p6; ● cout<<"\nProduct of both is:\n"; for(i=0;i<2;i++) { for(j=0;j<2;j++) { cout<<c[i][j]<<"\n"; } cout<<"\n"; } return 0; } Output :- Experiment - 7 Aim :WAP To implement the Longest Common Subsequence problem and analyse its time complexity. DESCRIPTION AND ALGORITHM :LCS-Length-Table-Formulation (X, Y) m := length(X) n := length(Y) for i = 1 to m do C[i, 0] := 0 for j = 1 to n do C[0, j] := 0 for i = 1 to m do for j = 1 to n do if xi = yj C[i, j] := C[i - 1, j - 1] + 1 B[i, j] := ‘D’ else if C[i -1, j] ≥ C[i, j -1] C[i, j] := C[i - 1, j] + 1 B[i, j] := ‘U’ else C[i, j] := C[i, j - 1] B[i, j] := ‘L’ return C and B Print-LCS (B, X, i, j) if i = 0 and j = 0 return if B[i, j] = ‘D’ Print-LCS(B, X, i-1, j-1) Print(xi) else if B[i, j] = ‘U’ Print-LCS(B, X, i-1, j) else Print-LCS(B, X, i, j-1) ANALYSIS :- Doing this recursion the naive way in C++ isn't hard but that can easily take exponential time. Instead, we can use dynamic programming because there are only m*n distinct subproblems, and compute the values in a bottom up fashion. In other words, you set up a table pre-loaded with the trivial solution (X has length 0, Y has length 0). Each entry can be calculated depending only on the neighbors on its top, left, and top-left, as shown in the recurrence relation. For example, (2,3) depends on (1,3) (2,2) and (1,2). Fill in the table, and you get the length of the longest common substring in c[m, n]. T(m,n) = O(mn) + T(m/2,k) + T(m/2,n-k) You can think of this as sort of like quicksort -- we're breaking both strings into parts. But unlike quicksort it doesn't matter that the second string can be broken unequally. No matter what k is, the recurrence solves to O(mn). The easiest way to see this is to think about what it's doing in the array L. The main part of the algorithm visits the whole array, then the two calls visit two subarrays, one above and left of (m/2,k) and the other below and to the right. No matter what k is, the total size of these two subarrays is roughly mn/2. So instead we can write a simplified recurrence T(mn) = O(mn) + T(mn/2) which solves to O(mn) time total. Program:#include<stdio.h> #include<string.h> int i,j,m,n,c[20][20]; char x[20],y[20],b[20][20]; void print(int i,int j) { if(i==0 || j==0) return; if(b[i][j]=='c') { print(i-1,j-1); printf("%c",x[i-1]); } else if(b[i][j]=='u') print(i-1,j); else print(i,j-1); } void lcs() { m=strlen(x); n=strlen(y); for(i=0;i<=m;i++) c[i][0]=0; for(i=0;i<=n;i++) c[0][i]=0; //c, u and l denotes cross, upward and downward directions respectively for(i=1;i<=m;i++) for(j=1;j<=n;j++) { if(x[i-1]==y[j-1]) { c[i][j]=c[i-1][j-1]+1; b[i][j]='c'; } else if(c[i-1][j]>=c[i][j-1]) { c[i][j]=c[i-1][j]; b[i][j]='u'; } else { c[i][j]=c[i][j-1]; b[i][j]='l'; } } } int main() { printf("Enter 1st sequence:"); scanf("%s",x); printf("Enter 2nd sequence:"); scanf("%s",y); printf("\nThe Longest Common Subsequence is "); lcs(); print(m,n); return 0; } Output :- Experiment - 8 Aim :WAP To implement Dijkstra's algorithm and analyse its time complexity. DESCRIPTION AND ALGORITHM :Dijsktra ( G,W,S) 1. Initialize Single Source (G,S) 2. Set , S = Φ , QUEUE = V[G] 3. Repeat the steps 4 to 6 while QUEUE not empty. 4. U = EXTRACT_MIN(QUEUE) , S = S Union { U } 5. Repeat for each vertex V belongs to Adj [ U ] 6. RELAX ( U , V , C) 7. Exit Procdeure Relax( U,V,C) If distance[V] > distance[U] + Cost(U,V) then : distance[V] = distance[U] + Cost(U,V) , Parent (V) = U RETURN ANALYSIS :The time complexity of the above code/algorithm looks O(V^2) as there are two nested while loops. If we take a closer look, we can observe that the statements in inner loop are executed O(V+E) times (similar to BFS). The inner loop has decreaseKey() operation which takes O(LogV) time. So overall time complexity is O(E+V)*O(LogV) which is O((E+V)*LogV) = O(ELogV) Note that the above code uses Binary Heap for Priority Queue implementation. Time complexity can be reduced to O(E + VLogV) using Fibonacci Heap. The reason is, Fibonacci Heap takes O(1) time for decrease-key operation while Binary Heap takes O(Logn) time. Program:#include <iostream> #include<bits/stdc++.h> using namespace std; template<typename T> class Graph { unordered_map<T,list<pair<T,int>>> m; public: void addEdge(T u,T v,int dist) { m[u].push_back(make_pair(v,dist)); } void printGraph() { for(auto j:m) { cout<<j.first<<":"; for(auto k:j.second) { cout<<"("<<k.first<<","<<k.second<<") "; } cout<<endl; } } void dijkstra(T src) { unordered_map<T,int>dist; for(auto j:m) dist[j.first]=INT_MAX; set<pair<int,T>>s; dist[src]=0; s.insert(make_pair(0,src)); while(!s.empty()) { auto p=*(s.begin()); T node = p.second; int nodeDist=p.first; s.erase(s.begin()); for(auto childPair:m[node]){ if(nodeDist+childPair.second < dist[childPair.first]) { T dest=childPair.first; auto f=s.find(make_pair(dist[dest],dest)); if(f!=s.end()){ s.erase(f); } dist[dest]=nodeDist+childPair.second; s.insert(make_pair(dist[dest],dest)); } } } for(auto d:dist){ cout<<d.first<<" is located at distance of "<<d.second<<endl; } } }; int main() { Graph<int> g; g.addEdge(1,2,1); g.addEdge(1,3,3); g.addEdge(2,3,1); g.addEdge(3,4,2); g.addEdge(1,4,7); cout<<"Before Dijsktra Applied On Graph : \n\n"; g.printGraph(); cout<<"\nAfter Dijsktra Applied On Graph : \n\n"; g.dijkstra(1); return 0; } Output :-