Skip to content

07: Quick Sort & Pivot Strategies

Quick Sort & Pivot Strategies Intermediate

Quick Sort is one of the most important and widely used sorting algorithms. Despite having O(n²) worst-case complexity, it's typically faster than other O(n log n) algorithms in practice due to excellent cache locality and low overhead. In this chapter, we'll master Quick Sort and learn how to optimize it with smart pivot selection.

What You'll Learn

Estimated time: 65 minutes

By the end of this chapter, you will:

  • Implement Quick Sort using the divide-and-conquer partitioning approach
  • Master different pivot selection strategies (first, last, middle, random, median-of-three)
  • Understand Quick Sort's O(n log n) average case and O(n²) worst case scenarios
  • Learn optimization techniques including tail recursion and hybrid approaches
  • Apply Quick Sort to real-world scenarios and understand why it's often faster than Merge Sort

Prerequisites

Before starting this chapter, ensure you have:

  • ✓ Understanding of recursion (70 mins from Chapter 3 if not done)
  • ✓ Understanding of Big O notation (60 mins from Chapter 1 if not done)
  • ✓ Completion of Chapters 05-06 (115 mins if not done)

Quick Checklist

Complete these hands-on tasks as you work through the chapter:

  • [ ] Implement basic Quick Sort with the partition function
  • [ ] Try different pivot strategies: first element, last element, middle element
  • [ ] Implement median-of-three pivot selection for better performance
  • [ ] Add random pivot selection to avoid worst-case scenarios
  • [ ] Benchmark Quick Sort against Merge Sort on various datasets
  • [ ] Implement hybrid Quick Sort that switches to Insertion Sort for small subarrays
  • [ ] (Optional) Implement three-way partitioning for handling duplicate elements

How Quick Sort Works

Quick Sort uses a divide-and-conquer strategy:

  1. Pick a pivot element from the array
  2. Partition the array so elements smaller than pivot are on the left, larger on the right
  3. Recursively sort the left and right partitions

Key insight: After partitioning, the pivot is in its final sorted position!

Detailed Example with Step-by-Step Partition

Sort [8, 3, 1, 7, 0, 10, 2] using last element as pivot:

Initial Array: [8, 3, 1, 7, 0, 10, 2], pivot = 2

Partition Process:

i = -1 (tracks boundary of smaller elements)

j=0: arr[0]=8, 8 < 2? No → skip
     [8, 3, 1, 7, 0, 10, 2]  i=-1

j=1: arr[1]=3, 3 < 2? No → skip
     [8, 3, 1, 7, 0, 10, 2]  i=-1

j=2: arr[2]=1, 1 < 2? Yes → i++, swap arr[0] with arr[2]
     [1, 3, 8, 7, 0, 10, 2]  i=0

j=3: arr[3]=7, 7 < 2? No → skip
     [1, 3, 8, 7, 0, 10, 2]  i=0

j=4: arr[4]=0, 0 < 2? Yes → i++, swap arr[1] with arr[4]
     [1, 0, 8, 7, 3, 10, 2]  i=1

j=5: arr[5]=10, 10 < 2? No → skip
     [1, 0, 8, 7, 3, 10, 2]  i=1

Place pivot: swap arr[2] with pivot
     [1, 0, 2, 7, 3, 10, 8]  pivot at index 2

Result: All elements < 2 are on left, all > 2 are on right

Recursion:

  • Left: [1, 0] → pivot=0 → [0, 1]
  • Right: [7, 3, 10, 8] → pivot=8 → ... → [3, 7, 8, 10]

Final: [0, 1, 2, 3, 7, 8, 10]

Animation Concept: Imagine a dividing wall moving through the array. Elements smaller than pivot jump over the wall to the left, while larger elements stay on the right. The pivot then slides into position at the wall.

Basic Implementation

