Table of contents

  1. In Place Quick Sort

In Place Quick Sort

  • Do you know Quicksort?

Yes I do, because I am recursion God who has cracked the Y Combinator thanks to The Little Schemer but lets hope no one asks me that in an interview.

Also here’s my quicksort implementation.

def quicksort(array):
if len(array) <= 1:
return array
# use last element as pivot
pivot = array[-1]
# assuming no duplicates
left = [x for x in array if x < pivot]
right = [y for y in array if y > pivot]
return quicksort(left) + [pivot] + quicksort(right)

But people squint at me and accuse me of using extra memory.

  • How do you respond to those people?

I take a deep breath and pull out my whiteboard and scribble IN PLACE QUICKSORT

  • How would you do it “in place”?

Since this requires partitioning, I suppose we could partition the array in place into 2 parts and recursively sort the partitions in place.

  • How would you partition it?

I guess we could split the array into 2 partitions by having 1 boundary. We can traverse the array and move items < pivot to the left side of the partition, and naturally the only items in the right partition will be items > pivot, assuming there are no duplicates going further.

  • How would you move the items in place?

Easy, Swap them.

  • Can I see an example? Sure, I am a visual learner so here you go.
[ 7 2 1 8 6 3 5 4 ]

Considering the last item as pivot(More on that later), we can partition the array as left having 0 items.

[ ][ 7 2 1 8 6 3 5 ] 4
^ ^

Since 7 > 4, 7 can stay on the right partition

[ ][ 7 2 1 8 6 3 5 ] 4
^ ^

2 < 4, we can move it to the left partition

[ 2 ][ 7 1 8 6 3 5 ] 4
^ ^

The first iteration of quicksort will result in the following array

[ 2 1 3 ][ 7 8 6 5 ] 4
  • But how do you do swaps?

I guess we could maintain an index as boundary. At the beginning, since we don’t have any items on the left we can use 0 as the boundary. So for the first item < pivot with index, we would swap out item[index] and item[boundary]

array = [...]
def inplace_quicksort(...):
...
boundary = 0
# use last element as pivot
pivot = array[-1]
# iterate till second last element
for index in range(len(array) - 2):
item = array[index]
if item < pivot:
array[boundary], array[index] = array[index], array[boundary]
...
  • Where does boundary lie, in the left or right partition?

Right

  • What if the first element < pivot and thus needs to be in the left partition?

No worries, we iterate from the beginning that would result in the same swap. No harm done.

  • Can you code that up?
array = [23, 87, 5, 99, 42]
def inplace_quicksort(left, right):
global array
if left > right:
return
if right - left + 1 <= 1:
return
# use last element as pivot
pivot = array[right]
boundary = left
# iterate till second last element
for index, item in enumerate(array[left:right]):
index = left + index
if item < pivot:
array[boundary], array[index] = array[index], array[boundary]
boundary = boundary + 1
inplace_quicksort(left, boundary - 1)
inplace_quicksort(boundary, right - 1)
# place pivot in between left & right partitions
# by shifting it from extreme right to the boundary position
while right > boundary:
array[right], array[right - 1] = array[right - 1], array[right]
right = right - 1
  • Does pivot always has to be the last item?

No, we can use any random item from the array as pivot. Although, a good pivot partitions left and right equally.

  • How can I get the best pivot?

Finding the med… https://stackoverflow.com/questions/164163/quicksort-choosing-the-pivot here you go.

  • Are you a god?

I might be.


← Back to notes