You are on page 1of 21

1. Given a list with numbers (6,5,12,10,9,1). Apply merge sort and sort the given list.

Also
implement the same using any programming language in recursive mode and iterative.
Derive the complexity for the Best and Worst case for Merge sort. Do specify various
applications of Merge sort

#include <iostream>
using namespace std;

// Function to merge two subarrays


void merge(int arr[], int left, int middle, int right) {
int i, j, k;
int n1 = middle - left + 1;
int n2 = right - middle;

// Create temporary arrays


int leftArr[n1], rightArr[n2];

// Copy data to temporary arrays


for (i = 0; i < n1; i++)
leftArr[i] = arr[left + i];
for (j = 0; j < n2; j++)
rightArr[j] = arr[middle + 1 + j];

// Merge the temporary arrays back into arr[]


i = 0; // Initial index of first subarray
j = 0; // Initial index of second subarray
k = left; // Initial index of merged subarray
while (i < n1 && j < n2) {
if (leftArr[i] <= rightArr[j]) {
arr[k] = leftArr[i];
i++;
} else {
arr[k] = rightArr[j];
j++;
}
k++;
}
// Copy the remaining elements of leftArr[], if any
while (i < n1) {
arr[k] = leftArr[i];
i++;
k++;
}

// Copy the remaining elements of rightArr[], if any


while (j < n2) {
arr[k] = rightArr[j];
j++;
k++;
}
}

// Recursive function to perform merge sort


void mergeSortRecursive(int arr[], int left, int right) {
if (left < right) {
int middle = left + (right - left) / 2;

// Sort first and second halves


mergeSortRecursive(arr, left, middle);
mergeSortRecursive(arr, middle + 1, right);

// Merge the sorted halves


merge(arr, left, middle, right);
}
}

// Function to print the array


void printArray(int arr[], int size) {
for (int i = 0; i < size; i++)
cout << arr[i] << " ";
cout << endl;
}

int main() {
int arr[] = {6, 5, 12, 10, 9, 1};
int size = sizeof(arr) / sizeof(arr[0]);

cout << "Original array: ";


printArray(arr, size);
mergeSortRecursive(arr, 0, size - 1);

cout << "Sorted array (Recursive): ";


printArray(arr, size);

return 0;
}

Iterative approach:

#include <iostream>
using namespace std;

// Function to merge two subarrays


void merge(int arr[], int left, int middle, int right) {
int i, j, k;
int n1 = middle - left + 1;
int n2 = right - middle;

// Create temporary arrays


int leftArr[n1], rightArr[n2];

// Copy data to temporary arrays


for (i = 0; i < n1; i++)
leftArr[i] = arr[left + i];
for (j = 0; j < n2; j++)
rightArr[j] = arr[middle + 1 + j];

// Merge the temporary arrays back into arr[]


i = 0; // Initial index of first subarray
j = 0; // Initial index of second subarray
k = left; // Initial index of merged subarray
while (i < n1 && j < n2) {
if (leftArr[i] <= rightArr[j]) {
arr[k] = leftArr[i];
i++;
} else {
arr[k] = rightArr[j];
j++;
}
k++;
}

// Copy the remaining elements of leftArr[], if any


while (i < n1) {
arr[k] = leftArr[i];
i++;
k++;
}

// Copy the remaining elements of rightArr[], if any


while (j < n2) {
arr[k] = rightArr[j];
j++;
k++;
}
}

// Iterative function to perform merge sort


void mergeSortIterative(int arr[], int size) {
int left, middle, right;
int step = 1;

while (step < size) {


left = 0;
while (left + step < size) {
middle = left + step - 1;
right = middle + step;
if (right >= size)
right = size - 1;

merge(arr, left, middle, right);


left = left + 2 * step;
}
step *= 2;
}
}

// Function to print the array


void printArray(int arr[], int size) {
for (int i = 0; i < size; i++)
cout << arr[i] << " ";
cout << endl;
}

