You are on page 1of 10

Week-2

Program 3: Program to implement Merge Sort using the Divide and Conquer technique
and analyze its Time Complexity.

Description: Merge sort is a sorting technique based on divide and conquer technique. With
worst-case time complexity being Ο(n log n), it is one of the most used and approached
algorithms.

Merge sort is a recursive algorithm that continuously splits the array in half until it cannot be
further divided i.e., the array has only one element left (an array with one element is always
sorted). Then the sorted subarrays are merged into one sorted array.

ALGORITHM:
Step 1: If it is only one element in the list, consider it already
sorted, so return.
Step 2: Divide the list recursively into two halves until it can no
more be divided.
Step 3: Merge the smaller lists into new list in sorted order.

Time Complexity:

o Best Case Complexity - It occurs when there is no sorting required, i.e. the array is
already sorted. The best-case time complexity of merge sort 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 merge sort is O(n*logn).
o Worst Case Complexity - It occurs when the array elements are required to be sorted
in reverse order. That means suppose you have to sort the array elements in ascending
order, but its elements are in descending order. The worst-case time complexity of
merge sort is O(n*logn).

Explanation:

Let us consider, the running time of Merge-Sort as T(n). Hence,

16
Therefore, using this recurrence relation,

17
SOURCE CODE:
#include<bits/stdc++.h>
using namespace std;

