My answer is very similar to the great one from @alisianoi . However, I believe there is a slight inefficiency in his code (see my comment), which I removed. Moreover, I added more explanation and was a bit more specific about the problem of duplicate (pivot) values.
def quicksort(nums, begin=0, end=None):
# Only at the beginning end=None. In this case set to len(nums)-1
if end is None: end = len(nums) - 1
# If list part is invalid or has only 1 element, do nothing
if begin>=end: return
# Pick random pivot
pivot = nums[random.randint(begin, end)]
# Initialize left and right pointers
left, right = begin, end
while left < right:
# Find first "wrong" value from left hand side, i.e. first value >= pivot
# Find first "wrong" value from right hand side, i.e. first value <= pivot
# Note: In the LAST while loop, both left and right will point to pivot!
while nums[left] < pivot: left += 1
while nums[right] > pivot: right -= 1
# Swap the "wrong" values
if left != right:
nums[left], nums[right] = nums[right], nums[left]
# Problem: loop can get stuck if pivot value exists more than once. Simply solve with...
if nums[left] == nums[right]:
assert nums[left]==pivot
left += 1
# Now, left and right both point to a pivot value.
# All values to its left are smaller (or equal in case of duplicate pivot values)
# All values to its right are larger.
assert left == right and nums[left] == pivot
quicksort(nums, begin, left - 1)
quicksort(nums, left + 1, end)
return
Without recursion:
def quicksort(nums, ranges=None):
if ranges is None:
ranges = [[0, len(nums) - 1]]
while ranges != []:
[start, end] = ranges[0]
ranges = ranges[1:]
if start >= end:
continue
pivot = nums[randint(start, end)]
left = start
right = end
while left < right:
while nums[left] < pivot:
left += 1
while nums[right] > pivot:
right -= 1
if left != right:
nums[left], nums[right] = nums[right], nums[left]
if nums[left] == nums[right]:
left += 1
ranges = [[start, left - 1], [left + 1, end]] + ranges