数组排序算法 — 快速排序

对于经典算法,你是否也遇到这样的情形:学时觉得很清楚,可过阵子就忘了?

本系列文章就尝试解决这个问题。

研读那些排序算法,细品它们的名字,其实都很贴切。

比如快速排序,一个快字就能体现出其价值,因而它是用得最多的。

因为它相对难一些,本系列将分两篇文章讲解它。

快速排序这个名字是针对其性能来起的,但很难让人做到见名知意。

所以,给它重新起了个名字:归分排序。

与归并算法一样,归分算法也是分而治之算法,讲究分、归、并。归并的重头戏在于如何去合并,快排的重头戏在于如何去划分。

image.png

上图中,先把数组按最后一个元素4作为分界点,把数组一分为三。除了分界点之外,左子部分全是小于等于4的,右子部分全是大于4的,它们可以进一步递归排序。因为是原地排序(不需要额外空间),因此不需归并那种合并操作。

其中,归相对容易些,该算法的核心是:如何把数组按分界点一分为三

各个教程的实现方式不一,这里我介绍一个最容易理解的方式。

具体过程是这样的,选取最后一个元素为分界点,然后遍历数组找小于等于分界点的元素,然后往数组前面交换。比如:

image.png

上图中,我们按顺序找小于等于4的元素,共1、2、3、4。然后分别与数组的前4个元素交换即可,结果自然是一分为三。

是不是非常容易理解的思路?快排也不难学嘛。

我们用JS实现一遍:

let array = [7, 1, 6, 5, 3, 2, 4]
let j = 0
let pivot = array[array.length - 1]
for (let i = 0; i < array.length; i++) {
  if (array[i] <= pivot) {
    swap(array, i, j++)
  }
}
console.log(array) // [ 1, 3, 2, 4, 7, 6, 5 ]

其中swap函数封装了两个元素如何交换:

function swap(array, i, j) {
  [array[i], array[j]] = [array[j], array[i]]
}

进一步封装成函数:

function partition(array, start, end) {
  let j = start
  let pivot = array[end]
  for (let i = start; i <= end; i++) {
    if (array[i] <= pivot) {
      swap(array, i, j++)
    }
  }
  return j - 1
}

start和end表示数组起止下标。最后返回的j-1是分界点的位置。

接下来就需要递归处理左子部分和右子部分了。

对于递归,虽然它不符合线性思维,但其实也没啥难的。

只要有递归步骤(递归公式),很容翻译成代码的。

我们再回忆一下快排算法的步骤:

  1. 数组分成三部分left、pivot、right,使left<=pivot,right>pivot。
  2. 递归处理left
  3. 递归处理right

轻松翻译成代码:

function quickSort(array, start = 0, end = array.length -1) {
  let pivotIndex = partition(array, start, end)
  quickSort(array, start, pivotIndex - 1)
  quickSort(array, pivotIndex + 1, end)
  return array
}

递归是自身调用自身,不能无限次的调用下去,因此需要有递归出口(初始条件)。

它的递归出口是,当数组元素个数为小于2时,就是已经是排好序的,不需要再递归调用了。

因此需要在前面加入代码:

if (end - start < 1) return array

至此,快速排序原理和实现已经说完了。

快排的算法主要在于partition函数的实现,不同教程的实现方式都不一样,这个需要注意一下。

其时间复杂度平均是O(nlogn)。最坏情形是,假如待排的数组已经是排好序的,该算法将退化成O(n^2)级的。此时可以通过合理的分区点选择来避免。常见策略有选中间、随机选、三选一等。假如这里我们随机选一个分区点,再与最后的元素交换,就能大概率避免最坏情形的出现。完整代码:

const utils = {
  swap(array, a, b) {
    [array[a], array[b]] = [array[b], array[a]]
  },
  randomNum() {
    return Math.floor(Math.random() * 100)
  },
  randomArray() {
    return Array.from(Array(this.randomNum()), _ => this.randomNum())
  }
}

function partition(array, start, end) {
  let j = start
  let pivot = array[end]
  for (let i = start; i <= end; i++) {
    if (array[i] <= pivot) {
      utils.swap(array, i, j++)
    }
  }
  return j - 1
}

function quickSort(array, start = 0, end = array.length -1) {
  if (end - start < 1) return array
  let pivotIndex = partition(array, start, end)
  quickSort(array, start, pivotIndex - 1)
  quickSort(array, pivotIndex + 1, end)
  return array
}
let array = utils.randomArray()
console.log(quickSort(array))

这里总结一下,快速排序是原地算法,不需要额外空间,但递归是需要空间的的(相当于手动维护个调用栈),总体空间复杂度是O(logn)。相等元素可能会交换前后顺序,因而不是稳定排序(因为交换)。时间复杂度为O(nlogn)。

快速排序,要做到能分分钟手写出来,是需要掌握其排序原理的。关键在于,如何按照分界点把数组一分为三。至于递归,只要能说清楚递归步骤和出口,就能很容易写出来,不需要死记硬背的。

希望有所帮助,本文完。

参考链接:https://juejin.im/post/5d75f77e5188253e4b2f0d3d

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