Exploring the Time and Space Complexities of Quick Sort
This article is a complementary resource to the DSA with Python and DSA with C++ courses.
This article is a complementary resource to the DSA with Python and DSA with C++ courses.
Quick Sort is a divide-and-conquer sorting algorithm. It works by
def quick_sort(lst):
length = len(lst)
# Base case
if length <= 1:
return lst
else:
# Choose the last element as the pivot
pivot = lst.pop()
# Partition step: Divide elements into
# left (<= pivot) and right (> pivot)
right = []
left = []
for element in lst:
if element > pivot:
right.append(element)
else:
left.append(element)
# Recursive step: Sort left and right partitions
# Then combine with pivot
return quick_sort(left) + [pivot] + quick_sort(right)
template<typename T>
int partition(vector<T>& arr, int low_index, int high_index) {
// Consider the last element as pivot
T pivot = arr[high_index];
int i = low_index;
int j = high_index-1;
while(true) {
// Find element larger than pivot
while(i < high_index && arr[i] < pivot) {
++i;
}
// Find element smaller than pivot
while(j >= low_index && arr[j] > pivot) {
--j;
}
// If two pointers crossover
if(i >= j) {
break;
}
// If a smaller element exists after the larger element
swap(arr[i], arr[j]);
}
// Position the pivot at its final position
swap(arr[i], arr[high_index]);
// Return the final index of the pivot
return i;
}
template<typename T>
void quick_sort(vector<T>& arr, int low_index, int high_index) {
// Return vector has less than two elements
if(low_index >= high_index) {
return;
}
// Partition the vector and get the final index of the pivot
int pivot_index = partition(arr, low_index, high_index);
// Recursively sort the left part
quick_sort(arr, low_index, pivot_index-1);
// Recursively sort the right part
quick_sort(arr, pivot_index+1, high_index);
}
Best Case Time Complexity: O(n logn)
In the best case, the pivot divides the array into two nearly equal halves at each recursive step.
Consider the following array:
[4, 2, 5, 3, 1]
Here, we choose the right-most element as pivot i.e. 1.
[]
[4, 2, 5, 3]
In the second step, the pivot element is 3, which divides the array into almost equal halves ([2]
and [4, 5]
).
The new right sub-array is similarly divided into almost equal halves in each successive step.
In this scenario, each partitioning operation takes O(n)
time, and the recursion depth is O(logn)
. Therefore, the total time complexity is O(n logn)
.
Worst Case Time Complexity: <katex>O(n^{2})</katex>
In the worst case, the pivot is always the smallest or largest element in multiple steps of recursion, which causes the array to be divided into uneven partitions. This results in a recursion depth of O(n)
.
Consider the reverse-sorted array:
[5, 4, 3, 2, 1]
Here, we choose the right-most element as pivot i.e. 1.
[]
[5, 4, 3, 2]
In the second step, the pivot element is 2, which divides the array again into unequal halves. The array is similarly divided into unequal halves in each successive step.
In this scenario, each partitioning operation takes O(n)
time, and the recursion depth is O(n)
. Therefore, the total time complexity is <katex>O(n^{2})</katex>
.
Average Case Time Complexity: O(n logn)
In the average case, quick sort performs well with O(n logn)
time complexity.
Assuming that the pivot divides the array into roughly equal parts, each partitioning step takes O(n)
time, and the recursion depth is O(logn)
.
Quick sort is an in-place sorting algorithm. However, it uses additional space on the call stack due to recursion.
The space complexity is O(logn)
for the average and best cases, where the recursion depth is proportional to the logarithm of the array size.
In the worst case (when the pivot always divides the array poorly), the space complexity can degrade to O(n)
.
However, such worst cases are extremely rare, so the space complexity is practically O(logn)
.
Best Case Time Complexity | O(n logn) |
Worst Case Time Complexity | <katex>O(n^{2})</katex> |
Average Time Complexity | O(n logn) |
Space Complexity | O(logn) |
Quick sort is one of the most efficient and widely used sorting algorithms, especially for large datasets. Alongside insertion and merge sort, it's one of the most preferred sorting algorithms among programmers..
When to Use:
O(n logn)
.When to Avoid:
<katex>O(n^{2})</katex>
. In such cases, other algorithms like Merge Sort may perform better.Here is a comparison of quick sort's performance in different scenarios:
Scenario | Comparisons | Swaps |
---|---|---|
10 elements (random order) | 45 | 30 |
10 elements (sorted) | 45 | 0 |
10 elements (reverse sorted) | 45 | 30 |
Quick sort's performance is heavily dependent on the choice of pivot. With the right-most pivot, the worst case occurs more often with sorted or nearly sorted data.