Quicksort Steps

I’m going to be really lame and copy and paste the steps from the Wikipedia page here because they are very clear.

Pick an element, called a pivot, from the array. Partitioning: reorder the array so that all elements with values less than the pivot come before the pivot, while all elements with values greater than the pivot come after it (equal values can go either way). After this partitioning, the pivot is in its final position. This is called the partition operation. Recursively apply the above steps to the sub-array of elements with smaller values and separately to the sub-array of elements with greater values.

Okay let’s go!

Step 1

Pick an element, called a pivot, from the array.

A little vague, right? How do we know which element to select? Is there a right or a wrong way to choose?

There is no wrong way. Some people select the first element, some people select the last or the middle, and some people go with a median of the three.

If you select the first or last element, you get a worst-case runtime on already sorted arrays, which just feels really silly to me. It’s already sorted, so my sorting function shouldn’t have to do much work, right? This problem is solved by selecting a random element, the median, or just the one in the middle. Let’s start with the one in the middle:

Awesome. We have a pivot value! Next step!

Step 2

Partitioning: reorder the array so that all elements with values less than the pivot come before the pivot, while all elements with values greater than the pivot come after it (equal values can go either way). After this partitioning, the pivot is in its final position. This is called the partition operation.

We are going to use a partitioning method referred to as Hoare’s partitioning scheme, named for Sir Charles Antony Richard Hoare, who developed the quicksort algorithm.

This method works by creating two pointers, called “left” and “right” and placing them at both ends of the array. The two pointers move toward the center until they each find a pair of elements that are in the wrong place. The two elements are switched and the process is continued until all the elements are separated into two sub-arrays, with the left values being less than or equal to the pivot and the right values being greater than the pivot.

Don’t worry if that doesn’t make sense yet. We’ll break it down in code. First, let’s make our partition function and get our two pointers set up.

The partition is going to need to take in the array, the index of the left pointer (we’ll see why later), the index of the right pointer, and the pivot value.

So now we need to move our pointers toward the center until they find elements that need to be switched.

Great! So once those while loops hit their exit condition, we will know that the elements they are pointing at need to be switched. Let’s see how that would play out in practice.

Say we have this array:

let arr = [1, 3, 7, 4, 5, 2, 6]

And suppose 4 was our selected pivot. We would use 0 for our left value and 6 as our right value for our first call because we want to partition the whole array.

Note: Later, when we begin recursively calling quicksort, we will only want to partition part of our array, which is why I set left and right as arguments to be passed in rather than hard coding them as 0 and arr.length -1.

So our first while loop will continue moving the left pointer until it hits the 7 at index 2.

Our second while loop will move the right pointer until it hits the 2 at index 5.

The next thing we need to do is switch these elements, so that our array looks like this:

[1, 3, 2, 4, 5, 7, 6]

Notice how the elements are partitioned around that 4? Everything to the left of 4 is smaller and everything to the right is larger. That’s what we want!

Let’s add that to our partition function. We’ll first check to make sure that the left pointer is still to the left of the right pointer, then we will switch the elements.

For our example array, we were lucky in that it only took one of these swaps to partition the entire array on the pivot point of 4. But it’s very important we remember that this process is continued until all the elements are partitioned, which might be more than just one swap!

So let’s start by moving the left and right pointers after our swap, as the two elements they are now pointing at are in the correct place. Then we can wrap this whole thing in a while loop that will exit when the left pointer moves past the right pointer. That will mean that the entire array has been partitioned.

Great! Now this process will repeat until the array is truly partitioned around that pivot value. On to step 3!

EDIT: See the final chunk of code at the bottom of the article. There is an added check to handle for arrays of all the same value. Line 3 of this code should say while (arr[left] < pivot && left <= right)

Step 3

Recursively apply the above steps to the sub-array of elements with smaller values and separately to the sub-array of elements with greater values.

Alright recursion! That’s fun. So first we need to make a base case to prevent our function from calling itself infinite times.

We are going to do this by passing in two more arguments to our quicksort function. These are going to be more “left” and “right” pointers, which we will use to divide the array into its sub-arrays for future recursive calls.

Let’s start with some default values for our left and right pointers. These are going to be the first and last indexes of the array, as our initial call of quicksort is going to be on the entire array.

Our base case will be whenever those two pointers meet each other (which will mean our sub-array has a length of 1).

Notice here on line 3 that we also changed our pivot definition. We need to make sure that the pivot is going to be halfway between the left and right pointers and not just halfway through the array.

Great! Now we have our base case and our pivot point. So let’s plug our partition function in!

Awesome! Now we just gotta…

Hmmm…

Well we need to recursively call quicksort on the left and right sub-arrays right? But we don’t know where our pivot point is in the array now that everything has been swapped and moved around. Without that, we can’t know where the sub-arrays start and end.

What can we do?

If you take a look at our partition function again, you can see that our pointers from that function can be useful here too.

When we break out of the while loop on line 2, our left pointer is going to be at the index to the right of the pivot point! Wild right? Take a look at our example array from before:

[1, 3, 7, 4, 5, 2, 6]

After our while loop broke, the array would look like this:

[1, 3, 2, 4, 5, 7, 6]

Our while loop on line 2 breaks when the left pointer passes the right, which happens when “left” points to 5 (or index 4). This is the element to the right of the pivot, meaning that is where the left sub-array ends and the right one begins.

Pretty useful, huh? Let’s just return left from our partition function and set it to a variable in our quicksort function.

Reading step three again:

Recursively apply the above steps to the sub-array of elements with smaller values and separately to the sub-array of elements with greater values.

Now that we know where those sub-arrays start and end, we can recursively call quicksort on them. Let’s do it!

All that’s left now is to return our array. Remember, since we are operating in-place, our return value will be the original array.

So all together, our code looks like this: