Lecture 07: QuickSort

COSC 311 Algorithms, Fall 2022

$ \def\compare{ {\mathrm{compare}} } \def\swap{ {\mathrm{swap}} } \def\sort{ {\mathrm{sort}} } \def\insert{ {\mathrm{insert}} } \def\true{ {\mathrm{true}} } \def\false{ {\mathrm{false}} } \def\BubbleSort{ {\mathrm{BubbleSort}} } \def\SelectionSort{ {\mathrm{SelectionSort}} } \def\Merge{ {\mathrm{Merge}} } \def\MergeSort{ {\mathrm{MergeSort}} } \def\QuickSort{ {\mathrm{QuickSort}} } \def\Split{ {\mathrm{Split}} } $

Announcements

  1. Homework 1, Exercise 4
  2. Collaboration and Exercise
  3. Homework 2, Posted Sunday

Overview

  1. QuickSort

Picture so Far:

SelectionSort. $O(n^2)$ operations

  • $O(n^2)$ comparisons
  • $O(n)$ swaps

BubbleSort and InsertionSort. $O(n^2)$ operations

  • $O(n^2)$ comparisons
  • $O(n^2)$ swaps

MergeSort. $O(n \log n)$ operations

  • $O(n \log n)$ comparisons
  • $O(n \log n)$ modifications
  • uses $O(n)$ space overhead

Today

QuickSort: Divide by Value

QuickSort: Another D&C Sort

Idea. Divide array $a$ by value

  • choose a value $p$ from $a$, the pivot
  • arrage values of $a$ such that:
    • $p$ is at index $k$
    • values $\leq p$ are at indices $i \leq k$
    • values $> p$ are at indices $j > k$
  • recursively sort indices $i < k$
  • recursively sort indice $j > k$

QuickSort Illustration

QuickSort Pseudocode

QuickSort(a, i, j):
  if j - i <= 1 then
    return
  endif
  p <- GetPivot(a, i, j) # select a pivot
  k <- Split(a, i, j, p)
  QuickSort(a, i, k-1)
  QuickSort(a, k+1, j)

Split Illustration

Split Method

Split(a, i, j, p):
  left <- i, right <- j
  while left < right do
    if a[left] > p and a[right] <= p then
       swap(a, left, right)
       left++, right--
    else
      if a[left] <= p then left++
      if a[right] > p then right--
    endif
  endwhile
  return right

What Is Split Running Time?

QuickSort Pseudocode

QuickSort(a, i, j):
  if j - i <= 1 then
    return
  endif
  p <- GetPivot(a, i, j) # select a pivot
  k <- Split(a, i, j, p)
  QuickSort(a, i, k-1)
  QuickSort(a, k+1, j)

What is Worst-Case QS Running Time?

Assume $\mathrm{GetPivot}(a, i, j)$ returns a value in $a[i..j]$

  • as with $\MergeSort$, total time at each depth of recursion is $O(n)$

When is QS Running Time Better?

Best pivot selection?

Acceptable Pivot Selection?

What if we can’t get the best possible? What might still be acceptable?

A Heuristic

A pivot $p$ is good if its rank is between $\frac 1 4 k$ and $\frac 3 4 k$

  • $\implies$ larger recursive call has size at most $\frac 3 4 k$

Depth with Good Pivots?

Question. How many good pivots until we reach a base case?

Random Pivot Selection

GetPivot(a, i, j):
  k <- RandomInt(i, j)
  return a[k]

Question. How likely is it that a pivot is good?

Heuristic

With random pivot selection

  1. on average, half of pivots are good
  2. on average, depth at most doubles

So running time is $O(n \log n)$ on average

Careful Analysis

Can show. If random pivot is chosen, then on average QuickSort uses $O(n \log n)$ operations

  • Formalizing previous argument is a bit tricky

  • More clever and simpler analyses exist!

Empirical Analysis

Homework 2!

Next Time

  • Lower bounds for sorting

  • Lecture Ticket: Argue that every sorting algorithm uses $\Omega(n)$ operations