Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 16

Quicksort: Implementation and Partitioning

Recommended resources

Hello! Welcome to your next lesson on sorting algorithms.

In our last session, we explored Merge Sort, a prime example of the "Divide and Conquer" strategy. We saw how it methodically splits an array, recursively sorts the halves, and then does the main work in the combine step by merging them back together.

Today, we'll dive into another famous divide-and-conquer algorithm: Quicksort. While it shares the same high-level strategy as Merge Sort, it flips the script. Quicksort's cleverness lies in its "divide" step, where it partitions the array. This difference leads to fascinating trade-offs in performance and memory usage, making it one of the most widely used sorting algorithms in practice.

Your goal for this lesson is to implement Quicksort and understand its partitioning logic. By the end, you'll see how choosing a "pivot" and rearranging the array around it is the key to this algorithm's speed.

The Quicksort Strategy: Pivot and Partition

Like Merge Sort, Quicksort is recursive. But instead of blindly splitting the array in half, it intelligently rearranges it first. The three main steps are:

  1. Choose a Pivot: Select one element from the array. This element is called the pivot.
  2. Partition: Rearrange the array so that all elements smaller than the pivot are on its left, and all elements greater than the pivot are on its right. The pivot is now in its final, sorted position. This is the "divide" step, and it's where all the important work happens.
  3. Recurse: Recursively apply the same process to the two sub-arrays on the left and right of the pivot.

The "combine" step is trivialโ€”since the partitions are sorted in place, there's nothing to merge!

To get a feel for this process, let's watch a short conceptual overview.

Quick Sort - Data Structures & Algorithms Tutorial Python #15

This clip from the 'codebasics' channel provides an excellent high-level introduction to Quicksort, showing how a pivot is used to create two partitions that are then sorted recursively.

Watch from the beginning until 03:30. Focus on the overall strategy rather than the exact mechanism of partitioning, which we will cover next.

This image visualizes the entire process, from the initial partitioning to the final sorted array.

Quicksort Algorithm Visualization
This diagram shows Quicksort in action. The top half illustrates the key 'partition' step around a pivot. The bottom half shows the recursive calls, where the array is repeatedly partitioned into smaller sub-arrays until the entire list is sorted.

The Heart of Quicksort: Partitioning Logic

The effectiveness of Quicksort hinges entirely on the partition function. There are several ways to implement it, but we'll focus on a classic and efficient method known as Hoare's partition scheme, named after Quicksort's inventor, Tony Hoare.

The general idea is to use two pointers, start and end, that move towards each other.

  1. The start pointer moves right, looking for an element that is greater than the pivot.
  2. The end pointer moves left, looking for an element that is less than the pivot.
  3. Once both pointers have stopped, the elements they point to are in the wrong respective partitions. We swap them.
  4. This process repeats until the pointers cross. At this point, the array is partitioned.

Let's see a detailed, step-by-step walkthrough of this logic.

Quick Sort - Data Structures & Algorithms Tutorial Python #15

Continuing with the 'codebasics' video, this section provides a fantastic animated explanation of Hoare's partition scheme. It makes the movement of the pointers and the swapping logic very clear.

Watch from 03:30 to 08:03. Pay close attention to the conditions that make the start and end pointers stop, and when the swap occurs. Notice what happens when the pointers eventually cross.

Another popular method is the Lomuto partition scheme. It works slightly differently, often using the last element as the pivot and a single pointer to keep track of the boundary for smaller elements. While Hoare's scheme is generally more efficient, Lomuto's can be simpler to implement. For competitive programming, being aware of both is useful.

Test your understanding!

Given the array [11, 7, 2, 15, 9, 28] and using the first element (11) as the pivot, perform one pass of Hoare's partitioning.

  1. Where do the start and end pointers first stop?
  2. What is the state of the array after the first swap?
Show answer
  1. The start pointer (initially at index 1) moves right, searching for a value > 11. It stops at 15 (index 3).
  2. The end pointer (initially at index 5) moves left, searching for a value < 11. It stops at 9 (index 4).
  3. The elements 15 and 9 are swapped. The array becomes [11, 7, 2, 9, 15, 28].