int main() {
int arr[] = {6, 5, 12, 10, 9, 1};
int size = sizeof(arr) / sizeof(arr[0]);

cout << "Original array: ";


printArray(arr, size);

mergeSortIterative(arr, size);

cout << "Sorted array (Iterative): ";


printArray(arr, size);

return 0;
}
The best and worst time complexity of merge sort is as follows:

• Best case time complexity: O(n log n)


• Worst case time complexity: O(n log n)

Some of the common applications of merge sort include:

• Sorting: Merge sort is primarily used for sorting large datasets efficiently. It guarantees a time complexity
of O(n log n) in both the best and worst cases, making it suitable for sorting applications where
performance is crucial.

• External Sorting: Merge sort is well-suited for external sorting scenarios where the data to be sorted
exceeds the available memory. It can handle large datasets by reading and writing data from and to
external storage devices, minimizing the need for excessive memory usage.

• Inversion Count: Merge sort can be utilized to count the number of inversions in an array. An inversion
occurs when two elements in an array are out of order with respect to their sorted order. This property of
merge sort makes it useful in applications such as analyzing data for inversions or inversions-based
problems.
• External Merging in External Memory Algorithms: Merge sort is extensively used in external memory
algorithms, particularly for external merging. When dealing with large datasets that cannot fit entirely in
memory, external merging involves merging sorted subsequences from disk or other external storage
devices. Merge sort's efficient merging operation is crucial for external memory algorithms.

• Inversion-based Problems: Merge sort's property of counting inversions can be used to solve various
inversion-based problems, such as finding the number of swaps required to sort an array or identifying
the closest pair of points in a plane.

2. Given a list with numbers (52, 37, 63, 14, 17, 8, 6, 25), Apply Quick sort and sort the given list. Also
implement the same using any programming language like c, c++ or Java in iterative mode and
recursive. Derive the complexity for the Best and Worst case for Quick sort. Do specify various
applications of Quicksort

Recursive Approach

#include <iostream>
using namespace std;

// Function to swap two elements


void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}

// Partition function for QuickSort


int partition(int arr[], int low, int high) {
int pivot = arr[high]; // Choosing the last element as the pivot
int i = (low - 1);

for (int j = low; j <= high - 1; j++) {


if (arr[j] < pivot) {
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]);
return (i + 1);
}

// Recursive function to perform QuickSort


void quickSortRecursive(int arr[], int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);

quickSortRecursive(arr, low, pi - 1);


quickSortRecursive(arr, pi + 1, high);
}
}

// Function to print the array


void printArray(int arr[], int size) {
for (int i = 0; i < size; i++)
cout << arr[i] << " ";
cout << endl;
}

int main() {
int arr[] = {52, 37, 63, 14, 17, 8, 6, 25};
int size = sizeof(arr) / sizeof(arr[0]);

cout << "Original array: ";


printArray(arr, size);

quickSortRecursive(arr, 0, size - 1);

cout << "Sorted array (Recursive): ";


printArray(arr, size);

return 0;
}
Iterative Approach

#include <iostream>
#include <stack>
using namespace std;

// Function to swap two elements


void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}

// Partition function for QuickSort


int partition(int arr[], int low, int high) {
int pivot = arr[high]; // Choosing the last element as the pivot
int i = (low - 1);

for (int j = low; j <= high - 1; j++) {


if (arr[j] < pivot) {
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]);
return (i + 1);
}

// Iterative function to perform QuickSort


void quickSortIterative(int arr[], int low, int high) {
stack<int> stack;

stack.push(low);
stack.push(high);

while (!stack.empty()) {
high = stack.top();
stack.pop();
low = stack.top();
stack.pop();

int pi = partition(arr, low, high);

if (pi - 1 > low) {


stack.push(low);
stack.push(pi - 1);
}

if (pi + 1 < high) {


stack.push(pi + 1);
stack.push(high);
}
}
}

// Function to print the array


void printArray(int arr[], int size) {
for (int i = 0; i < size; i++)
cout << arr[i] << " ";
cout << endl;
}

