特性:

排序方法 时间复杂度 空间复杂度 稳定性
快速排序 O(nlogn) O(logn) 不稳定

和归并排序的对比

快速排序 归并排序
特点 两个子数组都有序时,则整个数组也就自然有序了 将数组分成两个子数组分别排序,并将有序的子数组归并
切分 取决于数组的内容 等分两半

思路:

选取一个“切分“点,确保:

  1. 切分点的左侧都小于它。
  2. 切分点的右侧都大于它。
  3. 分别对切分点进行排序

过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据比另一部分的所有数据要小,再按这种方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,使整个数据变成有序序列。

特点:将一个数组分成两个子数组,将两部分独立排序。

伪代码:

  1. function quickSort(a, lo, hi) {
  2. if (hi <= lo) return;
  3. const j = partition(a, lo, hi); // 切分,快速排序的关键
  4. quickSort(a, lo, j - 1);
  5. quickSort(a, j + 1, hi);
  6. }

切分

  1. const { less, exch } = require('./util');
  2. function partition(a, lo, hi) {
  3. let i = lo;
  4. let j = hi + 1;
  5. // 随意选取a[lo]做为切分元素
  6. const v = a[lo];
  7. while(1) {
  8. // 从数组的左端开始向右扫描,直到寻找到一个大于等于v的元素
  9. while(less(a[++i], v])) if(i === hi) break;
  10. // 从数组的右端开始向左扫描,直到寻找到一个小于等于v的元素
  11. while(less(v, a[--j])) if (j === lo) break;
  12. // 边界条件:如果指针相遇,则退出循环
  13. if(i >= j) break;
  14. // 调换他们的位置,一定可以找到,如果找不到则在上一步终止了
  15. exch(a, i, j);
  16. }
  17. // 交换v和指针相遇位置的值
  18. exch(a, lo, j);
  19. return j;
  20. }

实现

  1. const { less, exch, getArr } = require('../utils');
  2. function partition(arr, lo, hi) {
  3. let i = lo;
  4. let j = hi + 1;
  5. // 随意选取一个作为“切分”点
  6. const v = arr[lo];
  7. while (1) {
  8. // 从左到右寻找到大于 v 的值(下标)
  9. while (less(arr[++i], v)) {
  10. if (i === hi) break;
  11. }
  12. // 从右到左寻找到小于 v 的值(下标)
  13. while (less(v, arr[--j])) {
  14. if (j === lo) break;
  15. }
  16. // 如果指针(下标)对撞则终止循环
  17. if (i >= j) break;
  18. // 交换小的值
  19. exch(arr, i, j);
  20. }
  21. exch(arr, lo, j);
  22. return j;
  23. }
  24. function quickSort(arr, lo, hi) {
  25. if (lo === undefined) {
  26. lo = 0;
  27. hi = arr.length - 1;
  28. }
  29. if (lo >= hi) return;
  30. const j = partition(arr, lo, hi); // 获取切分点
  31. quickSort(arr, lo, j - 1);
  32. quickSort(arr, j + 1, hi);
  33. return arr;
  34. }

算法改进

切换到插入排序

小数组使用使用插入排序吧,长度在5-15内用这个,很快;

三取样切分

使用子数组的一小部分元素的中位数来切分数组,代价是需要计算中位数。

三向切分的快速排序

针对重复数据比较多的数组。

  1. function quick3way(a, lo, hi) {
  2. if (hi <= lo) return;
  3. let lt = lo; // a[lo, ...lt - 1]中的所有元素都小于v
  4. let gt = hi; // a[gt + 1, ...hi]中的所有元素都大于v
  5. let i = lo + 1; // a[lt, ...i]中的所有元素都等于v
  6. // a[i, ...gt] 中的元素还未确定
  7. let v = a[lo];
  8. while(i <= gt) {
  9. let cmp = a[i] - v;
  10. // a[i] < v,交换 a[lt] 和 a[i],lt指针、i指针右移,将小于v的都放到v的右边
  11. if (cmp < 0) exch(a, lt++, i++);
  12. // a[i] > v,交换 a[i] 和 a[gt],gt左移,将大于v的都迁移到v的右边
  13. else if (cmp > 0) exch(a, i, gt--);
  14. // 相等情况移动i指针
  15. else i++
  16. }
  17. sort(a, lo, lt - 1);
  18. sort(a, gt + 1, hi);
  19. }