Implementing Quicksort in Python

Now let's translate this logic into code. The implementation naturally consists of two functions:

  1. partition(arr, start, end): This function implements the Hoare scheme we just saw. It rearranges the sub-array arr[start...end] and returns the index where the partition boundary lies.
  2. quick_sort(arr, start, end): This is the main recursive driver. It calls partition and then recursively calls itself on the left and right sub-arrays.

The following video provides a complete, from-scratch implementation. A particularly valuable part is watching the process of debugging the code to handle edge cases, a crucial skill in programming.

Quick Sort - Data Structures & Algorithms Tutorial Python #15

This part of the 'codebasics' video demonstrates the full implementation of Quicksort in Python using the Hoare partition scheme we just learned.

Watch from 13:19 to 28:57. The first segment (until 21:32) focuses on coding the partition function. The second segment integrates this into the recursive quick_sort function and handles necessary boundary checks and debugging.

Here is a complete, clean implementation based on what you've just seen, using the Lomuto partition scheme for comparison. This version is from GeeksforGeeks and is also very common. Notice it picks the last element as the pivot.

Quick Sort

To solidify your understanding, let's look at a full Python implementation using the Lomuto partition scheme from GeeksforGeeks. This will give you a well-structured example to study and adapt.

Review the Python code block under the 'QuickSort in Python' heading. Compare its structure to the Hoare implementation from the video. Identify the partition and quickSort functions and trace how they interact.


Complexity and Performance

Quicksort is famous for being fast, but its performance depends heavily on the choice of the pivot.

  • Time Complexity:

    • Best and Average Case: O(n log n). This happens when the pivot consistently divides the array into two roughly equal halves. The log n comes from the recursion depth, and the n comes from the O(n) work done by the partition function at each level.
    • Worst Case: O(nยฒ). This is a critical point. The worst case occurs when the pivot is always the smallest or largest element. This creates extremely unbalanced partitions (one of size 0 and the other of size n-1). This can happen if you, for example, always pick the first element as the pivot and the input array is already sorted.
  • Space Complexity:

    • Average Case: O(log n). Unlike Merge Sort, Quicksort is an in-place algorithm. It doesn't need extra arrays for merging. The space is used by the recursion call stack, which is O(log n) deep on average.
    • Worst Case: O(n). In the worst-case scenario of unbalanced partitions, the recursion depth can be O(n), leading to O(n) space complexity for the call stack.

This trade-off is central to understanding Quicksort: it's generally faster and more space-efficient than Merge Sort, but you lose Merge Sort's guarantee of O(n log n) performance in all cases. In practice, using a randomized pivot makes the worst case extremely unlikely.

Sorting Algorithms in Python

This article from RealPython provides a concise summary of Quicksort's complexity and the importance of pivot selection.

Please read the sections 'Selecting the pivot Element' and 'Measuring Quicksortโ€™s Big O Complexity'. This will reinforce why the worst case happens and how it can be mitigated.


Conclusion

Excellent work! You've now dissected Quicksort, one of the most important algorithms in computer science. Understanding its inner workings is a huge step forward in your competitive programming journey.

Key Takeaways:

  • Quicksort is an in-place Divide and Conquer algorithm.
  • The core of the algorithm is the partition step, which places a chosen pivot in its correct sorted position.
  • Its average time complexity is a very fast O(n log n) with O(log n) space complexity, making it highly efficient.
  • Its performance can degrade to O(nยฒ) in the worst case, which depends on pivot selection. This is a key contrast with Merge Sort's stable O(n log n) time and O(n) space complexity.

Up Next:
You've now implemented two fundamental sorting algorithms from scratch. While this is an essential skill, in practice, you'll often rely on the highly optimized sorting functions built into your programming language. In the next lesson, we will explore how to use Python's built-in sort() and sorted() functions, and how to customize their behavior using lambda functions as comparators. This will allow you to solve sorting problems quickly and effectively.