In [ ]:
%%HTML
<style>
.container { width:100% }
</style>
The function $\texttt{sort}(L)$ sorts the list $L$ in place.
In [ ]:
def sort(L):
quickSort(0, len(L) - 1, L)
The function $\texttt{quickSort}(a, b, L)$ sorts the sublist $L[a:b+1]$ in place.
In [ ]:
def quickSort(a, b, L):
if b <= a:
return # at most one element, nothing to do
m = partition(a, b, L) # m is the split index
quickSort(a, m - 1, L)
quickSort(m + 1, b, L)
The function $\texttt{partition}(\texttt{start}, \texttt{end}, L)$ returns an index $m$ into the list $L$ and regroups the elements of $L$ such that after the function returns the following holds:
Here, $\texttt{pivot}$ is the element that is at the index $\texttt{end}$ at the time of the invocation of the function, i.e. we have
at invocation time.
The for-loop of partition
maintains the following invariants:
This algorithm has been suggested by Nico Lomuto. It is not the most efficient implementation of partition
, but
it is easier to understand than the algorithms that use two separate loops.
In [ ]:
def partition(start, end, L):
pivot = L[end]
left = start - 1
for idx in range(start, end):
if L[idx] <= pivot:
left += 1
swap(left, idx, L)
swap(left + 1, end, L)
return left + 1
The function $\texttt{swap}(x, y, L)$ swaps the elements at index $x$ and $y$ in $L$.
In [ ]:
def swap(x, y, L):
L[x], L[y] = L[y], L[x]
In [ ]:
import random as rnd
In [ ]:
def demo():
L = [ rnd.randrange(1, 200) for n in range(1, 16) ]
print("L = ", L)
sort(L)
print("L = ", L)
In [ ]:
demo()
In [ ]:
def isOrdered(L):
for i in range(len(L) - 1):
assert L[i] <= L[i+1]
In [ ]:
from collections import Counter
In [ ]:
def sameElements(L, S):
assert Counter(L) == Counter(S)
The function $\texttt{testSort}(n, k)$ generates $n$ random lists of length $k$, sorts them, and checks whether the output is sorted and contains the same elements as the input.
In [ ]:
def testSort(n, k):
for i in range(n):
L = [ rnd.randrange(2*k) for x in range(k) ]
oldL = L[:]
sort(L)
isOrdered(L)
sameElements(oldL, L)
assert len(L) == len(oldL)
print('.', end='')
print()
print("All tests successful!")
In [ ]:
%%time
testSort(100, 20000)
Next, we sort a million random integers.
In [ ]:
k = 1000000
L = [ rnd.randrange(1000 * k) for x in range(k) ]
In [ ]:
%%time
sort(L)
Again, we sort a million integers. This time, many of the integers have the same value.
In [ ]:
L = [ rnd.randrange(1000) for x in range(k) ]
In [ ]:
%%time
sort(L)
Next, we test the worst case and sort 5000 integers that are sorted ascendingly. Since quicksort is recursive, we have to increment the recursion limit of Python, because otherwise we would get an error that we exceed the maximum recursion depth.
In [ ]:
import sys
In [ ]:
sys.setrecursionlimit(10000)
In [ ]:
L = list(range(5000))
In [ ]:
%%time
sort(L)
If we shuffle the list to be sorted before sorting, the worst case behaviour disappears.
In [ ]:
rnd.shuffle(L)
In [ ]:
%%time
sort(L)
In [ ]: