Back to Data Structures & Algorithms
2025-12-295 min read

Quicksort (Data Structures & Algorithms)

Learn Quicksort (Data Structures & Algorithms) step by step with clear examples and exercises.

Why This Matters

Quicksort is one of the most widely used and efficient sorting algorithms in modern computer science. It plays a significant role in real-world applications such as operating systems, databases, and programming languages' standard libraries. Understanding its workings can help you tackle complex problems, optimize your code, and even identify bugs in large programs. Additionally, knowledge of Quicksort is essential during interviews and exams, where understanding efficient algorithms is crucial.

Prerequisites

To follow this lesson, you should have a good grasp of the following concepts:

  1. Python programming basics (variables, functions, loops, recursion)
  2. Understanding of data structures like arrays and lists
  3. Basic sorting algorithms (bubble sort, selection sort, insertion sort)
  4. Familiarity with Big O notation for analyzing algorithm complexity
  5. Knowledge of basic concepts in probability theory (for understanding median-of-three pivot selection strategy)

Core Concept

Quicksort is a divide-and-conquer algorithm that sorts an array by selecting a pivot element and partitioning the other elements into two subarrays, according to whether they are less than or greater than the pivot. This process is then recursively applied to the subarrays until the entire array is sorted.

Partitioning the Array

The quicksort algorithm begins by choosing a pivot element (usually the first or last element of the array). The array is then partitioned into two subarrays: elements less than the pivot are placed in the left subarray, while elements greater than the pivot are placed in the right subarray. The pivot itself is placed in its final sorted position.

Recursive Implementation

The recursive implementation of quicksort calls a helper function quick_sort() that takes an array and a range (start and end indices) as arguments. The base case for this recursion is when the range contains only one element, in which case the array is already sorted. Otherwise, the pivot is chosen, the array is partitioned, and the helper function is called on each subarray.

Choosing a Pivot

There are several strategies for choosing a pivot:

  1. First Element (Lomuto Partition Scheme): The first element of the array is chosen as the pivot. This is the simplest approach but may lead to poor performance in some cases, such as when the input array is already sorted or nearly sorted.
  2. Last Element (Hoare Partition Scheme): The last element of the array is chosen as the pivot. This scheme generally leads to better performance than the first-element choice but may still have issues with near-sorted arrays.
  3. Median-of-Three: The median of the first, middle, and last elements of the array is chosen as the pivot. This approach often provides good performance in practice.
  4. Random Pivot: A random element from the array is chosen as the pivot. This strategy can help prevent worst-case scenarios but may still lead to poor average-case performance if the random choice is biased towards one end of the array.

Worked Example

Let's implement a basic quicksort algorithm using Python, employing the first-element (Lomuto Partition Scheme) pivot selection strategy:

def quick_sort(arr, low=0, high=None):
if high is None:
high = len(arr) - 1

if low < high:
pi = partition(arr, low, high)
quick_sort(arr, low, pi - 1)
quick_sort(arr, pi + 1, high)

def partition(arr, low, high):
pivot = arr[low]
i = low + 1
j = high

while True:

Find an element greater than the pivot on the left side

while i <= j and arr[i] < pivot:

i += 1

Find an element smaller than the pivot on the right side

while j >= i and arr[j] > pivot:

j -= 1

if i > j:

break

Swap elements at positions i and j

arr[i], arr[j] = arr[j], arr[i]

Swap the pivot with the element at position j

arr[low], arr[j] = arr[j], arr[low]

return j

Common Mistakes

  1. Choosing a poor pivot: Using the first or last element as the pivot can lead to poor performance, especially when dealing with nearly sorted arrays. Consider using median-of-three or random pivot selection strategies in such cases.
  2. Stack overflow: Recursive implementations of quicksort may cause stack overflows for large inputs. Use iterative implementations or tail recursion optimization if necessary.
  3. Ignoring duplicate elements: Quicksort assumes that the array contains unique elements. If duplicates are present, the algorithm may not sort them correctly. Consider using other sorting algorithms like mergesort for handling duplicate elements.
  4. Incorrect partitioning: Carelessly implemented partitioning can lead to incorrect subarray splits and poor performance. Ensure that the partition function properly places all elements in their correct subarrays.
  5. Premature optimization: Avoid optimizing the pivot selection strategy or partitioning method too early. Start with a simple implementation, then profile and optimize as needed.
  6. ### Subheadings under Common Mistakes:
  • Incorrect base case handling
  • Neglecting edge cases (empty arrays, single-element arrays)
  • Inefficient pivot selection strategies for specific input distributions

Practice Questions

  1. Implement quicksort using the last-element (Hoare Partition Scheme) pivot selection strategy.
  2. Modify the quicksort implementation to handle duplicate elements correctly.
  3. Write an iterative version of quicksort using a stack data structure.
  4. Analyze the time complexity of the quicksort algorithm in terms of Big O notation.
  5. Compare and contrast quicksort with other sorting algorithms like mergesort, heapsort, and radix sort.
  6. ### Subheadings under Practice Questions:
  • Comparing average-case and worst-case time complexities
  • Analyzing space complexity of various implementations
  • Optimizing pivot selection strategies for specific input distributions

FAQ

  1. Why is quicksort faster than other sorting algorithms? Quicksort's average-case time complexity (O(n log n)) makes it faster than many other sorting algorithms for large arrays. Its divide-and-conquer approach allows it to handle large inputs efficiently.
  2. What are the worst-case scenarios for quicksort? The worst-case scenario for quicksort occurs when the array is sorted or nearly sorted, leading to a time complexity of O(n^2). This can be mitigated by using better pivot selection strategies.
  3. Is it possible to implement quicksort in linear space (O(n))? While it's theoretically possible to implement quicksort with linear space, practical implementations require additional data structures like a stack or extra memory for temporary storage, making the space complexity O(log n) or O(n).
  4. How does quicksort compare to mergesort in terms of performance? Mergesort has a better worst-case time complexity (O(n log n)) than quicksort, but it requires more memory due to its divide-and-conquer approach. Quicksort is generally faster for large arrays, while mergesort may be preferred for small arrays or when dealing with duplicate elements.
  5. Can I use quicksort for sorting linked lists? Yes, you can adapt the quicksort algorithm to work with linked lists by choosing a pivot element from the middle of the list and partitioning the list recursively. However, this implementation may not be as efficient as sorting arrays due to the additional cost of traversing the linked list.
  6. ### Subheadings under FAQ:
  • Comparing quicksort with other in-place sorting algorithms (e.g., heapsort)
  • Discussing adaptations of quicksort for specific data structures (e.g., sorted arrays, linked lists)
  • Examining the impact of pivot selection strategies on algorithm performance
Quicksort (Data Structures & Algorithms) | Data Structures & Algorithms | XQA Learn