Quick Sort Algorithm: Steps & Python Implementation Guide

You want to learn about Quick Sort—one of the most efficient and widely used sorting algorithms, right? Quick Sort follows the “divide-and-conquer” principle: it selects a “pivot” element, partitions the array into two sub-arrays (elements smaller than the pivot and elements larger than the pivot), then recursively sorts the sub-arrays. It’s favored for its average-case efficiency and practical performance in real-world applications.

I. Core Principles of Quick Sort

Quick Sort’s workflow can be broken down into four key steps (divide-and-conquer):

  1. Choose a Pivot: Select an element from the array as the “pivot” (common choices: first element, last element, middle element, or a random element).
  2. Partition the Array: Rearrange the array so that all elements smaller than the pivot move to the left of the pivot, and all elements larger than the pivot move to the right (elements equal to the pivot can go either way).
  3. Recursively Sort Sub-Arrays: Apply Quick Sort recursively to the left sub-array (elements < pivot) and the right sub-array (elements > pivot).
  4. Base Case: When a sub-array has 0 or 1 element, it is already sorted (no further recursion needed).

Key Notes on Pivot Selection

  • A poor pivot choice (e.g., smallest/largest element in a sorted array) leads to worst-case performance (O(n²)).
  • Random pivot or median-of-three pivot (average of first, middle, last elements) avoids worst-case scenarios and ensures consistent efficiency.

II. Complete Implementation Code (Python)

Below is a practical Quick Sort implementation with random pivot selection (to optimize performance) and detailed comments. It includes edge case handling (empty arrays, single-element arrays, duplicate elements):

python

运行

import random

def quick_sort(arr):
    """
    Quick Sort (random pivot optimization): Sort the array in ascending order
    :param arr: The list to be sorted (supports numeric types)
    :return: The sorted list (creates new sub-arrays; does not modify the original array)
    """
    # Base case: arrays with 0 or 1 element are already sorted
    if len(arr) <= 1:
        return arr
    
    # Step 1: Choose a random pivot (avoids worst-case scenarios)
    pivot = random.choice(arr)
    
    # Step 2: Partition into three groups: elements < pivot, == pivot, > pivot
    left = [x for x in arr if x < pivot]    # Sub-array with elements smaller than pivot
    middle = [x for x in arr if x == pivot] # Sub-array with elements equal to pivot
    right = [x for x in arr if x > pivot]   # Sub-array with elements larger than pivot
    
    # Step 3: Recursively sort left and right sub-arrays, then combine results
    return quick_sort(left) + middle + quick_sort(right)

# In-place version (modifies original array, saves memory)
def quick_sort_in_place(arr, low=None, high=None):
    """
    In-place Quick Sort (more memory-efficient): Sort the array in ascending order
    :param arr: The list to be sorted
    :param low: Start index of the sub-array (default: 0)
    :param high: End index of the sub-array (default: len(arr)-1)
    :return: None (modifies the original array)
    """
    # Initialize low and high for the first call (no need to pass them manually)
    if low is None:
        low = 0
    if high is None:
        high = len(arr) - 1
    
    # Base case: if low >= high, the sub-array is already sorted
    if low >= high:
        return
    
    # Step 1: Partition the array and get the pivot's final index
    pivot_index = partition(arr, low, high)
    
    # Step 2: Recursively sort left (low to pivot_index-1) and right (pivot_index+1 to high)
    quick_sort_in_place(arr, low, pivot_index - 1)
    quick_sort_in_place(arr, pivot_index + 1, high)

def partition(arr, low, high):
    """
    Helper function for in-place Quick Sort: Partitions the sub-array [low, high]
    :return: The final index of the pivot element
    """
    # Choose the LAST element as the pivot (simple implementation)
    pivot = arr[high]
    
    # i: pointer for the "smaller element region" (initially before the first element)
    i = low - 1
    
    # Iterate through elements from low to high-1
    for j in range(low, high):
        # If current element is <= pivot, expand the smaller region and swap
        if arr[j] <= pivot:
            i += 1
            arr[i], arr[j] = arr[j], arr[i]
    
    # Place the pivot in its final sorted position (between smaller and larger elements)
    arr[i + 1], arr[high] = arr[high], arr[i + 1]
    return i + 1  # Return the pivot's final index

