Sorting is a fundamental operation in computer science with numerous applications in data processing, analysis, and more. In this tutorial, we will explore several popular sorting algorithms implemented in Python, including bubble sort, selection sort, insertion sort, merge sort, quick sort, counting sort, and radix sort. We'll also delve into their time and space complexities to understand the trade-offs involved.
Sorting refers to arranging a collection of items (e.g., numbers or strings) in a specific order, either ascending or descending. Efficient sorting is crucial for optimizing performance in applications that require frequent data retrieval and manipulation. Python provides built-in sorting functions like sorted() and .sort(), but understanding the underlying algorithms helps you make informed decisions about when to use each method.
Bubble sort is a simple comparison-based algorithm where adjacent elements are compared and swapped if they are in the wrong order. This process is repeated until the list is sorted. The algorithm gets its name because smaller elements "bubble" to the top of the list.
1def bubble_sort(arr):2n = len(arr)3for i in range(n):4# Track if any swap happens5swapped = False6for j in range(0, n-i-1):7if arr[j] > arr[j+1]:8arr[j], arr[j+1] = arr[j+1], arr[j]9swapped = True10# If no two elements were swapped by inner loop, then break11if not swapped:12break13return arr1415# Example usage16arr = [64, 34, 25, 12, 22, 11, 90]17sorted_arr = bubble_sort(arr)18print(sorted_arr)
Time Complexity:
\(O(n^2)\)\(O(n^2)\)\(O(n^2)\)Space Complexity: \(O(1)\) (in-place sorting)
Note
Insertion sort builds the final sorted array one item at a time. It takes each element from the input data and finds the location it belongs within the already sorted part of the list, inserting it there.
1def insertion_sort(arr):2n = len(arr)3for i in range(1, n):4key = arr[i]5j = i-16# Move elements of arr[0..i-1], that are greater than key, to one position ahead of their current position7while j >= 0 and key < arr[j]:8arr[j+1] = arr[j]9j -= 110arr[j+1] = key11return arr1213# Example usage14arr = [12, 11, 13, 5, 6]15sorted_arr = insertion_sort(arr)16print(sorted_arr)
Time Complexity:
\(O(n \log n)\)\(O(n \log n)\)\(O(n \log n)\)Space Complexity: \(O(n)\) (requires additional space for the temporary arrays)
Note
Quick sort is another divide-and-conquer algorithm that works by selecting a 'pivot' element from the array and partitioning the other elements into two sub-arrays, according to whether they are less than or greater than the pivot. The sub-arrays are then sorted recursively.
1def quick_sort(arr):2if len(arr) <= 1:3return arr4else:5pivot = arr[len(arr) // 2]6left = [x for x in arr if x < pivot]7middle = [x for x in arr if x == pivot]8right = [x for x in arr if x > pivot]9return quick_sort(left) + middle + quick_sort(right)1011# Example usage12arr = [3, 6, 8, 10, 1, 2, 1]13sorted_arr = quick_sort(arr)14print(sorted_arr)
Time Complexity:
\(O(n + k)\)\(O(n + k)\)\(O(n + k)\)Space Complexity: \(O(k)\) (where k is the range of input values)
Note
Radix sort sorts numbers by processing individual digits. It starts from the least significant digit and moves towards the most significant digit, using a stable sub-sorting algorithm (like counting sort) to sort each digit.
1def counting_sort_for_radix(arr, exp):2n = len(arr)3output = [0] * n4count = [0] * 1056# Store count of occurrences in count[]7for i in range(n):8index = (arr[i] // exp) % 109count[index] += 11011# Change count[i] so that it contains the actual position of this digit in output[]12for i in range(1, 10):13count[i] += count[i-1]1415# Build the output array16i = n - 117while i >= 0:18index = (arr[i] // exp) % 1019output[count[index] - 1] = arr[i]20count[index] -= 121i -= 12223# Copy the output array to arr[], so that arr now contains sorted numbers24for i in range(n):25arr[i] = output[i]2627def radix_sort(arr):28# Find the maximum number to know the number of digits29max_val = max(arr)3031# Do counting sort for every digit. Note that instead of passing digit number, exp is passed.32# exp is 10^i where i is current digit number33exp = 134while max_val // exp > 0:35counting_sort_for_radix(arr, exp)36exp *= 1037return arr3839# Example usage40arr = [170, 45, 75, 90, 802, 24, 2, 66]41sorted_arr = radix_sort(arr)42print(sorted_arr)
| Algorithm | Best Case | Average Case | Worst Case | Space Complexity |
|---|---|---|---|---|
| Bubble Sort | \(O(n)\) | \(O(n^2)\) | \(O(n^2)\) | \(O(1)\) |
| Selection Sort | \(O(n^2)\) | \(O(n^2)\) | \(O(n^2)\) | \(O(1)\) |
| Insertion Sort | \(O(n)\) | \(O(n^2)\) | \(O(n^2)\) | \(O(1)\) |
| Merge Sort | \(O(n \log n)\) | \(O(n \log n)\) | \(O(n \log n)\) | \(O(n)\) |
| Quick Sort | \(O(n \log n)\) | \(O(n \log n)\) | \(O(n^2)\) | \(O(\log n)\) |
| Counting Sort | \(O(n + k)\) | \(O(n + k)\) | \(O(n + k)\) | \(O(k)\) |
| Radix Sort | \(O(nk)\) | \(O(nk)\) | \(O(nk)\) | \(O(n + k)\) |
In the next topic, we will explore searching algorithms, including linear search and binary search. These algorithms are essential for efficiently finding elements within a dataset. Understanding both sorting and searching algorithms is crucial for optimizing data processing tasks in various applications.
Stay tuned for more tutorials on advanced topics in computer science!