Advanced Sorting Algorithms Explained
Advanced Sorting Algorithms Explained
2
Let's look at the working of above example:
Divide:
[38, 27, 43, 10] is divided into [38, 27 ] and [43, 10] .
3
C program of Merge sort
#include <stdio.h>
#include <stdlib.h>
// Merges two subarrays of arr[].
// First subarray is arr[l..m]
// Second subarray is arr[m+1..r]
void merge(int arr[], int l, int m, int r){
int i, j, k;
int n1 = m - l + 1;
int n2 = r - m;
// Create temp arrays
int L[n1], R[n2];
// Copy data to temp arrays L[] and R[]
for (i = 0; i < n1; i++)
L[i] = arr[l + i];
for (j = 0; j < n2; j++)
R[j] = arr[m + 1 + j];
// Merge the temp arrays back into arr[l..r
i = 0;
j = 0;
k = l;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
}
else {
arr[k] = R[j];
j++;
}
k++;
}
// Copy the remaining elements of L[],
// if there are any
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
// Copy the remaining elements of R[],
4
// if there are any
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
}
// l is for left index and r is right index of the
// sub-array of arr to be sorted
void mergeSort(int arr[], int l, int r){
if (l < r) {
int m = l + (r - l) / 2;
// Sort first and second halves
mergeSort(arr, l, m);
mergeSort(arr, m + 1, r);
merge(arr, l, m, r);
}
}
// Driver code
int main(){
10 27 38 43
Advantages
• Consistent Time Complexity: O(n log n) time complexity in all cases (best,
• average, worst).
• Stable Sorting: Maintains the relative order of equal elements.
• Efficient for Large Data Sets: Handles large arrays or lists efficiently.
• Parallelizable: Can be easily parallelized due to its divide-and-conquer nature.
5
• Predictable Performance: Performance does not degrade based on input data
characteristics.
Disadvantages
• High Space Complexity: Requires O(n) additional space for merging.
• Complex Implementation: More complex to implement compared to simpler
• algorithms like insertion sort or selection sort.
• Not In-Place: Uses extra space for temporary subarrays, which can be a
• limitation for memory-constrained environments.
• Overhead for Small Arrays: For small arrays, the overhead of recursive calls
• and merging can make it slower than simpler algorithms like insertion sort.
Heap Sort
Heap sort is a comparison-based sorting technique based on Binary Heap Data Structure. It can be
seen as an optimization over selection sort where we first find the max (or min) element and swap
it with the last (or first). We repeat the same process for the remaining elements. In Heap Sort, we
use Binary Heap so that we can quickly find and move the max element in O(Log n) instead of
O(n) and hence achieve the O(n Log n) time complexity.
6
• Repeat the following steps until the heap contains only one element:
o Swap the root element of the heap (which is the largest element in current heap)
with the last element of the heap.
o Remove the last element of the heap (which is now in the correct position). We
mainly reduce heap size and do not remove element from the actual array.
o Heapify the remaining elements of the heap.
• Finally we get sorted array.
7
8
Step 3: Sort the array by placing largest element at end of unsorted array.
9
10
C program of Heap sort
#include <stdio.h>
// To heapify a subtree rooted with node i
// which is an index in arr[].
void heapify(int arr[], int n, int i) {
// Initialize largest as root
int largest = i;
// left index = 2*i + 1
int l = 2 * i + 1;
// right index = 2*i + 2
int r = 2 * i + 2;
// If left child is larger than root
if (l < n && arr[l] > arr[largest]) {
largest = l;
}
// If right child is larger than largest so far
11
if (r < n && arr[r] > arr[largest]) {
largest = r;
}
// If largest is not root
if (largest != i) {
int temp = arr[i];
arr[i] = arr[largest];
arr[largest] = temp;
// Recursively heapify the affected sub-tree
heapify(arr, n, largest);
}
}
// Driver's code
int main() {
int arr[] = {9, 4, 3, 8, 10, 2, 5};
12
int n = sizeof(arr) / sizeof(arr[0]);
heapSort(arr, n);
printf("Sorted array is \n");
printArray(arr, n);
return 0;
}
Output
Sorted array is
2 3 4 5 8 9 10
13
Searching Algorithms:
Binary Search
Binary Search Algorithm is a searching algorithm used in a sorted array by repeatedly dividing the
search interval in half. The idea of binary search is to use the information that the array is sorted
and reduce the time complexity to O(log N).
Conditions to apply Binary Search Algorithm in a Data Structure
To apply Binary Search algorithm:
• The data structure must be sorted.
• Access to any element of the data structure should take constant time.
Binary Search Algorithm
Below is the step-by-step algorithm for Binary Search:
• Divide the search space into two halves by finding the middle index "mid".
• Compare the middle element of the search space with the key.
• If the key is found at middle element, the process is terminated.
• If the key is not found at middle element, choose which half will be used as the next search
space.
o If the key is smaller than the middle element, then the left side is used for next
search.
o If the key is larger than the middle element, then the right side is used for next
search.
• This process is continued until the key is found or the total search space is exhausted.
14
C program to implement iterative Binary Search
#include <stdio.h>
// An iterative binary search function.
int binarySearch(int arr[], int low, int high, int x)
{
while (low <= high) {
int mid = low + (high - low) / 2;
// Check if x is present at mid
if (arr[mid] == x)
return mid;
// If x greater, ignore left half
if (arr[mid] < x)
low = mid + 1;
// If x is smaller, ignore right half
15
else
high = mid - 1;
}
// Driver code
int main(void)
{
int arr[] = { 2, 3, 4, 10, 40 };
int n = sizeof(arr) / sizeof(arr[0]);
int x = 10;
int result = binarySearch(arr, 0, n - 1, x);
if(result == -1) printf("Element is not present in array");
else printf("Element is present at index %d",result);
}
Output
Element is present at index 3
16
if (arr[mid] > x)
return binarySearch(arr, low, mid - 1, x);
// Else the element can only be present
// in right subarray
return binarySearch(arr, mid + 1, high, x);
}
17
Ternary Search
Computer systems use different methods to find specific data. There are various search algorithms,
each better suited for certain situations. For instance, a binary search divides information into two
parts, while a ternary search does the same but into three equal parts. It's worth noting that ternary
search is only effective for sorted data. In this article, we're going to uncover the secrets of Ternary
Search – how it works, why it's faster in some situations.
18
•If the target is greater than the element at mid2, update the left pointer to mid2 +
1.
• If the target is between the elements at mid1 and mid2, update the left pointer
to mid1 + 1 and the right pointer to mid2 - 1.
4. Repeat or Conclude:
• Repeat the process with the reduced search space until the target is found or the
search space becomes empty.
• If the search space is empty and the target is not found, return a value indicating
that the target is not present in the array.
20
// Search the key using ternarySearch
p = ternarySearch(l, r, key, ar);
// Print the result
printf("Index of %d is %d\n", key, p);
// Checking for 50
// Key to be searched in the array
key = 50;
// Search the key using ternarySearch
p = ternarySearch(l, r, key, ar);
// Print the result
printf("Index of %d is %d", key, p);
}
Output
Index of 5 is 4
Index of 50 is -1
21
if (key < ar[mid1]) {
// The key lies in between l and mid1
r = mid1 - 1;
}
else if (key > ar[mid2]) {
// The key lies in between mid2 and r
l = mid2 + 1;
}
else {
// The key lies in between mid1 and mid2
l = mid1 + 1;
r = mid2 - 1;
}
}
// Key not found
return -1;
}
// Driver code
int main()
{
int l, r, p, key;
// Get the array
// Sort the array if not sorted
int ar[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Starting index
l = 0;
// end element index
r = 9;
// Checking for 5
// Key to be searched in the array
key = 5;
// Search the key using ternarySearch
p = ternarySearch(l, r, key, ar);
// Print the result
printf("Index of %d is %d\n", key, p);
// Checking for 50
// Key to be searched in the array
key = 50;
// Search the key using ternarySearch
p = ternarySearch(l, r, key, ar);
22
// Print the result
printf("Index of %d is %d", key, p);
}
Output
Index of 5 is 4
Index of 50 is -1
Advantages:
Ternary search can find maxima/minima for unimodal functions, where binary search is not
applicable.
Ternary Search has a time complexity of O(2 * log3n), which is more efficient than linear search
and comparable to binary search.
Fits well with optimization problems.
Disadvantages:
Ternary Search is only applicable to ordered lists or arrays, and cannot be used on unordered or
non-linear data sets.
Ternary Search takes more time to find maxima/minima of monotonic functions as compared to
Binary Search.
23
Dynamic Programming
Dynamic Programming is a commonly used algorithmic technique used to optimize
recursive solutions when same subproblems are called again.
The core idea behind DP is to store solutions to subproblems so that each is solved only
once.
To solve DP problems, we first write a recursive solution in a way that there are overlapping
subproblems in the recursion tree (the recursive function is called with the same parameters
multiple times)
To make sure that a recursive value is computed only once (to improve time taken by
algorithm), we store results of the recursive calls.
There are two ways to store the results, one is top down (or memoization) and other is
bottom up (or tabulation).
Example:
Consider the problem of computing the Fibonacci series. To compute the Fibonacci number
at index n, we need to compute the Fibonacci numbers at indices n-1 and n-2. This means
that the subproblem of computing the Fibonacci number at index n-2 is used twice (note
24
that the call for n - 1 will make two calls, one for n-2 and other for n-3) in the solution to
the larger problem of computing the Fibonacci number at index n.
You may notice overlapping subproblems highlighted in the second recursion tree for Nth
Fibonacci diagram shown below.
25
Example of Dynamic Programming (DP)
Example 1: Consider the problem of finding the Fibonacci sequence:
Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...
Brute Force Approach: To find the nth Fibonacci number using a brute force approach, you
would simply add the (n-1)th and (n-2)th Fibonacci numbers.
// C program to find fibonacci number using recursion.
#include <stdio.h>
// Function to find nth fibonacci number
int fib(int n) {
if (n <= 1) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
int main() {
int n = 5;
printf("%d", fib(n));
return 0;
}
26
• Identify Subproblems: Divide the main problem into smaller, independent
subproblems, i.e., F(n-1) and F(n-2)
• Store Solutions: Solve each subproblem and store the solution in a table or array
so that we do not have to recompute the same again.
• Build Up Solutions: Use the stored solutions to build up the solution to the main
problem. For F(n), look up F(n-1) and F(n-2) in the table and add them.
• Avoid Recomputation: By storing solutions, DP ensures that each subproblem (for
example, F(2)) is solved only once, reducing computation time.
27
• Bellman–Ford Shortest Path: Finds the shortest path from a given source to all other
vertices.
• Floyd Warshall : Finds shortest path from every pair of vertices.
• Knapsack Problem: Determines the maximum value of items that can be placed in a
knapsack with a given capacity.
• Matrix Chain Multiplication: Optimizes the order of matrix multiplication to minimize
the number of operations.
• Fibonacci Sequence: Calculates the nth Fibonacci number.
Knapsack Problem
The Knapsack problem is an example of the combinational optimization problem. This
problem is also commonly known as the "Rucksack Problem". The name of the problem is
defined from the maximization problem as mentioned below:
Given a bag with maximum weight capacity of W and a set of items, each having a weight
and a value associated with it. Decide the number of each item to take in a collection such
that the total weight is less than the capacity and the total value is maximized.
28
1. Fractional Knapsack Problem
The Fractional Knapsack problem can be defined as follows:
Given the weights and values of N items, put these items in a knapsack of capacity W to
get the maximum total value in the knapsack. In Fractional Knapsack, we can break items
for maximizing the total value of the knapsack.
Given two arrays, val[] and wt[], representing the values and weights of item respectively,
and an integer capacity representing the maximum weight a knapsack can hold, we have to
determine the maximum total value that can be achieved by putting the items in the
knapsack without exceeding its capacity.
Items can also be taken in fractional parts if required.
Examples:
Input: val[] = [60, 100, 120], wt[] = [10, 20, 30], capacity = 50
Output: 240
Explanation: We will take the items of weight 10kg and 20kg and 2/3 fraction of 30kg.
Hence total value will be 60 + 100 + (2/3) * 120 = 240.
Input: val[] = [500], wt[] = [30], capacity = 10
Output: 166.667
29
Given n items where each item has some weight and profit associated with it and also given
a bag with capacity W, [i.e., the bag can hold at most W weight in it]. The task is to put
the items into the bag such that the sum of profits associated with them is the maximum
possible.
Note: The constraint here is we can either put an item completely into the bag or cannot
put it at all [It is not possible to put a part of an item into the bag].
Input: W = 4, profit[] = [1, 2, 3], weight[] = [4, 5, 1]
Output: 3
Explanation: There are two items which have weight less than or equal to 4. If we select
the item with weight 4, the possible profit is 1. And if we select the item with weight 1, the
possible profit is 3. So the maximum possible profit is 3. Note that we cannot put both the
items with weight 4 and 1 together as the capacity of the bag is 4.
Input: W = 3, profit[] = [1, 2, 3], weight[] = [4, 5, 6]
Output: 0
Examples:
Input: N = 5, P[] = {2, 7, 1, 5, 3}, C[] = {2, 5, 2, 3, 4}, W = 8, K = 2.
Output: 12
Explanation:
Here, the maximum possible profit is when we take 2 items: item2 (P[1] = 7 and C[1] = 5)
and item4 (P[3] = 5 and C[3] = 3).
Hence, maximum profit = 7 + 5 = 12
Input: N = 5, P[] = {2, 7, 1, 5, 3}, C[] = {2, 5, 2, 3, 4}, W = 1, K = 2
Output: 0
Explanation: All weights are greater than 1. Hence, no item can be picked.
30
Approach: The dynamic programming approach is preferred over the general recursion
approach. Let us first verify that the conditions of DP are still satisfied.
1. Overlapping sub-problems: When the recursive solution is tried, 1 item is added
first and the solution set is (1), (2), ...(n). In the second iteration we have (1, 2) and
so on where (1) and (2) are recalculated. Hence there will be overlapping solutions.
2. Optimal substructure: Overall, each item has only two choices, either it can be
included in the solution or denied. For a particular subset of z elements, the solution
for (z+1)th element can either have a solution corresponding to the z elements or
the (z+1)th element can be added if it doesn't exceed the knapsack constraints.
Either way, the optimal substructure property is satisfied.
Approach: The idea here is to just find the item which has the largest value to weight ratio.
Then fill the whole knapsack with this item only, in order to maximize the final value of
the knapsack.
31
Variations of Knapsack Problem:
There are several variations possible for the Knapsack Problem. Some of the well-known
variations are provided below:
32
Applications of the Knapsack Problem:
The Knapsack problem has several real-life applications. Some of them are mentioned
here:
• One of the early applications of the Knapsack problem was in construction and
scoring of exams in which the test takers have a choice as to which questions they
answer.
• The subset sum problem is solved using the concept of the Knapsack problem.
• The multiple objective variations of the Knapsack problem is frequently used for
transportation logistics optimization problems.
• The multiple knapsack problem is often used in many loading and scheduling
algorithms in Operational Research.
33
QUESTION BANK
34
26. What is Dynamic Programming, and what is its core idea?
27. What are the two properties that a problem must have to be solved using Dynamic
Programming?
28. Differentiate between the Top-Down (Memoization) and Bottom-Up (Tabulation)
approaches of Dynamic Programming.
29. State the Knapsack Problem.
30. What is the key difference between the 0/1 Knapsack Problem and the Fractional
Knapsack Problem?
Programs:
1. Write a C program to implement Merge Sort for an array of integers.
2. Write a C program to implement Heap Sort for an array of integers.
3. Write a C program to find an element in a sorted array using an iterative Binary Search.
4. Write a C program to find an element in a sorted array using a recursive Binary Search.
5. Write a C program to find an element in a sorted array using a recursive Ternary Search.
6. Write a C program to find an element in a sorted array using an iterative Ternary Search.
7. Write a C program to find the nth Fibonacci number using recursion (brute force).
8. Write a C program to find the nth Fibonacci number using a Top-Down Dynamic
Programming (Memoization) approach.
9. Write a C program to find the nth Fibonacci number using a Bottom-Up Dynamic
Programming (Tabulation) approach.
10. Write a C program to solve the 0/1 Knapsack problem using recursion.
11. Write a C program to solve the 0/1 Knapsack problem using dynamic programming.
12. Write a C program to find the Longest Common Subsequence (LCS) of two strings
using recursion.
13. Write a C program to find the Longest Common Subsequence (LCS) of two strings
using dynamic programming.
14. Write a C program to demonstrate the fractional knapsack problem.
15. Write a C program to implement Matrix Chain Multiplication using dynamic
programming.
35