int main() {
int arr[] = {52, 37, 63, 14, 17, 8, 6, 25};
int size = sizeof(arr) / sizeof(arr[0]);

cout << "Original array: ";


printArray(arr, size);

quickSortIterative(arr, 0, size - 1);

cout << "Sorted array (Iterative): ";


printArray(arr, size);

return 0;
}
The best and worst case time complexity of QuickSort is as follows:
• Best case time complexity: O(n log n)
• Worst case time complexity: O(n^2)

Applications of QuickSort include:

• Sorting: QuickSort is widely used for sorting large datasets efficiently. Its average case
performance of O(n log n) and in-place partitioning make it a popular choice for sorting
applications.

• Language Compilers: QuickSort is often used in language compilers to optimize code generation. I
can be utilized for sorting function calls, register allocation, and other optimization techniques.

• Numerical Analysis: QuickSort finds applications in numerical analysis, such as solving linear
systems of equations, calculating eigenvalues, and matrix factorizations.

• Data Deduplication: QuickSort is used in data deduplication algorithms to efficiently identify and
eliminate duplicate entries from large datasets.

• Searching: QuickSort's partitioning property can be utilized for efficient searching algorithms, such
as quick select, where the goal is to find the kth smallest or largest element in an array.

3. Given a list A = (10,7,2,5,11)

Apply Cycle sort and sort the given list. Also implement the same using any programming language like c,
c++ or Java in recursive mode and iterative. Derive the complexity for the Best and Worst case for cycle
sort. Do specify various applications of cycle sort.

Recursive Approach:

#include <iostream>
using namespace std;

// Function to perform cycle sort recursively


void cycleSortRecursive(int arr[], int n, int currentIndex) {
int item = arr[currentIndex];
int position = currentIndex;

for (int i = currentIndex + 1; i < n; i++) {


if (arr[i] < item)
position++;
}

if (position == currentIndex)
return;

while (item == arr[position])


position++;

if (position != currentIndex) {
swap(item, arr[position]);
cycleSortRecursive(arr, n, position);
}
}

// Function to print the array


void printArray(int arr[], int size) {
for (int i = 0; i < size; i++)
cout << arr[i] << " ";
cout << endl;
}

int main() {
int arr[] = {10, 7, 2, 5, 11};
int size = sizeof(arr) / sizeof(arr[0]);

cout << "Original array: ";


printArray(arr, size);

cycleSortRecursive(arr, size, 0);

cout << "Sorted array (Recursive): ";


printArray(arr, size);

return 0;
}
Iterative Approach:

#include <iostream>
using namespace std;

// Function to perform cycle sort iteratively


void cycleSortIterative(int arr[], int n) {
for (int currentIndex = 0; currentIndex < n - 1; currentIndex++) {
int item = arr[currentIndex];
int position = currentIndex;

for (int i = currentIndex + 1; i < n; i++) {


if (arr[i] < item)
position++;
}

if (position == currentIndex)
continue;

while (item == arr[position])


position++;
if (position != currentIndex)
swap(item, arr[position]);

while (position != currentIndex) {


position = currentIndex;

for (int i = currentIndex + 1; i < n; i++) {


if (arr[i] < item)
position++;
}

while (item == arr[position])


position++;

if (item != arr[position])
swap(item, arr[position]);
}
}
}

// Function to print the array


void printArray(int arr[], int size) {
for (int i = 0; i < size; i++)
cout << arr[i] << " ";
cout << endl;
}

int main() {
int arr[] = {10, 7, 2, 5, 11};
int size = sizeof(arr) / sizeof(arr[0]);

cout << "Original array: ";


printArray(arr, size);

cycleSortIterative(arr, size);

cout << "Sorted array (Iterative): ";


printArray(arr, size);

return 0;
}
The best and worst case time complexity of Cycle Sort is as follows:

• Best case time complexity: O(n^2)


• Worst case time complexity: O(n^2)

Applications of Cycle Sort include:

• Memory Systems: Cycle Sort is useful in memory systems where the cost of write or swap operations is
high compared to the number of comparisons. It minimizes the number of writes or swaps required to
sort an array.

