Mastering Merge Sort in PHP: A Step-by-Step Guide

Mastering Merge Sort in PHP: A Step-by-Step Guide
Photo by Roman Bürki / Unsplash

Merge Sort is one of the most efficient sorting algorithms, especially useful for handling large datasets due to its predictable $O(n \log n)$ performance. Unlike simpler algorithms, Merge Sort divides the problem, conquers each part recursively, and combines them in sorted order. Today, we'll dive into how Merge Sort works and implement it in PHP.

What is Merge Sort?

The Merge Sort algorithm works by recursively dividing an array into smaller halves until each subarray contains a single element or no elements (making it trivially sorted). Then, it merges these subarrays back together in sorted order. The divide-and-conquer approach makes Merge Sort highly effective, particularly for sorting large datasets that might overwhelm simpler sorting methods like bubble sort or insertion sort.

Pseudocode

function mergeSort(A : Array of Element; lo, hi : Integer)
    if hi - lo <= 1 then 
        return // Base case: already sorted if there is 1 or no element

    mid := (lo + hi) / 2 // Find the midpoint

    mergeSort(A, lo, mid) // Sort the left half
    mergeSort(A, mid, hi) // Sort the right half

    B := allocate(Array of Element, size hi - lo) // Temporary array to store merged results
    i := lo, j := mid, k := 0 // Initialize pointers for left, right, and merged arrays

    while (i < mid) and (j < hi) do
        if A[i] < A[j] then
            B[k] := A[i] // Add element from left half to B
            i := i + 1
        else
            B[k] := A[j] // Add element from right half to B
            j := j + 1
        end if
        k := k + 1
    end while

    // Copy any remaining elements from the left half
    while i < mid do
        B[k] := A[i]
        i := i + 1
        k := k + 1
    end while

    // Copy any remaining elements from the right half
    while j < hi do
        B[k] := A[j]
        j := j + 1
        k := k + 1
    end while

    // Copy sorted elements back into the original array
    for m := 0 to (hi - lo) - 1 do
        A[lo + m] := B[m]
    end for

    dispose(B) // Free the temporary array
end function

How Does Merge Sort Work?

  1. Divide: We split the array into two halves, each half being recursively split until each subarray has just one element.
  2. Merge: Once we have subarrays of one element each, we merge them back together in sorted order. Merging is done by comparing the elements of each subarray and copying the smaller element to a new, temporary array.
  3. Combine: The temporary array is then copied back into the original array, completing the sort for that segment.

One significant advantage of Merge Sort is its stable sorting nature—it maintains the relative order of equal elements, which can be critical in cases where secondary sorting criteria matter.

PHP Implementation of Merge Sort

Let's walk through the PHP implementation, emphasizing the key steps in the divide-and-conquer process.

function mergeSort(array &$array, int $lo, int $hi): void {
    // Base case: if the subarray has 1 or 0 elements, it is already sorted
    if ($hi - $lo <= 1) {
        return;
    }
    
    // Step 1: Divide - Find the middle point to split the array into two halves
    $mid = intdiv($lo + $hi, 2);
    
    // Recursively sort both halves
    mergeSort($array, $lo, $mid); // Left half
    mergeSort($array, $mid, $hi); // Right half
    
    // Step 2: Merge - Create a temporary array to store merged elements
    $temp = [];
    $i = $lo;
    $j = $mid;
    
    // Merge both halves into the temporary array in sorted order
    while ($i < $mid && $j < $hi) {
        if ($array[$i] < $array[$j]) {
            $temp[] = $array[$i++];
        } else {
            $temp[] = $array[$j++];
        }
    }
    
    // Copy any remaining elements from the left subarray
    while ($i < $mid) {
        $temp[] = $array[$i++];
    }
    
    // Copy any remaining elements from the right subarray
    while ($j < $hi) {
        $temp[] = $array[$j++];
    }
    
    // Step 3: Combine - Copy the sorted elements back into the original array
    for ($k = 0; $k < count($temp); $k++) {
        $array[$lo + $k] = $temp[$k];
    }
}

Explanation of Key Sections

In this PHP implementation, we use three main steps to achieve a working Merge Sort.

  1. Base Case: The function begins by checking if the current segment of the array (from lo to hi) has one or zero elements. If it does, we immediately return, as such subarrays are already sorted.
  2. Divide and Recursive Sorting:
    • We calculate the midpoint using intdiv, a safe way to perform integer division in PHP.
    • We call mergeSort recursively on both halves. The recursion will continue dividing until we have subarrays of one element.
  3. Merge:
    • After sorting each half, we merge them into a temporary array ($temp). Here, we compare elements from each subarray and append the smaller one to $temp.
    • If elements remain in either subarray after the main comparison loop, we append those to $temp, ensuring no elements are missed.
  4. Combine:
    • We copy the sorted $temp array back into the original array segment from lo to hi.
    • This process completes the sorting for that segment and allows the parent recursion call to work with fully sorted halves.

Important Considerations

  • Memory Usage: Merge Sort requires additional memory for the temporary array used in merging. In some cases, this extra space is not ideal, but it’s a necessary tradeoff for the stability and performance of Merge Sort.
  • Stability: Merge Sort is stable, meaning it maintains the order of equal elements in the original array, which can be beneficial if you’re sorting objects with multiple criteria.
  • Performance: Merge Sort’s $O(n \log n)$ time complexity makes it efficient for large datasets. Unlike quicksort, which can degrade to $O(n^2)$ in the worst case, Merge Sort is consistently $O(n \log n)$ due to its systematic approach.
  • Usage in PHP: PHP’s native sorting functions (like sort() and usort()) are usually faster due to optimizations in the underlying C implementation. However, understanding and implementing Merge Sort can deepen your understanding of sorting algorithms and be useful in custom situations.

Testing Our Implementation

To test our Merge Sort function, we can create a sample array and call mergeSort on it:

$array = [38, 27, 43, 3, 9, 82, 10];
mergeSort($array, 0, count($array));
print_r($array); // Output: [3, 9, 10, 27, 38, 43, 82]

This example should print the sorted array as expected, showing that Merge Sort works correctly.

💡
Live demo in PHP and Go

Finally

Understanding and implementing Merge Sort offers valuable insights into how divide-and-conquer algorithms operate. Although the function relies on recursion and auxiliary memory, its predictable performance makes it a reliable sorting choice, especially for data that requires stability or large datasets that might be too slow with other methods. PHP provides faster alternatives for most cases, but Merge Sort’s conceptual clarity and consistent time complexity make it a useful algorithm to have in your toolkit.

Support Us