Implementing Quick Select in Python
If I ask you to think about an algorithm to find the kth smallest element in a list of integers, your answer would probably be this: sort the list first and then extract the element at index k-1. This is a simple and effective solution. The time complexity of the above algorithm would be dependent on the sorting algorithm used.
But how can we find the kth smallest element in a list without (completely) sorting the list? The answer is Quick Select. And we can implement it using a simple technique: partitioning, which is also used in quicksort.
How Does Quick Select Work?
In order to understand how this algorithm works, let’s first look at the pseudocode:
- Choose a pivot p
- Partition the array in two sub-arrays w.r.t. p (same partitioning as in quicksort)
- LEFT –> elements smaller than or equal to p
- RIGHT–> elements greater than p
- If index(pivot) == k:
- Return pivot (or index of pivot)
- If k > index(pivot)
- QuickSelect(LEFT)
- Else:
- QuickSelect(RIGHT)
The logic of the algorithm is extremely evident from the above pseudocode: if the index of the pivot after partitioning is the same as ‘k’ then return the pivot; else if k is greater than the index of the pivot then we recur the left side of the partition; else we recur on the right side of the partition.
NOTE: This can be easily converted into an algorithm to find kth largest element by simply changing the condition in line 7 (from ‘>’ to ‘<’) of the pseudocode.
Complexity analysis
Considering an input list of size n:
- Best case time complexity: O(n) when the first chosen pivot is also the kth smallest element.
- Worst-case time complexity: O(n^2)
The worst-case occurs when we are extremely unlucky in our pivot choices and our partitions only remove one element from the list at a time. Hence, our list sizes in each of the recursive calls would reduce by 1.
This would result in the following time complexity: O( (n) + (n -1) + (n – 2) +……+3+2+1) which is equal to O(n^2)
NOTE: Although the worst-case complexity is O(n^2), in practice the average time complexity turns out to be O(n).
Implementation of Quick Select in Python
We already discussed a lot in theory. Now its time to code in Python to implement the Quick Select technique. Below is the given code:
import math def quickselect(list_of_numbers, k): """ Input: a list of numbers and an integer 'k'. Output: kth smallest element in the list. Complexity: best case: O(n) worst case: O(n^2) """ quick_selected= _kthSmallest(list_of_numbers, k, 0, len(list_of_numbers)-1) if quick_selected!=math.inf: print('The ' + str(k)+ 'th smallest element of the given list is ' + str(quick_selected)) else: print('k-th element does not exist') def _kthSmallest(arr, k, start, end): """ private helper function for quickselect """ # checking if k is smaller than # number of elements in the list if (k > 0 and k <= end - start + 1): # Partition the array with last # element as the pivot and get # position of pivot element in # sorted array pivot_index = _partition(arr, start, end) # if position of the pivot # after partition is same as k if (pivot_index - start == k - 1): return arr[pivot_index] # if position of the pivot # is greater than k then # recursive call _kthSmallest # on the left partition of the pivot if (pivot_index - start > k - 1): return _kthSmallest(arr, k, start, pivot_index - 1) # Else recursive call for right partition return _kthSmallest(arr, k - pivot_index + start - 1, pivot_index + 1, end) return math.inf def _partition(arr, l, r): """ private helper function Input: a list and two integers: l: start index of the list to be partitioned r: end index of the list to be partitioned Output: index of the pivot after partition (using arr[r] as the pivot) """ pivot = arr[r] i = l for j in range(l, r): if arr[j] <= pivot: arr[i], arr[j] = arr[j], arr[i] i += 1 arr[i], arr[r] = arr[r], arr[i] return i
Now let’s run an example:
num_list=[5,2,3,6,2,3,7,6] quickselect(num_list,4)
Output:
The 4th smallest element of the given list is 3
Thank you for reading this article.
Leave a Reply