Merge Sort in C++ with O(n log n) Time Complexity


In this video lesson we will study how the Merge Sort algorithm works, we will analyze its time and space complexities and then we will implement it:


Problem - Merge Sort:

Given an array of integers nums, sort it in ascending order using Merge Sort

Example 1:

Input: nums = [3, 1, 3, 2, 5, 4]
Output: [1, 2, 3, 3, 4, 5]

Note:

Your algorithm should run in O(n log n) time and use O(n) extra space.


Understanding the Problem

The core challenge of this problem is to sort an array of integers efficiently. Merge Sort is a classic sorting algorithm that uses the divide-and-conquer strategy to achieve this. It is significant because it guarantees a time complexity of O(n log n), making it suitable for large datasets. Common applications include sorting data in databases, file systems, and more.

Potential pitfalls include misunderstanding the recursive nature of the algorithm and mishandling the merging process, which can lead to incorrect sorting.

Approach

To solve this problem, we can break it down into the following steps:

  1. Divide the array into two halves.
  2. Recursively sort each half.
  3. Merge the two sorted halves into a single sorted array.

A naive solution might involve repeatedly finding the minimum element and moving it to the front, but this would result in a time complexity of O(n^2), which is not efficient for large arrays.

Merge Sort, on the other hand, is more efficient. It divides the array into smaller subarrays, sorts them, and then merges them back together. This approach ensures that the sorting process is efficient and scalable.

Algorithm

Here is a step-by-step breakdown of the Merge Sort algorithm:

  1. Divide the array into two halves.
  2. Recursively sort each half.
  3. Merge the two sorted halves by comparing elements from each half and arranging them in order.

The merging process is crucial as it ensures that the final array is sorted. By repeatedly dividing and merging, we achieve the desired sorted array.

Code Implementation


#include <iostream>
#include <vector>

using namespace std;

// Function to merge two halves
void merge(vector<int>& nums, int left, int mid, int right) {
    int n1 = mid - left + 1;
    int n2 = right - mid;

    // Create temporary arrays
    vector<int> L(n1), R(n2);

    // Copy data to temporary arrays L[] and R[]
    for (int i = 0; i < n1; i++)
        L[i] = nums[left + i];
    for (int j = 0; j < n2; j++)
        R[j] = nums[mid + 1 + j];

    // Merge the temporary arrays back into nums[left..right]
    int i = 0, j = 0, k = left;
    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) {
            nums[k] = L[i];
            i++;
        } else {
            nums[k] = R[j];
            j++;
        }
        k++;
    }

    // Copy the remaining elements of L[], if any
    while (i < n1) {
        nums[k] = L[i];
        i++;
        k++;
    }

    // Copy the remaining elements of R[], if any
    while (j < n2) {
        nums[k] = R[j];
        j++;
        k++;
    }
}

// Function to implement merge sort
void mergeSort(vector<int>& nums, int left, int right) {
    if (left >= right)
        return;

    int mid = left + (right - left) / 2;

    // Recursively sort first and second halves
    mergeSort(nums, left, mid);
    mergeSort(nums, mid + 1, right);

    // Merge the sorted halves
    merge(nums, left, mid, right);
}

int main() {
    vector<int> nums = {3, 1, 3, 2, 5, 4};
    mergeSort(nums, 0, nums.size() - 1);

    // Print the sorted array
    for (int num : nums) {
        cout << num << " ";
    }
    return 0;
}

Complexity Analysis

The time complexity of Merge Sort is O(n log n) because the array is divided in half log n times, and the merging process takes linear time in each division. The space complexity is O(n) due to the temporary arrays used for merging.

Compared to the naive approach, Merge Sort is significantly more efficient, especially for large datasets.

Edge Cases

Potential edge cases include:

Testing these edge cases ensures the robustness of the algorithm.

Testing

To test the solution comprehensively, consider the following test cases:

Using a testing framework like Google Test can help automate and manage these tests effectively.

Thinking and Problem-Solving Tips

When approaching such problems, consider the following tips:

Conclusion

Merge Sort is a powerful and efficient sorting algorithm that guarantees a time complexity of O(n log n). Understanding and implementing this algorithm is crucial for solving sorting problems effectively. By practicing and exploring further, you can enhance your problem-solving skills and tackle more complex challenges.

Additional Resources

For further reading and practice, consider the following resources: