快速排序(英语:Quicksort),又称划分交换排序(partition-exchange sort),
简称快排,一种排序算法,最早由东尼·霍尔提出。
在平均状况下,排序n个项目要O(log n)(大O符号)次比较。
在最坏状况下则需要 O(n^2)次比较,但这种状况并不常见。
事实上,快速排序通常明显比其他算法更快,
因为它的内部循环(inner loop)可以在大部分的架构上很有效率地达成。

解释

以上是 维基百科 关于快速排序的描述,转换为人话就是大概这么几条:

  • 分治策略 把原来为 n 的问题拆分为几个部分
  • 确定一个中心点,通过以中心点为基准去做判断
  • 平均执行时间都是 O(lgn)

首先快速通过确定一个中心点,让其余元素依次去和该中心点去比较。如果比中心点小则放左边,比中心点大则放右边。这个放左右边并不是简简单单的判断大小而已,比如现在有个数组:

const arr = [1,8,3,9,4,5,7]

现在要对它进行快速排序核心在于判断中心点。大概有这么几个流程:

  1. 确定一个中心点(默认先以最后一个元素7作为中心点)
  2. 拿这个中心点依次去和数组中的其他元素做比较
  3. 如果数组中某个元素小于该中心点,说明顺序正确,不做处理
  4. 如果数组中某个元素大于该中心点,需要做交换
  5. 左边第一个大于中心点的值,要和从右边第一个小于中心点的值做交换,那么此时做完交换,原数组就变成了:
const arr = [1,5,3,9,4,8,7]
6.从左边第二个大于中心点的值,要和从右边第二个小于中心点的值做交换,那么此时做完交换。此时,原数组又变成了:
const arr = [1,5,3,4,9,8,7]
7.最后,很重要一点。要将中心点放到数组的中间位置
const arr = [1,5,3,4,7,9,8]
8.那么可以看到,第一个中心点7,便被确定好了位置。7永远只能这个位置,不可能变换位置。接下来对中心点左右两边的剩余数组,做相同的递归处理即可。

kuaisu.gif

上图是一个快速排序的动态演示。这个中心点是可以随意选取的,上面动图中是以起点作为中心点。

JS实现

/**
 * 快速排序
 * @param {无序数组} arr
 * @param {起点} left
 * @param {终点} right
 */
function qsort(arr, left = 0, right = arr.length) {
  if (right - left <= 1) return false; // 数组长度小于1 返回

  // 首先通过 partition 函数来确定中心点位置
  const m = partition(arr, left, right);

  // 递归中心点左边数组区间
  qsort(arr, left, m);
  // 递归中心点右边数组区间
  qsort(arr, m + 1, right);
  
  // 该函数很简单 就是一个递归函数,核心在下方的 partition 函数
}

/**
 * 该函数完成的就是上述的8条流程
 * @param {无序数组} arr
 * @param {起点} left
 * @param {终点} right
 */
function partition(arr, left, right) {
  let middle = arr[right - 1]; // 取数组中最后一个值作为中心点
  let i = left;
  let j = right - 1; // 最后索引

  while (i !== j) {
    // 当i等于j的时候,就表示所有元素都已确认 (i 不断增加 j不断减小)
    if (arr[i] < middle) {
      // 元素小于中心点 不做处理
      i++; // i + 1 判断下一个元素
    } else {
      // 元素大于中心点 做值交换 同时j要--
      swap(arr, i, --j);
    }
    // i 不断增加 j不断减小 那么:
    // 此时 未排序确认的范围就是[i, j]
    // 小于中心点范围: [left, i) 左闭右开区间
    // 大于中心点范围: [j, right - 1) 左闭右开区间
  }

  // while循环结束之后 要再做一次最后的值交换
  // 调整中心点位置 将位于数组末尾的中心点 放到属于它的位置
  // 即是i的位置(或者j, 因为while结束后i===j)
  swap(arr, i, right - 1);

  // 最后返回中心点位置
  return i;
}

function swap(arr, i, j) {
  [arr[i], arr[j]] = [arr[j], arr[i]];
}
测试一下
const arr_1 = [10, 50, 30, 90, 40, 80, 70];

qsort(arr_1);

console.log(arr_1);

//[10, 30, 40, 50,70, 80, 90]

其实说白了,快速排序就是一个不断确定中心点不断分割数组的过程。这个分割数组并不是真正的在电脑内存去新建空间。而是通过中心点将左右两边标识为两个区间。再对这两个区间做递归操作。所以,快速的排序的空间复杂度很小,仅为O(1)

尾递归优化 qsort

/**
 * 快速排序
 * @param {无序数组} arr
 * @param {起点} left
 * @param {终点} right
 */
function qsort(arr, left = 0, right = arr.length) {
    while(left < right){ // 增加while判断条件 
      // 和上面一样
      const m = partition(arr, left, right);

      // 首先递归中心点左边数组区间 和上面一样
      qsort(arr, left, m);

      // 上面左区间递归完毕之后 将left赋值 
      // 等于p+1 也就是右区间的第一个值
      // 这样就开启了右区间的比较
      // 通过一个while 去掉一个函数调用 性能得到提升
      left = m + 1;
    }
}

Funky_Tiger
443 声望33 粉丝

刷题,交流,offer,内推,涨薪,相亲,前端资源共享...