One of the most popular sorting algorithms is QuickSort. It’s a divide-and-conquer algorithm that iteratively picks a pivot element from the array, splits the array into 3 parts - elements that are smaller, equal, and larger than the pivot element and repeats that process until getting to a point where the whole array is sorted.
So, on each iteration:
We pick a pivot element from the array
Move the smaller elements to the left part of the pivot
Move the larger elements to the right part of the pivot
Collect all the elements equal to the pivot in the middle
Repeat this process on both the left and the right parts
def quicksort(a):
if len(a) <= 1:
return a
pivot = a[len(a) // 2]
left = [x for x in a if x < pivot]
middle = [x for x in a if x == pivot]
right = [x for x in a if x > pivot]
return quicksort(left) + middle + quicksort(right)
This implementation of QuickSort uses additional arrays for the left, middle, and right parts. We can make it in place by modifying the code to use indices and rearranging the elements right in the array.
def quicksort(a, start, end):
if start >= end: # No elements to sort
return
pivot = a[start] # Choose a pivot
l, r = start + 1, end # Sort a[start+1 ... end]
while l <= r: # While there are elements left in the range
if a[l] < pivot: # if a[l] < pivot => leave a[i] untouched
l += 1 # Increase the left pointer
else: # otherwise move a[l] to the end
a[l], a[r] = a[r], a[l] # Swap a[l] and a[r] -> move to the end
r -= 1 # Reduce the right pointer
a[start], a[r] = a[r], a[start] # Swap pivot with a[r]
quicksort(a, start, r - 1) # Sort a[start ... r-1]
quicksort(a, r + 1, end) # Sort a[r+1 ... end]
You might have noticed that the choice of the pivot was different in these two implementations. The QuickSort algorithm does not have a restriction on picking a pivot. There are several ways to do that. Here are a few ideas on how to pick a pivot:
First element: a[start] as demonstrated in the second implementation
Last element: a[end]
Middle element: a[(start + end + 1) // 2]
Random element: Pick any element in a[start ... end]
Median element: Pick the median of the current segment. This makes sure that each split results in equal left and right parts but requires a more sophisticated implementation.
The average running time of the QuickSort algorithm is . Yet, depending on the choice of the pivot, it can result in quadratic performance.
🤔 Can you think of an example where the algorithm would perform operations if we always pick the start as the pivot?
Challenge
Given a list of n integers and q pivots, you are asked to move all the elements smaller than the pivot to the left, and all the elements larger than the pivot to the right of the pivot element, while keeping all the equal ones together (next to each other, to the right of smaller numbers, and to the left of larger ones).
Input
The first line of the input contains a single integer n (1 ≤ n ≤ 100 000).
The next line contains a list of n space-separated integers ( ≤ ≤ ).
The third line contains a single integer q (1 ≤ q ≤ 10).
The next line contains a list of n space-separated integers (1 ≤ ≤ n) representing the index of the pivot element.
Output
For each of the q pivots, the program should print the resulting array after performing the rearrangement. If there are multiple possible answers, the program should print any of those.