# Test cases
if __name__ == "__main__":
    # Test 1: Ordinary unordered array (standard version)
    test1 = [64, 34, 25, 12, 22, 11, 90]
    print("Standard Quick Sort - Before:", test1)
    sorted_test1 = quick_sort(test1)
    print("Standard Quick Sort - After:", sorted_test1)  # Output: [11, 12, 22, 25, 34, 64, 90]

    # Test 2: In-place version (modifies original array)
    test2 = [5, 3, 8, 3, 2, 7, 1, 9]
    print("\nIn-place Quick Sort - Before:", test2)
    quick_sort_in_place(test2)
    print("In-place Quick Sort - After:", test2)  # Output: [1, 2, 3, 3, 5, 7, 8, 9]

    # Test 3: Edge cases (empty array, single element, sorted array)
    test3 = []
    test4 = [42]
    test5 = [1, 2, 3, 4, 5]
    print("\nEmpty array sorted:", quick_sort(test3))  # Output: []
    print("Single element sorted:", quick_sort(test4))  # Output: [42]
    print("Already sorted array:", quick_sort(test5))  # Output: [1, 2, 3, 4, 5]

    # Test 4: Array with duplicates
    test6 = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
    print("Array with duplicates sorted:", quick_sort(test6))  # Output: [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]

III. Key Code Explanations

1. Standard Quick Sort (Non-In-Place)

  • Pivot Selection: Uses random.choice(arr) to select a pivot, avoiding worst-case performance for sorted/reverse-sorted arrays.
  • Partitioning: Uses list comprehensions to split the array into three groups (leftmiddleright), which simplifies the code (but uses extra memory for sub-arrays).
  • Recursion: Recursively sorts left and right, then combines them with middle (pivot elements) for the final sorted array.

2. In-Place Quick Sort (Memory-Efficient)

  • Partition Function: The core of the in-place version:
    • Selects the last element as the pivot.
    • Uses two pointers (i and j): i tracks the end of the “smaller element region,” and j iterates through the array.
    • Swaps elements ≤ pivot into the smaller region, then places the pivot in its final sorted position.
  • Memory Efficiency: Modifies the original array directly, using O(log n) stack space for recursion (vs. O(n) for the standard version).

IV. Algorithm Performance Analysis

MetricResultExplanation
Time Complexity (Best)O(n log n)When the pivot splits the array into two nearly equal sub-arrays.
Time Complexity (Worst)O(n²)When the pivot is the smallest/largest element (e.g., sorted array with first/last pivot). Avoided with random/median pivot.
Time Complexity (Average)O(n log n)The most common case; faster than Bubble Sort, Insertion Sort, and even Merge Sort in practice (lower constant factors).
Space ComplexityO(log n) (in-place) / O(n) (standard)In-place version uses recursion stack space; standard version uses extra space for sub-arrays.
StabilityUnstableEqual elements may change relative positions (e.g., [2a, 2b, 1] → [1, 2b, 2a] if 1 is the pivot).

Why Quick Sort Is Popular

  • Practical Speed: Despite having the same average time complexity as Merge Sort (O(n log n)), Quick Sort is faster in practice because of lower constant factors (fewer comparisons/swaps and better cache performance).
  • Memory Efficiency: The in-place version uses minimal extra space, making it suitable for large datasets.
  • Flexibility: Works well with various data types (numbers, strings) and can be optimized for specific use cases.

V. Quick Sort vs. Merge Sort (Key Differences)

FeatureQuick SortMerge Sort
Time Complexity (Worst)O(n²) (avoidable)O(n log n) (guaranteed)
Space ComplexityO(log n) (in-place)O(n) (requires extra space for merging)
StabilityUnstableStable
Practical PerformanceFaster (lower constants)Slower (higher constants for merging)
Use CasesGeneral-purpose sorting (databases, applications)Sorting linked lists, stable sorting requirements

Summary

Disadvantages: Unstable, worst-case O(n²) (avoidable with good pivot choice).

Quick Sort’s core is the divide-and-conquer strategy: pivot selection → partitioning → recursive sorting of sub-arrays.

Pivot optimization (random/median) is critical to avoid worst-case O(n²) time complexity.

Two common implementations:

Standard version: Simple, uses extra memory, easy to understand.

In-place version: Memory-efficient, better for large datasets.

Advantages: Fast average performance (O(n log n)), low constant factors, memory efficiency (in-place).



了解 Ruigu Electronic 的更多信息

订阅后即可通过电子邮件收到最新文章。

Posted in

Leave a comment