php
function quickSort(array $arr): array
{
    // Base case: arrays with 0 or 1 element are already sorted
    if (count($arr) < 2) {
        return $arr;
    }

    // Choose pivot (simple: use first element)
    $pivot = $arr[0];
    $left = [];
    $right = [];

    // Partition: elements < pivot go left, >= pivot go right
    for ($i = 1; $i < count($arr); $i++) {
        if ($arr[$i] < $pivot) {
            $left[] = $arr[$i];
        } else {
            $right[] = $arr[$i];
        }
    }

    // Recursively sort and combine
    return array_merge(
        quickSort($left),
        [$pivot],
        quickSort($right)
    );
}

$numbers = [8, 3, 1, 7, 0, 10, 2];
print_r(quickSort($numbers));
// Output: [0, 1, 2, 3, 7, 8, 10]

Note: This implementation is easy to understand but not space-efficient (creates new arrays). Let's build an in-place version.

In-Place Quick Sort

In-place sorting uses O(1) extra space by partitioning within the original array:

php
function quickSortInPlace(array &$arr, int $low = 0, int $high = null): void
{
    if ($high === null) {
        $high = count($arr) - 1;
    }

    if ($low < $high) {
        // Partition and get pivot index
        $pivotIndex = partition($arr, $low, $high);

        // Recursively sort left and right partitions
        quickSortInPlace($arr, $low, $pivotIndex - 1);
        quickSortInPlace($arr, $pivotIndex + 1, $high);
    }
}

function partition(array &$arr, int $low, int $high): int
{
    // Choose last element as pivot
    $pivot = $arr[$high];
    $i = $low - 1; // Index of smaller element

    for ($j = $low; $j < $high; $j++) {
        // If current element is smaller than pivot
        if ($arr[$j] < $pivot) {
            $i++;
            // Swap arr[i] and arr[j]
            [$arr[$i], $arr[$j]] = [$arr[$j], $arr[$i]];
        }
    }

    // Place pivot in correct position
    [$arr[$i + 1], $arr[$high]] = [$arr[$high], $arr[$i + 1]];

    return $i + 1;
}

// Usage
$numbers = [8, 3, 1, 7, 0, 10, 2];
quickSortInPlace($numbers);
print_r($numbers);
// Output: [0, 1, 2, 3, 7, 8, 10]

Understanding the Partition Algorithm

The partition process moves elements around the pivot:

php
function partitionVisualized(array &$arr, int $low, int $high): int
{
    $pivot = $arr[$high];
    echo "Pivot: $pivot\n";
    echo "Initial: [" . implode(', ', array_slice($arr, $low, $high - $low + 1)) . "]\n";

    $i = $low - 1;

    for ($j = $low; $j < $high; $j++) {
        if ($arr[$j] < $pivot) {
            $i++;
            echo "  Swap {$arr[$i]} and {$arr[$j]}\n";
            [$arr[$i], $arr[$j]] = [$arr[$j], $arr[$i]];
        }
    }

    // Place pivot
    [$arr[$i + 1], $arr[$high]] = [$arr[$high], $arr[$i + 1]];
    echo "After partition: [" . implode(', ', array_slice($arr, $low, $high - $low + 1)) . "]\n";
    echo "Pivot at index: " . ($i + 1) . "\n\n";

    return $i + 1;
}

Complexity Analysis

ScenarioTime ComplexitySpace ComplexityWhy?
Best caseO(n log n)O(log n)Pivot divides array evenly
Average caseO(n log n)O(log n)Random pivots generally balanced
Worst caseO(n²)O(n)Pivot is always smallest/largest
StableNo*-Equal elements may be reordered

*Can be made stable with extra space

Why O(n log n) in Best/Average Case?

  1. Balanced Partitioning:

    • Good pivot divides array roughly in half
    • Results in log n levels of recursion
    • Each level processes all n elements
  2. Visual Recursion Tree (Best Case):

                    [n elements]           ← n work
                   /            \
            [n/2]              [n/2]       ← n work total
           /     \            /     \
        [n/4]  [n/4]      [n/4]  [n/4]    ← n work total
         ...    ...        ...    ...

        Levels: log₂(n)
        Work per level: n
        Total: n × log n
  1. Example with 8 elements:
    • Level 0: 1 partition of 8 = 8 comparisons
    • Level 1: 2 partitions of 4 = 8 comparisons
    • Level 2: 4 partitions of 2 = 8 comparisons
    • Levels: log₂(8) = 3
    • Total: 3 × 8 = 24 comparisons = O(n log n)

