Skip to content

Commit 09af83d

Browse files
committed
quicksort fixes
1 parent 5e6ebba commit 09af83d

File tree

3 files changed

+62
-30
lines changed

3 files changed

+62
-30
lines changed

book/chapters/quick-sort.adoc

+39-12
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
= Quicksort
22
(((Sorting, QuickSort)))
33
(((QuickSort)))
4-
Quicksort is an efficient recursive sorting algorithm that uses <<Divide and Conquer, divide and conquer>> paradigm to sort faster. It can be implemented in-place so it doesn't require additonal memory.
4+
Quicksort is an efficient recursive sorting algorithm that uses <<Divide and Conquer, divide and conquer>> paradigm to sort faster. It can be implemented in-place, so it doesn't require additonal memory.
55

66
indexterm:[Divide and Conquer]
7-
In practice quicksort outperforms efficient sorting algorithms like <<Merge Sort>>. And, of course, It also outperforms simple sorting algorithms like <<Selection Sort>>, <<Insertion Sort>> and <<Bubble Sort>>.
7+
In practice, quicksort outperforms other sorting algorithms like <<Merge Sort>>. And, of course, It also outperforms simple sorting algorithms like <<Selection Sort>>, <<Insertion Sort>> and <<Bubble Sort>>.
88

9-
Quicksort basically picks a "pivot" element (preferably random) and move all the elements that are smaller than the pivot to the right and the ones that are bigger to the left. It does this recursively until all the array is sorted.
9+
Quicksort picks a "pivot" element (preferably random) and move all the parts that are smaller than the pivot to the right and the ones that are bigger to the left. It does this recursively until all the array is sorted.
1010

1111
== Quicksort Implementation
1212

1313
Quicksort implementation uses the divide-and-conquer in the following way:
1414

1515
.Quicksort Algorithm
16-
. Pick a "pivot" element (at random)
17-
. Move everything that is lower than the pivot to the left and everything that is bigger than the pivot to the right.
18-
. Recursively repeat step 1 and 2, the sub-arrays on the left and on the right WITHOUT including the pivot.
16+
. Pick a "pivot" element (at random).
17+
. Move everything lower than the pivot to the left and everything more significant than the pivot to the right.
18+
. Recursively repeat step #1 and #2 in the sub-arrays on the left and on the right WITHOUT including the pivot.
1919

2020
Let's convert these words into code!
2121

@@ -29,19 +29,45 @@ include::{codedir}/algorithms/sorting/quick-sort.js[tag=quickSort, indent=0]
2929
<3> Do the partition of the sub-array at the right of the pivot.
3030
<4> Only do the partition when there's something to divide.
3131

32-
The real heavy-lifting is don in the partion function. Let's implement that:
32+
The `partition` function does the real heavy-lifting. 🏋️‍♀️
3333

3434
.Quicksort implementation in JavaScript (partition)
3535
[source, javascript]
3636
----
3737
include::{codedir}/algorithms/sorting/Quick-sort.js[tag=partition, indent=0]
3838
----
39-
<1> Make the rightmost element as the pivot.
40-
<2> This is the place holder for the final pivot index. We start in low and as we move all the lower elements to the left we will get the final place where the pivot should be.
41-
<3> Move one element at a time comparing it to the pivot value.
42-
<4> If the current element value is less than the pivot, then increment pivot index (pivot should be place after all the lower values). We also swap the value before incrementing because current element that is lower than the pivot to be at its left side.
39+
<1> Take the leftmost element as the pivot.
40+
<2> `pivotFinalIndex` is the placeholder for the final position where the pivot will be placed when the array is sorted.
41+
<3> Check all values other than the pivot to see if any value is smaller than the pivot.
42+
<4> If the `current` element's value is less than the pivot, then increment `pivotFinalIndex` to make room on the left side.
43+
<5> We also swap the smaller element to the left side since it's smaller than the pivot.
44+
<6> Finally, we move the pivot to its final position. Everything to the left is smaller than the pivot and everything to the right is bigger.
4345

