Quicksort
Quicksort works by partitioning an array based on an arbitrary element in the array, which we will refer to as The array is divided into two subarrays, one which contains elements larger than , and one which contains elements smaller than . The operation is then repeated with each of the subarrays, with a new for each subarray, until the array is separated into sub-arrays with a designated length, usually 1.
We can visualize this process with a tree-like structure as shown below in Figure S.1.
Figure S.1
Then, each one-element subarray is placed back together. The single-digit array containing the value is placed at the beginning, followed by the element, and finally the array with the larger element. Note, on subarrays with a size of 1, this just creates an ordered array. With larger arrays, a recursive method is utilized, sorting subtrees from the bottom up, as seen by the diagram in figure S.2. QuickSort takes in its worst case scenario, and in both its expected and best case QuickSort takes .
Figure S.2
python
def QuickSort(sub):
if len(sub) == 1:
return [sub[0]]
else:
p_index = rd.randrange(0, len(sub), 1) // choose a random p
p = sub[p_index]
less = []
more = []
sort = [p]
for i in range(len(sub)): // populate arrays with elements
if i != p_index:
if sub[i] < p:
less.append(sub[i])
elif sub[i] > p:
more.append(sub[i])
elif (sub[i] == p):
sort.append(p)
if len(more) > 0: // recursion on sub-arrays
sub1 = QuickSort(more)
sort += (sub1)
if len(less) > 0:
sub2 = QuickSort(less)
sort = sub2 + sort
return sort
Analysis of Quicksort
Worst Case
In the worst-case scenario, the array of size is split in a way that maximizes the uneveness. This would be when each recursive split creates one subarray of size and the other of size . If represents the worst-case runtime of Quicksort, then . Since a recursive call of an array of size is constant, this becomes . However, in the worst-case scenario, this uneven split would occur at every recursion, resulting in . In comparison to Insertion Sort, the worst-case running times are the same. However, while Quicksort would still take time to sort an already sorted array, Insertion Sort would only take time.
Best Case
The best-case runtime will occur if the subarrays created are of size no greater than . This means that one subarray would be of size and the other would be . So, the runtime is . Since the recursion tree has a depth of , the best-case scenario has a runtime of , the same as the expected case.
Expected Runtime
The expected run-time for Quicksort is with a randomized partition element. Randomized Partition creates a constant fraction of elements in each subarray so that the recursion tree has a depth of . Each recursion takes to go through each of the elements. Even if a few of the levels are split in the most uneven way, the runtime will remain . Furthermore, even if there is a split into a subarray of length and another of length , another very uneven split, the runtime is still . Uneven splits slow down the algorithm with a slightly larger constant in the runtime which is hidden in the notation. Thus, the expected runtime is .
Exercises
[We would probably get more question, but at the very least an answer for this one]
When would QuickSort take time to sort?
When the array is completely random
When the array is already in sorted order
When the partition is always the highest or lowest value
When the array is too big
Sources
General: https://www.youtube.com/watch?v=SLauY6PpjW4&ab_channel=HackerRank
Introduction to Algorithms, 3rd edition: https://web.stanford.edu/class/archive/cs/cs161/cs161.1168/lecture9.pdf