Why O(n²) in Worst Case?

  1. Unbalanced Partitioning:

    • Pivot is always minimum or maximum
    • Array divided into [0] and [n-1] elements
    • Results in n levels instead of log n
  2. Visual Recursion Tree (Worst Case):

        [n]              ← n work
         \
          [n-1]          ← n-1 work
           \
            [n-2]        ← n-2 work
             \
              ...
               \
                [1]      ← 1 work

        Levels: n
        Total work: n + (n-1) + (n-2) + ... + 1
                  = n(n+1)/2 ≈ n²/2 = O(n²)
  1. Example: Already Sorted with First Element Pivot
[1, 2, 3, 4, 5]  pivot=1
   → [1] | [2, 3, 4, 5]  ← unbalanced!

[2, 3, 4, 5]  pivot=2
   → [2] | [3, 4, 5]      ← still unbalanced!

Result: n levels, n² total work

Space Complexity:

  • Best/Average: O(log n) - balanced recursion depth
  • Worst: O(n) - maximum recursion depth
  • In-place version: Only recursion stack, no extra arrays

Cache Locality: Quick sort has excellent cache performance because:

  • Partitioning scans array sequentially
  • Elements are swapped in-place
  • Better cache hit rates than merge sort
  • Fewer memory allocations

Pivot Selection Strategies

The pivot choice dramatically affects performance. Let's explore different strategies:

Strategy 1: First Element (Poor)

php
function quickSortFirstPivot(array &$arr, int $low, int $high): void
{
    if ($low < $high) {
        $pivotIndex = partitionFirst($arr, $low, $high);
        quickSortFirstPivot($arr, $low, $pivotIndex - 1);
        quickSortFirstPivot($arr, $pivotIndex + 1, $high);
    }
}

function partitionFirst(array &$arr, int $low, int $high): int
{
    // Swap first with last to use it as pivot
    [$arr[$low], $arr[$high]] = [$arr[$high], $arr[$low]];
    return partition($arr, $low, $high);
}

Problem: O(n²) on already sorted or reverse sorted arrays!

Strategy 2: Random Pivot (Good)

php
function quickSortRandom(array &$arr, int $low, int $high): void
{
    if ($low < $high) {
        $pivotIndex = partitionRandom($arr, $low, $high);
        quickSortRandom($arr, $low, $pivotIndex - 1);
        quickSortRandom($arr, $pivotIndex + 1, $high);
    }
}

function partitionRandom(array &$arr, int $low, int $high): int
{
    // Pick random index as pivot
    $randomIndex = rand($low, $high);

    // Swap with last position
    [$arr[$randomIndex], $arr[$high]] = [$arr[$high], $arr[$randomIndex]];

    return partition($arr, $low, $high);
}

Advantage: Average O(n log n) even on sorted data!

Strategy 3: Median-of-Three (Best)

Choose median of first, middle, and last elements:

php
function quickSortMedianOfThree(array &$arr, int $low, int $high): void
{
    if ($low < $high) {
        $pivotIndex = partitionMedianOfThree($arr, $low, $high);
        quickSortMedianOfThree($arr, $low, $pivotIndex - 1);
        quickSortMedianOfThree($arr, $pivotIndex + 1, $high);
    }
}

function partitionMedianOfThree(array &$arr, int $low, int $high): int
{
    $mid = (int)(($low + $high) / 2);

    // Order first, middle, last
    if ($arr[$mid] < $arr[$low]) {
        [$arr[$low], $arr[$mid]] = [$arr[$mid], $arr[$low]];
    }
    if ($arr[$high] < $arr[$low]) {
        [$arr[$low], $arr[$high]] = [$arr[$high], $arr[$low]];
    }
    if ($arr[$high] < $arr[$mid]) {
        [$arr[$mid], $arr[$high]] = [$arr[$high], $arr[$mid]];
    }

    // Now: arr[low] <= arr[mid] <= arr[high]
    // Use middle element as pivot
    [$arr[$mid], $arr[$high]] = [$arr[$high], $arr[$mid]];

    return partition($arr, $low, $high);
}

Advantage: Better pivot selection leads to more balanced partitions!

Pivot Strategy Comparison Table

StrategyBest CaseWorst CaseAvg CaseBest ForWorst For
First ElementO(n log n)O(n²)O(n log n)Random dataSorted data
Last ElementO(n log n)O(n²)O(n log n)Random dataSorted data
RandomO(n log n)O(n²)*O(n log n)All patternsVery rare
Median-of-ThreeO(n log n)O(n²)**O(n log n)Most patternsAdversarial
Median-of-MediansO(n log n)O(n log n)O(n log n)GuaranteedHigh overhead

*Extremely unlikely with random pivots **Rare with median-of-three

Performance Comparison: Pivot Strategies

Test: Sorting 10,000 elements with different pivot strategies

Data PatternFirst PivotRandom PivotMedian-of-Three
Random15ms14ms12ms
Sorted5000ms ⚠️15ms12ms
Reverse5000ms ⚠️15ms12ms
Nearly Sorted200ms18ms13ms
Many Duplicates100ms20ms15ms
All Equal5000ms ⚠️5000ms ⚠️5000ms ⚠️

Key Insights:

  • First/Last pivot: Disastrous on sorted data (O(n²))
  • Random pivot: Reliable for most patterns
  • Median-of-three: Best overall, handles most patterns well
  • All equal: All strategies struggle (use 3-way partitioning!)

Edge Cases and Special Scenarios

Edge Case 1: Empty or Single Element

php
quickSort([], 0, -1);        // No work needed
quickSort([42], 0, 0);       // Already sorted

Edge Case 2: Two Elements

php
$arr = [5, 2];
quickSort($arr, 0, 1);
// One partition: pivot=2, swap → [2, 5]

Edge Case 3: Already Sorted Array (Worst Case with Poor Pivot)

php
$sorted = [1, 2, 3, 4, 5];
// With first element pivot: O(n²)!
// Partitions: [1] | [2,3,4,5] → [2] | [3,4,5] → ...
// Use random or median-of-three to avoid!

Edge Case 4: All Equal Elements (Worst Case)

php
$equal = [5, 5, 5, 5, 5];
// Standard quick sort: O(n²)
// Every partition: [5] | [5,5,5,5]
// Solution: Use 3-way partitioning!

Edge Case 5: Array with Many Duplicates

php
$duplicates = [3, 5, 3, 7, 3, 5, 3];
// Standard: Inefficient, many equal comparisons
// 3-way partitioning: Groups equal elements, O(n log k)
// where k = number of distinct elements

Edge Case 6: Reverse Sorted Array

php
$reverse = [5, 4, 3, 2, 1];
// With last element pivot: O(n²)
// Partitions: [1] | [5,4,3,2] → [2] | [5,4,3] → ...
// Solution: Random or median-of-three pivot

Edge Case 7: Nearly Sorted with One Swap

php
$nearly = [1, 2, 3, 10, 5, 6, 7, 8, 9];
// Most pivots perform well
// Median-of-three: Optimal ~O(n log n)

Optimizations

Optimization 1: Switch to Insertion Sort for Small Subarrays

php
function quickSortOptimized(array &$arr, int $low, int $high): void
{
    // Use insertion sort for small subarrays
    if ($high - $low < 10) {
        insertionSortRange($arr, $low, $high);
        return;
    }

    if ($low < $high) {
        $pivotIndex = partitionMedianOfThree($arr, $low, $high);
        quickSortOptimized($arr, $low, $pivotIndex - 1);
        quickSortOptimized($arr, $pivotIndex + 1, $high);
    }
}