void merge(int * arr,int low,int mid,int high){


int n1=mid-low+1;
int n2=high-mid;
int left[n1];
int right[n2];
for(int i=0;i<n1;i++){
left[i]=arr[i+low];
}
for(int i=0;i<n2;i++){
right[i]=arr[i+mid+1];
}`

int i=0,j=0,k=low;
while(i<n1 && j<n2){
if(left[i]<=right[j]){
arr[k]=left[i];
i++;
k++;
}
else{
arr[k]=right[j];
k++;
j++;
}
}
while(i<n1){
arr[k]=left[i];
i++;k++;
}
while(j<n2){
arr[k]=right[j];
j++;k++;
}

}
void mergesort(int* arr,int low,int high){
if(low<high){
int mid= low +(high-low)/2;
mergesort(arr,low,mid);
mergesort(arr,mid+1,high);
merge(arr,low,mid,high);
}

18
int main(){
int n;
cout<<"Enter the number of Elements in Array: ";
cin>>n;
int arr[n];
cout<<"Enter the Elements: "<<endl;
for(int i=0;i<n;i++){
cin>>arr[i];
}
mergesort(arr,0,n-1);
cout<<"Sorted Array: "<<endl;
for(int i=0;i<n;i++){
cout<<arr[i]<<" ";
}
return 0;
}

19
OUTPUT:

20
Program 4: . Program to implement Starssen’s Matrix Multiplication Algorithm and
analyze its Time Complexity.
Description:
Strassen's algorithm is an efficient method for matrix multiplication, especially for large
matrices. It's based on the principle of recursively breaking down matrices into smaller
submatrices to reduce the number of scalar multiplications required.
ALGORITHM:
• Divide a matrix of order of 2*2 recursively till we get the matrix of 2*2.
• Use the previous set of formulas to carry out 2*2 matrix multiplication.
• In this eight multiplication and four additions, subtraction are performed.
• Combine the result of two matrixes to find the final product or final matrix.
D1 = (a11 + a22) (b11 + b22)
D2 = (a21 + a22).b11
D3 = (b12 – b22).a11
D4 = (b21 – b11).a22
D5 = (a11 + a12).b22
D6 = (a21 – a11) . (b11 + b12)
D7 = (a12 – a22) . (b21 + b22)
C11 = d1 + d4 – d5 + d7
C12 = d3 + d5
C21 = d2 + d4
C22 = d1 + d3 – d2 – d6

Time complexity:

Using this recurrence relation, we get T(n)=O(nlog7)


• Worst case time complexity: Θ(n^2.8074)
• Best case time complexity: Θ(1)
• Space complexity: Θ(logn)
Strassen's algorithm has a time complexity of O(n^log2(7)), which approximately equals
O(n^2.81) due to the seven recursive calls it makes.

21
SOURCE CODE:
#include<bits/stdc++.h>
using namespace std;

//Function to add two matrices


vector<vector<int>> addMatrix(const vector<vector<int>>& A, const vector<vector<int>>&
B) {
int n = A.size();
vector<vector<int>> result(n, vector<int>(n, 0));

for (int i = 0; i < n; ++i) {


for (int j = 0; j < n; ++j) {
result[i][j] = A[i][j] + B[i][j];
}
}

return result;
}

// Function to subtract two matrices


vector<vector<int>> subtractMatrix(const vector<vector<int>>& A, const
vector<vector<int>>& B) {
int n = A.size();
vector<vector<int>> result(n, vector<int>(n, 0));

for (int i = 0; i < n; ++i) {


for (int j = 0; j < n; ++j) {
result[i][j] = A[i][j] - B[i][j];
}
}

return result;
}

// Function to multiply two matrices using the Strassen algorithm


vector<vector<int>> strassenMultiply(const vector<vector<int>>& A, const
vector<vector<int>>& B) {
int n = A.size();

// Base case: If matrices are 1x1, perform standard multiplication


if (n == 1) {
return {{A[0][0] * B[0][0]}};
}

// Divide matrices into four submatrices


int newSize = n / 2;

vector<vector<int>> A11(newSize, vector<int>(newSize, 0));

22
vector<vector<int>> A12(newSize, vector<int>(newSize, 0));
vector<vector<int>> A21(newSize, vector<int>(newSize, 0));
vector<vector<int>> A22(newSize, vector<int>(newSize, 0));

vector<vector<int>> B11(newSize, vector<int>(newSize, 0));


vector<vector<int>> B12(newSize, vector<int>(newSize, 0));
vector<vector<int>> B21(newSize, vector<int>(newSize, 0));
vector<vector<int>> B22(newSize, vector<int>(newSize, 0));

// Populate submatrices
for (int i = 0; i < newSize; ++i) {
for (int j = 0; j < newSize; ++j) {
A11[i][j] = A[i][j];
A12[i][j] = A[i][j + newSize];
A21[i][j] = A[i + newSize][j];
A22[i][j] = A[i + newSize][j + newSize];

B11[i][j] = B[i][j];
B12[i][j] = B[i][j + newSize];
B21[i][j] = B[i + newSize][j];
B22[i][j] = B[i + newSize][j + newSize];
}
}

// Calculate seven products using recursive calls


vector<vector<int>> M1 = strassenMultiply(addMatrix(A11, A22), addMatrix(B11, B22));
vector<vector<int>> M2 = strassenMultiply(addMatrix(A21, A22), B11);
vector<vector<int>> M3 = strassenMultiply(A11, subtractMatrix(B12, B22));
vector<vector<int>> M4 = strassenMultiply(A22, subtractMatrix(B21, B11));
vector<vector<int>> M5 = strassenMultiply(addMatrix(A11, A12), B22);
vector<vector<int>> M6 = strassenMultiply(subtractMatrix(A21, A11), addMatrix(B11,
B12));
vector<vector<int>> M7 = strassenMultiply(subtractMatrix(A12, A22), addMatrix(B21,
B22));

// Calculate result matrices


// Calculate result matrices
vector<vector<int>> C11 = subtractMatrix(addMatrix(M1, M4), addMatrix(M5, M7));
vector<vector<int>> C12 = addMatrix(M3, M5);
vector<vector<int>> C21 = addMatrix(M2, M4);
vector<vector<int>> C22 = subtractMatrix(subtractMatrix(addMatrix(M1, M3), M2),
addMatrix(M5, M6));

// newSize is already halved during the recursive calls in the Strassen algorithm

// Combine the result matrices into the final result matrix C


vector<vector<int>> result(n, vector<int>(n, 0));

23
for (int i = 0; i < newSize; ++i) {
for (int j = 0; j < newSize; ++j) {
result[i][j] = C11[i][j];
result[i][j + newSize] = C12[i][j];
result[i + newSize][j] = C21[i][j];
result[i + newSize][j + newSize] = C22[i][j];
}
}

return result;
}

// Function to print a matrix


void printMatrix(const vector<vector<int>>& matrix) {
for (const auto& row : matrix) {
for (int val : row) {
cout << val << " ";
}
cout << endl;
}
}

int main() {
// Example usage
vector<vector<int>> A = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}};
vector<vector<int>> B = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}};

// Ensure matrices are square and of size 2^n


int n = A.size();
int m = B.size();
if (n != m || (n & (n - 1)) != 0) {
cerr << "Matrices must be square and of size 2^n." << endl;
return 1;
}

vector<vector<int>> result = strassenMultiply(A, B);

// Print the result matrix


cout << "Result Matrix:" << endl;
printMatrix(result);

return 0;
}

24
OUTPUT:

25

You might also like