• Embedded Systems: Cycle Sort finds applications in embedded systems, where memory and processing
power are limited. Its in-place nature and minimal memory usage make it suitable for such systems.

• Data Duplication: Cycle Sort can be used to eliminate duplicate entries from a dataset. It ensures that each
value appears only once in the sorted output.
• Small Data Sets: Cycle Sort is efficient for small data sets, as it reduces the number of writes or swaps
compared to other sorting algorithms. It can be a good choice when the number of writes or swaps is
costly.

• Stable Sorting: Although Cycle Sort itself is not a stable sorting algorithm, it can be combined with other
stable sorting algorithms to achieve stable sorting. By performing multiple cycles, stability can be
maintained.

4. Given elements (.5,2,9,5,2,3,5) apply count sort


Choose any programming language and recursive mode iterations and best and worst time complexity.
Also mention its applications.

Recursive Approach

#include <iostream>
#include <vector>
#include <algorithm> // Include the <algorithm> header for max_element and min_element
using namespace std;

void countSortRecursive(vector<double>& arr, int n) {


double maxElement = *max_element(arr.begin(), arr.end());
double minElement = *min_element(arr.begin(), arr.end());
int range = int(maxElement - minElement + 1);

vector<double> count(range, 0), output(n);

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


count[int(arr[i] - minElement)]++;
}

for (int i = 1; i < range; i++) {


count[i] += count[i - 1];
}

for (int i = n - 1; i >= 0; i--) {


output[count[int(arr[i] - minElement)] - 1] = arr[i];
count[int(arr[i] - minElement)]--;
}
for (int i = 0; i < n; i++) {
arr[i] = output[i];
}
}

void countSortIterative(vector<double>& arr, int n) {


double maxElement = *max_element(arr.begin(), arr.end());
double minElement = *min_element(arr.begin(), arr.end());
int range = int(maxElement - minElement + 1);

vector<double> count(range, 0), output(n);

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


count[int(arr[i] - minElement)]++;
}

int outputIndex = 0;
for (int i = 0; i < range; i++) {
while (count[i] > 0) {
output[outputIndex] = i + minElement;
count[i]--;
outputIndex++;
}
}

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


arr[i] = output[i];
}
}

void printArray(const vector<double>& arr) {


for (double num : arr) {
cout << num << " ";
}
cout << endl;
}

int main() {
vector<double> arr = {0.5, 2, 9, 5, 2, 3, 5};
int size = arr.size();

cout << "Original array: ";


printArray(arr);
// Applying Count Sort using recursive approach
countSortRecursive(arr, size);
cout << "Sorted array (Recursive): ";
printArray(arr);

// Resetting the array


arr = {0.5, 2, 9, 5, 2, 3, 5};

// Applying Count Sort using iterative approach


countSortIterative(arr, size);
cout << "Sorted array (Iterative): ";
printArray(arr);

return 0;
}

The best and worst case time complexity of Count Sort is as follows:

• Best case time complexity: O(n + k)


• Worst case time complexity: O(n + k)

Counting Sort has several applications, including:


• Sort a list of integers within a specific range: Counting Sort is most commonly used to sort a list of
integers when the range of the input elements is known. It works efficiently when the range is
relatively small compared to the number of elements.

• Frequency analysis: Counting Sort can be used to analyse the frequency of occurrence of elements in a
given dataset. It can provide insights into the distribution of elements and identify patterns or
anomalies.

• Stable sorting: Counting Sort is a stable sorting algorithm, meaning that it maintains the relative order
of elements with equal values. This property is useful in situations where preserving the order of equal
elements is important.

• Auxiliary algorithm: Counting Sort is often used as an auxiliary algorithm in combination with other
sorting algorithms to optimize performance. For example, it can be used as a subroutine within Radix
Sort or Bucket Sort.

• Histogram generation: Counting Sort can be used to generate a histogram from a set of data. It counts
the occurrences of different values and produces a histogram representing the frequency distribution.

You might also like