44-
Merge sort has a _O(n log n)_ running time. For more details about the how to extract the runtime go to <<Linearithmic>>.
46+
*What would happen if use Quicksort for an array in reverse order?*
47+
48+
E.g. `[10, 7, 5, 4, 2, 1]`, if we always choose the first element as the pivot, we would have to swap everything to the left of `10`.
49+
50+
So in the first partition we would have `[7, 5, 4, 2, 1, 10]`.
51+
Then, we take `7` would be the next pivot and we have to swap everything to the left.
52+
This is the worst-case for this quicksort since it will perform O(n^2^) work.
53+
If instead of partitioning by the first we do it by the middle element (or even better at random) we would have better peformance. That's why we usually shuffle the array to avoid edge cases.
54+
55+
[source, javascript]
56+
----
57+
include::{codedir}/algorithms/sorting/Quick-sort.js[tag=sort, indent=0]
58+
----
59+
<1> Convert to array (or clone array). If you want to modify the input directly remove this line.
60+
<2> Shuffle array to avoid edge cases (desc order arrays)
61+
62+
And you can see the implementation of `shuffle` below:
63+
64+
.Shuffling an array
65+
[source, javascript]
66+
----
67+
include::{codedir}/algorithms/sorting/sorting-common.js[tag=shuffle, indent=0]
68+
----
69+
70+
With the optimization, Quicksort has a _O(n log n)_ running time. Similar to the merge sort we divide the array into halves each time. For more details about the how to extract the runtime go to <<Linearithmic>>.
4571

4672
== Quicksort Properties
4773

@@ -60,3 +86,4 @@ indexterm:[Space Complexity, Linear]
6086
// https://algs4.cs.princeton.edu/23quicksort/
6187
// https://twitter.com/mathias/status/1036626116654637057?lang=en
6288
// https://www.toptal.com/developers/sorting-algorithms/quick-sort
89+
// https://stackoverflow.com/q/19255999/684957 // Is Quicksort “adaptive” and “online”?

src/algorithms/sorting/quick-sort.js

+11-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { swap } = require('./sorting-common');
1+
const { swap, shuffle } = require('./sorting-common');
22

33
// tag::partition[]
44
/**
@@ -14,18 +14,18 @@ const { swap } = require('./sorting-common');
1414
* @returns {integer} pivot index
1515
*/
1616
function partition(array, low, high) {
17-
const pivotInitialIndex = high; // <1>
18-
let pivotIndex = low; // <2>
17+
const pivotIndex = low; // <1>
18+
let pivotFinalIndex = pivotIndex; // <2>
1919

20-
for (let current = low; current < high; current += 1) { // <3>
21-
if (array[current] <= array[pivotInitialIndex]) { // <4>
22-
swap(array, current, pivotIndex);
23-
pivotIndex += 1;
20+
for (let current = pivotIndex + 1; current <= high; current++) {
21+
if (array[current] < array[pivotIndex]) { // <3>
22+
pivotFinalIndex += 1; // <4>
23+
swap(array, current, pivotFinalIndex); // <5>
2424
}
2525
}
2626

27-
swap(array, pivotInitialIndex, pivotIndex);
28-
return pivotIndex;
27+
swap(array, pivotIndex, pivotFinalIndex); // <6>
28+
return pivotFinalIndex;
2929
}
3030
// end::partition[]
3131

@@ -54,9 +54,11 @@ function quickSort(array, low = 0, high = array.length - 1) {
5454
* Quick sort
5555
* Runtime: O(n log n)
5656
* @param {Array|Set} collection elements to be sorted
57+
* @returns {Array} sorted array
5758
*/
5859
function quickSortWrapper(collection) {
5960
const array = Array.from(collection); // <1>
61+
shuffle(array); // <2>
6062
return quickSort(array);
6163
}
6264
// end::sort[]

src/algorithms/sorting/sorting-common.js

+12-9
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,23 @@ function swap(array, from, to) {
1212
}
1313
// end::swap[]
1414

15+
// tag::shuffle[]
1516
/**
16-
* Move an element in an array *from* a postion *to* another.
17+
* Shuffle items in an array in-place
1718
* Runtime: O(n)
18-
* @param {array} array
19-
* @param {integer} from index of the element to remove (source)
20-
* @param {integer} to index where the removed element would be move (destination)
19+
* @param {*} array
2120
*/
22-
function moveElement(array, from, to) {
23-
if (from === to + 1) return;
24-
const [elementToInsert] = array.splice(from, 1); // delete from position
25-
array.splice(to + 1, 0, elementToInsert); // insert element in to the position.
21+
function shuffle(array) {
22+
const { length } = array;
23+
for (let index = 0; index < length; index++) {
24+
const newIndex = Math.floor(Math.random() * length);
25+
swap(array, index, newIndex);
26+
}
27+
return array;
2628
}
29+
// end::shuffle[]
2730

2831
module.exports = {
2932
swap,
30-
moveElement,
33+
shuffle,
3134
};

0 commit comments

Comments
 (0)