快速排序
快速排序的思路是,先选择任意元素为轴,将比轴小的元素都放在其左边,比轴大的元素都放在其右边,这样轴的位置一定是正确的。之后通过递归,对轴左右两边的元素都进行快速排序。最终实现整个数组有序。
那么第一个问题来了,怎么选择轴呢?这里b站的coderwhy老师给出一种方案:让数组首尾中三处元素当中的中位数作为初始轴,最大值放在末尾,最小值放在开头。最后将初始轴和数组倒数第二位的元素交换,将轴返回。
代码如下:
ArrayList.prototype.median = function (left, right) { // 以首尾中三处元素的中位数作为轴的初值 let center = Math.floor((left + right) / 2) let x = this.array[left], y = this.array[center], z = this.array[right] let arr = [x, y, z] arr.sort( (a, b) => { return a - b }) this.array[left] = arr[0] this.array[center] = arr[1] this.array[right] = arr[2] // 将轴放在倒数第二个元素上 this.swap(right - 1, center) return this.array[right - 1] }
我们已经得到了轴,并且轴位于数组的倒数第二位,倒数第一位的元素已经确认大于轴,所以不用关心。接下来,只需要考虑轴前面的元素即可。
定义两个指针,左指针 i 指向数组开头,右指针指向轴
let i = left,
j = right - 1
接下来两个代码块写在一个死循环中:
左指针逐个格子向右移动,当遇到大于或等于轴的值时,就停下来。
右指针逐个格子向左移动,当遇到小于或等于轴的值时,就停下来。
简单的说就是把轴左边的大值移到右边,把轴右边的小值移到左边。
while (this.array[++i] < pivot) {} while (this.array[--j] > pivot) {}
若左指针还在右指针的左边,将两指针所指的值交换位置。否则,退出循环。
if (i < j) { this.swap(i, j) } else { break }
退出循环时,左指针的位置就是轴元素应该所处的位置:
this.swap(i, right - 1)
递归调用当前方法处理轴左、右两边的元素:
this.quick(left, i - 1) this.quick(i + 1, right)
接下来需要考虑一些特殊情况,比如轴左边只有一个元素,不需要进行处理;或者轴右边只有两个元素,必要的话交换一下位置即可:
if (left >= right) return if( right - left == 1){ if(this.array[left] > this.array[right]){ this.swap(left, right) } return }
完整代码如下:
ArrayList.prototype.quickSort = function () { let length = this.array.length this.quick(0, length - 1) }
ArrayList.prototype.quick = function (left, right) { if (left >= right) return if( right - left == 1){ if(this.array[left] > this.array[right]){ this.swap(left, right) } return } let pivot = this.median(left, right) let i = left, j = right - 1 while (true) { while (this.array[++i] < pivot) {} while (this.array[--j] > pivot) {} if (i < j) { this.swap(i, j) } else { break } } this.swap(i, right - 1) this.quick(left, i - 1) this.quick(i + 1, right) }