Searching & Sorting
Quicksort
Algorithm · Core
Level: CoreDivide & Conquer
Watch it run
5
2
8
1
9
3
Step 1 of 18
Quick sort: pick a pivot, move smaller values left of it and larger right, then repeat on each side.
Complexity
| Name | Best | Average | Worst | Memory | Stable | Comments |
|---|---|---|---|---|---|---|
| Quick sort | n log(n) | n log(n) | n2 | log(n) | No | Quicksort is usually done in-place with O(log(n)) stack space |
Quicksort is a divide and conquer algorithm. Quicksort first divides a large array into two smaller sub-arrays: the low elements and the high elements. Quicksort can then recursively sort the sub-arrays
The steps are:
- 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.
Animated visualization of the quicksort algorithm. The horizontal lines are pivot values.
Code
import Sort from '../Sort';
export default class QuickSort extends Sort {
/**
* @param {*[]} originalArray
* @return {*[]}
*/
sort(originalArray) {
// Clone original array to prevent it from modification.
const array = [...originalArray];
// If array has less than or equal to one elements then it is already sorted.
if (array.length <= 1) {
return array;
}
// Init left and right arrays.
const leftArray = [];
const rightArray = [];
// Take the first element of array as a pivot.
const pivotElement = array.shift();
const centerArray = [pivotElement];
// Split all array elements between left, center and right arrays.
while (array.length) {
const currentElement = array.shift();
// Call visiting callback.
this.callbacks.visitingCallback(currentElement);
if (this.comparator.equal(currentElement, pivotElement)) {
centerArray.push(currentElement);
} else if (this.comparator.lessThan(currentElement, pivotElement)) {
leftArray.push(currentElement);
} else {
rightArray.push(currentElement);
}
}
// Sort left and right arrays.
const leftArraySorted = this.sort(leftArray);
const rightArraySorted = this.sort(rightArray);
// Let's now join sorted left array with center array and with sorted right array.
return leftArraySorted.concat(centerArray, rightArraySorted);
}
}import Sort from '../Sort';
export default class QuickSortInPlace extends Sort {
/** Sorting in place avoids unnecessary use of additional memory, but modifies input array.
*
* This process is difficult to describe, but much clearer with a visualization:
* @see: https://www.hackerearth.com/practice/algorithms/sorting/quick-sort/visualize/
*
* @param {*[]} originalArray - Not sorted array.
* @param {number} inputLowIndex
* @param {number} inputHighIndex
* @param {boolean} recursiveCall
* @return {*[]} - Sorted array.
*/
sort(
originalArray,
inputLowIndex = 0,
inputHighIndex = originalArray.length - 1,
recursiveCall = false,
) {
// Copies array on initial call, and then sorts in place.
const array = recursiveCall ? originalArray : [...originalArray];
/**
* The partitionArray() operates on the subarray between lowIndex and highIndex, inclusive.
* It arbitrarily chooses the last element in the subarray as the pivot.
* Then, it partially sorts the subarray into elements than are less than the pivot,
* and elements that are greater than or equal to the pivot.
* Each time partitionArray() is executed, the pivot element is in its final sorted position.
*
* @param {number} lowIndex
* @param {number} highIndex
* @return {number}
*/
const partitionArray = (lowIndex, highIndex) => {
/**
* Swaps two elements in array.
* @param {number} leftIndex
* @param {number} rightIndex
*/
const swap = (leftIndex, rightIndex) => {
const temp = array[leftIndex];
array[leftIndex] = array[rightIndex];
array[rightIndex] = temp;
};
const pivot = array[highIndex];
// visitingCallback is used for time-complexity analysis.
this.callbacks.visitingCallback(pivot);
let partitionIndex = lowIndex;
for (let currentIndex = lowIndex; currentIndex < highIndex; currentIndex += 1) {
if (this.comparator.lessThan(array[currentIndex], pivot)) {
swap(partitionIndex, currentIndex);
partitionIndex += 1;
}
}
// The element at the partitionIndex is guaranteed to be greater than or equal to pivot.
// All elements to the left of partitionIndex are guaranteed to be less than pivot.
// Swapping the pivot with the partitionIndex therefore places the pivot in its
// final sorted position.
swap(partitionIndex, highIndex);
return partitionIndex;
};
// Base case is when low and high converge.
if (inputLowIndex < inputHighIndex) {
const partitionIndex = partitionArray(inputLowIndex, inputHighIndex);
const RECURSIVE_CALL = true;
this.sort(array, inputLowIndex, partitionIndex - 1, RECURSIVE_CALL);
this.sort(array, partitionIndex + 1, inputHighIndex, RECURSIVE_CALL);
}
return array;
}
}References
Practice
Divide & Conquer
- Majority ElementEasy
- Convert Sorted Array to Binary Search TreeEasy
- Pow(x, n)Medium
- Sort ListMedium
- Maximum SubarrayMedium
- Different Ways to Add ParenthesesMedium
- Construct Quad TreeMedium
- Kth Largest Element in an ArrayMedium
- Merge k Sorted ListsHard
- Reverse PairsHard
- The Skyline ProblemHard
- Median of Two Sorted ArraysHard
Spotted a mistake or something unclear? Report it on GitHub.