Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

QuickSort

Key charateristics of QuickSort

  • Time Complexity:
    • Average case:
    • Worst case: - rare with good pivot selection
    • Best case:
  • Space Complexity:
    • Average case: for the recursive call stack
    • Best Case:
    • Worst case:

sorts in place (modifies in original array)

def quicksort_inplace(arr, low, high):
    def partition(low, high):
        pivot = arr[high]
        i = low - 1  # index of smaller element
        
        for j in range(low, high):
            # If current element is smaller than or equal to pivot
            if arr[j] <= pivot:
                i += 1  # increment index of smaller element
                arr[i], arr[j] = arr[j], arr[i]
        
        arr[i + 1], arr[high] = arr[high], arr[i + 1]
        return i + 1

    if low < high:
        # pi is partitioning index
        pi = partition(low, high)
        
        # Separately sort elements before and after partition
        quicksort_inplace(arr, low, pi - 1)
        quicksort_inplace(arr, pi + 1, high)

# Usage
arr = [7, 2, 1, 6, 8, 5, 3, 4]
quicksort_inplace(arr, 0, len(arr)-1)

Calculate QuickSort's time complexity step by step.

First, let's understand what happens at each partition step:

def partition(arr,low,high):
    pivot = arr[high]  # @1 O(1) .$ \mathcal{O}(1)$
    i = low - 1 #  @1 O(1)

    for j in range(low,high):  # @2 O(n) .$ \mathcal{O}(n) $
        if arr[j] <= pivot:
            i += 1
            arr[i], arr[j] = arr[j], arr[i]
    arr[i+1], arr[high] = arr[high], arr[i+1] # @1 O(1)
    return i+1
  • 1.
  • 2.

Let's analyze different cases:

Best Case:

Initial array: [5,2,8,1,9,3]
Perfect partition each time:
Level 1:        [2,1,3] [5] [8,9]    → n comparisons
Level 2:        [1] [2] [3]  [8] [9]  → n/2 + n/2 comparisons
Depth: log n levels

Time Complexity = O(n log n)

Average Case:

Each partition splits array into roughly equal parts:
T(n) = T(n/2) + T(n/2) + n  (partition cost)
     = 2T(n/2) + n

Using Master Theorem:
a = 2 (subproblems)
b = 2 (size reduction)
k = 1 (partition work)

Time Complexity = O(n log n)

Worst Case:

Already sorted array: [1,2,3,4,5]
Uneven partitions each time:
Level 1: [] [1] [2,3,4,5]    → n comparisons
Level 2:    [] [2] [3,4,5]   → (n-1) comparisons
Level 3:       [] [3] [4,5]  → (n-2) comparisons

Sum = n + (n-1) + (n-2) + ... + 1
    = n(n+1)/2
Time Complexity = O(n²)

Mathematical Breakdown:

For average case:

T(n) = 2T(n/2) + n

Recursion tree:
Level 0:                n
Level 1:           n/2     n/2
Level 2:      n/4   n/4  n/4   n/4

Work at each level = n
Number of levels = log n
Total work = n * log n
  • Work at each level
  • Number of levels
  • Total work

Detailed breakdown of operations:

def quicksort(arr, low, high):
    if low < high:
        # Partition operation: O(n)
        pivot_index = partition(arr, low, high)

        # Recursive calls
        quicksort(arr, low, pivot_index - 1)   # T(k)

Recurrence relation:

  • Best/Average:
  • Worst:

Factors affecting time complexity:

  • Pivot selection strategy
# Better pivot selection to avoid worst case
def get_pivot(arr, low, high):
    mid = (low + high) // 2
    pivot = median([arr[low], arr[mid], arr[high]])
    return pivot
  • Data distribution
  • Initial array ordering
  • Handling of duplicate elements

Chapter 1

Here is an inline example, ,

Here is an inline example, ,

an equation,

and a regular $ symbol.