快速排序算法原来这么简单

原理简述

快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。

步骤为(From Wikipedia):

  1. 从数列中挑出一个元素,称为"基准"(pivot)
  2. 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
  3. 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。

简单实现

先上一个看起来巨简单的实现:

const quickSort = (arr) => {
  if (arr.length <= 1) {
    return arr;
  }
  const left = arr.filter((e, i) => e < arr[0] && i !== 0);
  const right = arr.filter((e, i) => e >= arr[0] && i !== 0);
  return [...quickSort(left), arr[0], ...quickSort(right)];
  // return quickSort(left).concat([arr[0]], quickSort(right));
};

const arr = [15, 10, 6, 34, 21, 66, 32];
console.log(quickSort(arr));

解释起来就很方便,从数组里取出第0个元素作为基准数,然后过滤数组里的元素,比基准数小的,放到left,剩下的放right。当然,要排除第0个元素本身。最后将它们连接起来,两边各自递归下去。

作为算法呢,用了两次filter其实不太划算,但比这更重要的是,这个实现占用了额外的内存空间。

原地排序(in-place)

其实原理也不太难,每次递归,都是将我们的基准数放到它最终应该在的位置

比如对于arr = [8, 10, 6, 34, 21, 66, 32]这样的数组,我们还是每次取第0个元素作为基准数。

初始状态:

NO. Pivot 0 1 2 3 4 5 6
0 void 8 10 6 34 21 66 32

将第0个元素8作为基准数,并且把0号的位置挖出来,等待其他元素填入:

NO. Pivot 0 1 2 3 4 5 6
0 void 8 10 6 34 21 66 32
1 8 void 10 6 34 21 66 32

从右边开始遍历,直到找到一个小于基准数8的6,将其放入0号坑位:

NO. Pivot 0 1 2 3 4 5 6
0 void 8 10 6 34 21 66 32
1 8 void 10 6 34 21 66 32
2 8 6 10 void 34 21 66 32

这时再从左边开始遍历,直到找到一个大于基准数8的10,将其放入2号坑位:

NO. Pivot 0 1 2 3 4 5 6
0 void 8 10 6 34 21 66 32
1 8 void 10 6 34 21 66 32
2 8 6 10 void 34 21 66 32
3 8 6 void 10 34 21 66 32

这时再从右边开始遍历,直到找到一个大于基准数8的……没了,那就说明1号坑位就是基准数8的窝了,将它填入:

NO. Pivot 0 1 2 3 4 5 6
0 void 8 10 6 34 21 66 32
1 8 void 10 6 34 21 66 32
2 8 6 10 void 34 21 66 32
3 8 6 void 10 34 21 66 32
4 void 6 8 10 34 21 66 32

这时候要进入到递归了,我们已经将本次的基准数归位到1号位置了,那么接下来就是要排序arr[0]和arr[2..-1],左边的就一个元素,刚好符合递归终止条件,直接return掉就可以了。右边还有五个元素,按照上述步骤继续递归下去就OK啦~

JavaScript(ES6) 代码实现:

const quickSort = (arr, left = 0, right = arr.length - 1) => {
  if (left >= right) {
    return;
  }

  let i = left;
  let j = right;

  // 取第一个值作为基准值
  let pivotVal = arr[left];

  // 取第一个位置作为坑位
  let blankIndex = left;

  while (i < j) {
    while (i < j && arr[j] >= pivotVal) {
      j -= 1;
    }
    if (i < j) {
      arr[blankIndex] = arr[j];
      blankIndex = j;
    }

    while (i < j && arr[i] <= pivotVal) {
      i += 1;
    }
    if (i < j) {
      arr[blankIndex] = arr[i];
      blankIndex = i;
    }
  }
  arr[blankIndex] = pivotVal;

  quickSort(arr, left, blankIndex - 1);
  quickSort(arr, blankIndex + 1, right);
};

const arr = [15, 10, 6, 34, 21, 66, 32];
quickSort(arr);
console.log(arr);

其中blankIndexpivotVal是为了可读性添加的,为了节省点代码,也是可以省略掉的。比如两次判断,可以简化成下面这样,最后i和j相等,所以取哪个都可以。

while (i < j) {
  while (i < j && arr[j] >= arr[left]) {
    j -= 1;
  }
  if (i < j) {
    arr[i] = arr[j];
  }

  while (i < j && arr[i] <= arr[left]) {
    i += 1;
  }
  if (i < j) {
    arr[j] = arr[i];
  }
}
arr[i] = arr[left];
// arr[j] = arr[left];
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容

  • Ba la la la ~ 读者朋友们,你们好啊,又到了冷锋时间,话不多说,发车! 1.冒泡排序(Bub...
    王饱饱阅读 1,794评论 0 7
  • 某次二面时,面试官问起Js排序问题,吾绞尽脑汁回答了几种,深感算法有很大的问题,所以总计一下! 排序算法说明 (1...
    流浪的先知阅读 1,192评论 0 4
  • 原博客 1.选择排序(Selection Sort): 选择最小元素,移动到首位置。 (1)算法描述和实现: 首先...
    Gitfan阅读 540评论 0 0
  • 引言 全栈工程师(本文称「全栈」开发者)和 DevOps 无疑是近期最火的词汇,无论是国外还是国内。而且火爆程度远...
    OneAPM阅读 453评论 0 3
  • 偷懒的三个境界,勤奋的三个高度 第一种偷懒叫“傻子”:这种偷懒很明显,别人一眼就可以看出来。这种也是大众眼中不勤奋...
    a0001911cc5a阅读 412评论 2 0