快速排序

快速排序最坏效率:O(N2)
快速排序平均效率:O(N log N )
快速排序最好效率:O(N
log N )

分区指的是从数组随机选取一个值,以其为轴,将比它小的值放到它左边,比它大的值放到它右边。

分区步骤如下。

(1)左指针逐个格子向右移动,当遇到大于或等于轴的值时,就停下来。
(2)右指针逐个格子向左移动,当遇到小于或等于轴的值时,就停下来。
(3)将两指针所指的值交换位置。
(4)重复上述步骤,直至两指针重合,或左指针移到右指针的右边。
(5)将轴与左指针所指的值交换位置。

快速排序严重依赖于分区。它的运作方式如下所示。

(1)把数组分区。使轴到正确的位置上去。
(2)对轴左右的两个子数组递归地重复第1、2步,也就是说,两个子数组都各自分区,并形成各自的轴以及由轴分隔的更小的子数组。然后也对这些子数组分区,以此类推。
(3)当分出的子数组长度为0或1时,即达到基准情形,无须进一步操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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: http://www.algomation.com/algorithm/quick-sort-visualization
*
* @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;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Sort {
constructor(originalCallbacks) {
this.callbacks = Sort.initSortingCallbacks(originalCallbacks);
this.comparator = new Comparator(this.callbacks.compareCallback);
}

/**
* @param {SorterCallbacks} originalCallbacks
* @returns {SorterCallbacks}
*/
static initSortingCallbacks(originalCallbacks) {
const callbacks = originalCallbacks || {};
const stubCallback = () => {};

callbacks.compareCallback = callbacks.compareCallback || undefined;
callbacks.visitingCallback = callbacks.visitingCallback || stubCallback;

return callbacks;
}

sort() {
throw new Error('sort method must be implemented');
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
class Comparator {
/**
* @param {function(a: *, b: *)} [compareFunction] - It may be custom compare function that, let's
* say may compare custom objects together.
*/
constructor(compareFunction) {
this.compare = compareFunction || Comparator.defaultCompareFunction;
}

/**
* Default comparison function. It just assumes that "a" and "b" are strings or numbers.
* @param {(string|number)} a
* @param {(string|number)} b
* @returns {number}
*/
static defaultCompareFunction(a, b) {
if (a === b) {
return 0;
}

return a < b ? -1 : 1;
}

/**
* Checks if two variables are equal.
* @param {*} a
* @param {*} b
* @return {boolean}
*/
equal(a, b) {
return this.compare(a, b) === 0;
}

/**
* Checks if variable "a" is less than "b".
* @param {*} a
* @param {*} b
* @return {boolean}
*/
lessThan(a, b) {
return this.compare(a, b) < 0;
}

/**
* Checks if variable "a" is greater than "b".
* @param {*} a
* @param {*} b
* @return {boolean}
*/
greaterThan(a, b) {
return this.compare(a, b) > 0;
}

/**
* Checks if variable "a" is less than or equal to "b".
* @param {*} a
* @param {*} b
* @return {boolean}
*/
lessThanOrEqual(a, b) {
return this.lessThan(a, b) || this.equal(a, b);
}

/**
* Checks if variable "a" is greater than or equal to "b".
* @param {*} a
* @param {*} b
* @return {boolean}
*/
greaterThanOrEqual(a, b) {
return this.greaterThan(a, b) || this.equal(a, b);
}

/**
* Reverses the comparison order.
*/
reverse() {
const compareOriginal = this.compare;
this.compare = (a, b) => compareOriginal(b, a);
}
}