function insertionSortRange(array &$arr, int $low, int $high): void
{
    for ($i = $low + 1; $i <= $high; $i++) {
        $key = $arr[$i];
        $j = $i - 1;

        while ($j >= $low && $arr[$j] > $key) {
            $arr[$j + 1] = $arr[$j];
            $j--;
        }

        $arr[$j + 1] = $key;
    }
}

Optimization 2: Three-Way Partitioning (Dutch National Flag)

Handle duplicate elements efficiently:

php
function quickSort3Way(array &$arr, int $low, int $high): void
{
    if ($low >= $high) return;

    [$lt, $gt] = partition3Way($arr, $low, $high);

    // Elements equal to pivot are in arr[lt..gt]
    quickSort3Way($arr, $low, $lt - 1);
    quickSort3Way($arr, $gt + 1, $high);
}

function partition3Way(array &$arr, int $low, int $high): array
{
    $pivot = $arr[$low];
    $lt = $low;      // arr[low..lt-1] < pivot
    $i = $low + 1;   // arr[lt..i-1] == pivot
    $gt = $high;     // arr[gt+1..high] > pivot

    while ($i <= $gt) {
        if ($arr[$i] < $pivot) {
            [$arr[$lt], $arr[$i]] = [$arr[$i], $arr[$lt]];
            $lt++;
            $i++;
        } elseif ($arr[$i] > $pivot) {
            [$arr[$i], $arr[$gt]] = [$arr[$gt], $arr[$i]];
            $gt--;
        } else {
            $i++;
        }
    }

    return [$lt, $gt];
}

// Excellent for arrays with many duplicates!
$numbers = [5, 2, 8, 2, 9, 1, 5, 5];
quickSort3Way($numbers, 0, count($numbers) - 1);

Optimization 3: Tail Call Optimization

php
function quickSortTailOptimized(array &$arr, int $low, int $high): void
{
    while ($low < $high) {
        // Use median-of-three
        $pivotIndex = partitionMedianOfThree($arr, $low, $high);

        // Recurse on smaller partition, iterate on larger
        if ($pivotIndex - $low < $high - $pivotIndex) {
            quickSortTailOptimized($arr, $low, $pivotIndex - 1);
            $low = $pivotIndex + 1; // Tail recursion eliminated
        } else {
            quickSortTailOptimized($arr, $pivotIndex + 1, $high);
            $high = $pivotIndex - 1; // Tail recursion eliminated
        }
    }
}

Visualizing Quick Sort

php
function quickSortVisualized(array &$arr, int $low, int $high, int $depth = 0): void
{
    $indent = str_repeat('  ', $depth);

    if ($low < $high) {
        echo $indent . "Sorting: [" . implode(', ', array_slice($arr, $low, $high - $low + 1)) . "]\n";

        $pivotIndex = partition($arr, $low, $high);

        echo $indent . "After partition (pivot={$arr[$pivotIndex]}): [" .
             implode(', ', array_slice($arr, $low, $high - $low + 1)) . "]\n\n";

        quickSortVisualized($arr, $low, $pivotIndex - 1, $depth + 1);
        quickSortVisualized($arr, $pivotIndex + 1, $high, $depth + 1);
    }
}

$numbers = [8, 3, 1, 7, 0, 10, 2];
quickSortVisualized($numbers, 0, count($numbers) - 1);
print_r($numbers);

Quick Sort vs Merge Sort: Comprehensive Comparison

FeatureQuick SortMerge SortWinner
Average timeO(n log n)O(n log n)Tie
Worst timeO(n²)*O(n log n)Merge
Best timeO(n log n)O(n log n)Tie
SpaceO(log n)O(n)Quick
In-placeYesNoQuick
StableNo**YesMerge
Cache localityExcellentGoodQuick
Typical speedFaster (2-3x)SlowerQuick
PredictableNoYesMerge
Adaptive*Yes (with optimizations)NoQuick
ParallelizableModerateExcellentMerge

