快速排序
概念:
快速排序采用分治法对列表元素进行排序。即将问题分解成子问题,再将子问题分解成子子问题,直到最后的子问题不能分解为止。
工作原理:
- 在数组中选择一个元素,这个元素被称为基准(Pivot)。通常把数组中的第一个或中间的元素或最后一个元素作为基准。
- 然后,重新排列数组的元素,以使基准左侧的有元素都小于基准,而右侧的所有元素都大于基准。这一步称为分区。如果一个元素等于基准,那么在哪一侧都无关紧要。
- 针对基准的左侧和右侧分别重复这一过程,直到对数组完成排序。
接下来通过一个例子理解这些步骤。假设有一个含有未排序元素 [7, -2, 4, 1, 6, 5, 0, -4, 2]
的数组。选择最后一个元素作为基准。数组的分解步骤如下图所示:
在算法的步骤1中被选为基准的元素带颜色。分区后,基准元素始终处于数组中的正确位置。
黑色粗体边框的数组表示该特定递归分支结束时的样子,最后得到的数组只包含一个元素。
最后可以看到该算法的结果排序。
代码实现:
这个算法最主要的是分区的思想,我们先抽出一个数组分区的方法:
function partition(arr, start, end){ // 以最后一个元素为基准 const pivotValue = arr[end]; let pivotIndex = start; for (let i = start; i < end; i++) { if (arr[i] < pivotValue) { // 交换元素(最后pivotIndex前面的值比pivotIndex后面的值小) [arr[i], arr[pivotIndex]] = [arr[pivotIndex], arr[i]]; // 移动到下一个元素 pivotIndex++; } } // 把基准值放在中间(pivotIndex的位置,不一定是数组最中间的位置) [arr[pivotIndex], arr[end]] = [arr[end], arr[pivotIndex]] return pivotIndex; };
递归方法1(以末尾元素为基准):
function quickSortRecursive(arr, start, end) { // 终止条件 if (start >= end) { return; } // 返回 pivotIndex let index = partition(arr, start, end); // 将相同的逻辑递归地用于左右子数组 quickSortRecursive(arr, start, index - 1); quickSortRecursive(arr, index + 1, end); }
递归方法2(以中间元素为基准)
var devide_Xin = function (array, start, end) { if(start >= end) return array; var baseIndex = Math.floor((start + end) / 2), // 基数索引 i = start, j = end; while (i <= j) { while (array[i] < array[baseIndex]) { i++; } while (array[j] > array[baseIndex]) { j--; } if(i <= j) { var temp = array[i]; array[i] = array[j]; array[j] = temp; i++; j--; } } return i; } var quickSort_Xin = function (array, start, end) { if(array.length < 1) { return array; } var index = devide_Xin(array, start, end); if(start < index -1) { quickSort_Xin(array, start, index - 1); } if(end > index) { quickSort_Xin(array, index, end); } return array; }
测试:
array = [7, -2, 4, 1, 6, 5, 0, -4, 2] quickSortRecursive(array, 0, array.length - 1) console.log(array) // [-4,-2,0,1,2,4,5,6,7]
效率:
快速排序在最坏情况下的时间复杂度是O( n^2 ) 。平均时间复杂度为O(nlogn)