基本思想
- 从数组中取出一个数, 称之为基数(pivot)
- 遍历数组, 将比基数大的数放到其右边,比基数小的数放到其左边. 遍历完成后, 数组就根据基数被分为了左右两个区域.
- 将左右两个区域视为两个数组, 重复前面两个步骤直到排序完成.
在排序的过程中,我们可以选择数组第一个或最后一个元素作为基数, 也可以随机选择一个元素作为基数. 就算法的时间复杂度来说, 随机选择的效率更高. 因为随机选择的时间复杂度恒为O(nlogn), 而选择首尾元素的复杂度则是[O(nlogn), O(n^2)], 因为选择首位在遇到正序或者逆序数组时会遇到每次排序完成后划分的左右区间有一个为空, 只能完成一个数字排序的情况.
快速排序的每一次遍历,都将基数摆到了最终位置上。第一轮遍历排好 1 个基数,第二轮遍历排好 2 个基数(每个区域一个基数,但如果某个区域为空,则此轮只能排好一个基数),第三轮遍历排好 4 个基数(同理,最差的情况下,只能排好一个基数),以此类推。总遍历次数为 logn~n 次,每轮遍历的时间复杂度为 O(n),所以很容易分析出快速排序的时间复杂度为 O(nlogn) ~ O(n2),平均时间复杂度为 O(nlogn)。
执行细节
在寻找基数的正确位置时, 我们通过左右两个指针left和right不断寻找数组左边>基数的数字和右边<基数的数字并将它们互相交换再缩小数组范围(left+1, right-1). 当左右指针相遇后, 判断右指针所代表元素是否为数组右半区域第一个小于基数的元素, 若是则交换基数与右指针所在元素, 若不是则将右指针向左移动一位再交换基数与其的位置. 最后返回右指针的位置.
代码
/**
* @param {number[]} nums
* @return {number[]}
*/
var sortArray = function(nums) {
const quickSort = (start, end, nums) => {
if (start >= end) return
let pivotNum = nums[start]
let l = start + 1, r = end
while (l < r) {
while (l < r && nums[l] <= pivotNum) l++
while (l < r && nums[r] >= pivotNum) r--
if (l < r) {
[nums[l], nums[r]] = [nums[r], nums[l]];
l++
r--
}
}
if (l == r && nums[r] > pivotNum) {
r--
}
[nums[start], nums[r]] = [nums[r], nums[start]];
quickSort(start, r-1, nums)
quickSort(r+1, end, nums)
}
quickSort(0, nums.length-1, nums)
return nums
};
稳定性
不稳定 [2', 2'', 1] => ([1, 2'', 2']