*Rare with good pivot selection **Can be made stable with extra space ***Adapts well to partially sorted data with optimizations

Performance Benchmarks: Quick vs Merge

Test: Various array sizes with random data

Array SizeQuick SortQuick (Optimized)Merge SortWinner
1000.05ms0.03ms0.08msQuick Opt
1,0000.6ms0.4ms1.2msQuick Opt
10,0008ms5ms15msQuick Opt
100,00095ms62ms180msQuick Opt
1,000,0001.1s720ms2.1sQuick Opt

Optimized Quick Sort includes:

  • Median-of-three pivot
  • Insertion sort for small subarrays (< 16)
  • 3-way partitioning for duplicates
  • Tail recursion optimization

When to Use Each

Use Quick Sort When:

  • ✅ General-purpose sorting (most common choice)
  • ✅ Average case performance matters most
  • ✅ Space is limited (in-place sorting)
  • ✅ Cache performance is critical
  • ✅ Data has few duplicates
  • ✅ Need fastest practical performance

Use Merge Sort When:

  • ✅ Need guaranteed O(n log n) (no worst case)
  • ✅ Stability is absolutely required
  • ✅ Sorting linked lists (natural fit)
  • ✅ External sorting (data doesn't fit in memory)
  • ✅ Parallel sorting (easy to parallelize)
  • ✅ Predictable performance is critical

Real-World Usage:

  • C++ std::sort: Uses Introsort (Quick + Heap fallback)
  • Java Arrays.sort: Quick sort for primitives, merge for objects
  • Python sorted(): Timsort (Merge + Insertion hybrid)
  • PHP sort(): Quick sort variant with optimizations

Why Quick Sort is Usually Faster

  1. Cache Locality:

    • Partitioning is sequential, cache-friendly
    • Merge sort creates temporary arrays (cache misses)
  2. In-Place:

    • No memory allocation overhead
    • Better for large datasets
  3. Fewer Comparisons:

    • Typically 30-40% fewer comparisons than merge sort
    • Better constant factors
  4. Adaptability:

    • Can be optimized for specific patterns
    • Hybrid approaches combine strengths

Practical Applications

1. Finding Kth Smallest Element (QuickSelect)

php
function quickSelect(array &$arr, int $k): mixed
{
    return quickSelectHelper($arr, 0, count($arr) - 1, $k - 1);
}

function quickSelectHelper(array &$arr, int $low, int $high, int $k): mixed
{
    if ($low === $high) {
        return $arr[$low];
    }

    $pivotIndex = partitionRandom($arr, $low, $high);

    if ($k === $pivotIndex) {
        return $arr[$k];
    } elseif ($k < $pivotIndex) {
        return quickSelectHelper($arr, $low, $pivotIndex - 1, $k);
    } else {
        return quickSelectHelper($arr, $pivotIndex + 1, $high, $k);
    }
}

$numbers = [7, 10, 4, 3, 20, 15];
echo "3rd smallest: " . quickSelect($numbers, 3); // Output: 7
// Average O(n) instead of O(n log n) for sorting!

2. Sorting Objects by Multiple Criteria

php
class Product
{
    public function __construct(
        public string $name,
        public float $price,
        public int $rating
    ) {}
}

function quickSortProducts(array &$products, int $low, int $high): void
{
    if ($low < $high) {
        $pivotIndex = partitionProducts($products, $low, $high);
        quickSortProducts($products, $low, $pivotIndex - 1);
        quickSortProducts($products, $pivotIndex + 1, $high);
    }
}

function partitionProducts(array &$products, int $low, int $high): int
{
    $pivot = $products[$high];
    $i = $low - 1;

    for ($j = $low; $j < $high; $j++) {
        // Sort by rating (descending), then price (ascending)
        if ($products[$j]->rating > $pivot->rating ||
            ($products[$j]->rating === $pivot->rating &&
             $products[$j]->price < $pivot->price)) {
            $i++;
            [$products[$i], $products[$j]] = [$products[$j], $products[$i]];
        }
    }

    [$products[$i + 1], $products[$high]] = [$products[$high], $products[$i + 1]];
    return $i + 1;
}

$products = [
    new Product('A', 10.99, 4),
    new Product('B', 15.99, 5),
    new Product('C', 12.99, 4),
];

quickSortProducts($products, 0, count($products) - 1);

Benchmarking Pivot Strategies

php
require_once 'Benchmark.php';

$bench = new Benchmark();
$sizes = [1000, 5000, 10000];

foreach ($sizes as $size) {
    echo "Array size: $size\n";

    // Random data
    $random = range(1, $size);
    shuffle($random);

    $bench->compare([
        'First Pivot' => function($arr) {
            quickSortFirstPivot($arr, 0, count($arr) - 1);
        },
        'Random Pivot' => function($arr) {
            quickSortRandom($arr, 0, count($arr) - 1);
        },
        'Median-of-Three' => function($arr) {
            quickSortMedianOfThree($arr, 0, count($arr) - 1);
        },
    ], $random, iterations: 10);

    echo "\n";
}

Common Mistakes

Mistake 1: Not Handling Single Element

php
// Wrong: infinite recursion on single element
if (count($arr) == 0) return $arr;

// Correct
if (count($arr) < 2) return $arr;

Mistake 2: Including Pivot in Both Partitions

php
// Wrong: pivot included twice
quickSort($arr, $low, $pivotIndex);     // Includes pivot
quickSort($arr, $pivotIndex, $high);    // Includes pivot again

// Correct: exclude pivot
quickSort($arr, $low, $pivotIndex - 1);
quickSort($arr, $pivotIndex + 1, $high);

Mistake 3: Poor Pivot for Sorted Data

Always use randomized or median-of-three pivots to avoid O(n²) on sorted data.

Practice Exercises

Exercise 1: Sort Colors (Dutch National Flag)

Sort an array of 0s, 1s, and 2s in one pass:

php
function sortColors(array &$nums): void
{
    // Your code here (use 3-way partitioning)
}

$colors = [2, 0, 2, 1, 1, 0];
sortColors($colors);
// Result: [0, 0, 1, 1, 2, 2]

Exercise 2: Nuts and Bolts Problem

Match nuts and bolts of different sizes:

php
function matchNutsAndBolts(array &$nuts, array &$bolts): void
{
    // Your code here (modify quick sort)
}

Exercise 3: Wiggle Sort

Rearrange array so arr[0] <= arr[1] >= arr[2] <= arr[3]...:

php
function wiggleSort(array &$nums): void
{
    // Your code here
}

Key Takeaways

  • Quick Sort is O(n log n) average, O(n²) worst case
  • Pivot selection is crucial for performance
  • Median-of-three or random pivot avoids worst case
  • In-place sorting with O(log n) space
  • Three-way partitioning excellent for duplicates
  • Quick Select finds kth element in O(n) average
  • Generally faster than merge sort in practice
  • Not stable, but cache-efficient

What's Next

In the next chapter, we'll explore Heap Sort & Priority Queues, learning about the heap data structure and how to use it for efficient sorting and priority management.

💻 Code Samples

All code examples from this chapter are available in the GitHub repository:

View Chapter 07 Code Samples

Files included:

  • 01-quick-sort-basic.php - Basic quick sort implementation with visualization and performance analysis
  • 02-pivot-strategies.php - Comparison of pivot selection strategies (last element, random, median-of-three)
  • 03-quick-sort-optimized.php - Optimized quick sort with 3-way partitioning and hybrid approach
  • 04-quick-select.php - QuickSelect algorithm for finding kth smallest element
  • README.md - Complete documentation and usage guide

Clone the repository to run the examples locally:

bash
git clone https://github.com/dalehurley/codewithphp.git
cd codewithphp/code/php-algorithms/chapter-07
php 01-quick-sort-basic.php

Continue to Chapter 08: Heap Sort & Priority Queues.