换一个角度理解快速排序算法

在掌握快速排序算法以前,我曾浏览过中文维基百科上关于快速排序算法的代码演示以及啊哈磊的文章:坐在马桶上看算法:快速排序 - 51CTO.COM,但后者的代码演示与前者存在差异。经过二者分别对同一数组进行手动排序并比较,我认为前者的算法更为合理。前端

咱们先自定义一个没有通过排序的数组,看一看快速排序算法是如何对数组中的各个元素进行排序的。算法

13, 25, 6, 1, 7, 63, 96, 71, 49, 52, 30, 28, 84, 74, 93, 69, 40

这是一个数组。咱们从中任选一个数做为基准数c,好比30。后端

在开始执行排序操做以前,咱们须要声明两个分列数组两端的整型变量a和b(a、b的值等于停留位置的数值的索引值),做为控制比较和交换数值的指针。将指针a置于最左侧,指针b置于最右侧。数组

其中,当指针a停留的数<c,就向右移动一个单位;当指针b停留的数>c,就向左移动一个单位。至于移动的条件为何不包括等于基准数c的情形,其实很容易理解:当基准数是数组中最小的数或最大的数时,可能致使指针a或b产生越界行为。优化

当a再也不位于b的左侧时,此回合交换即结束。指针

在啊哈磊的文章中,他率先移动的是右侧的指针。咱们从移动左侧的指针开始。code

在这个数组中,13至7之间的数都小于30,故指针a第一次停留时的数是63;而84至40之间的数都大于30,故指针b第一次停留时的数是28。htm

如今交换63和28。新的排列顺序以下(a、b指针的位置已标出,下同):blog

13, 25, 6, 1, 7, 28, 96, 71, 49, 52, 30, 63, 84, 74, 93, 69, 40
------------------a-----------------------b--------------------

因为a依然位于b的左侧,此过程继续进行。排序

a继续向右移动,停留在96的位置。b继续向左移动,停留在30(基准数)的位置。

如今交换96和30。新的排列顺序以下:

13, 25, 6, 1, 7, 28, 30, 71, 49, 52, 96, 63, 84, 74, 93, 69, 40
----------------------a---------------b------------------------

a继续向右移动,停留在71的位置。b继续向左移动,停留在30的位置:

13, 25, 6, 1, 7, 28, 30, 71, 49, 52, 96, 63, 84, 74, 93, 69, 40
----------------------b---a------------------------------------

虽然a、b停留位置的数都知足交换条件,但b停留的位置是a曾停留过的位置,即b位于a的左侧而不是右侧。故此回合结束。

不过啊哈磊在他的文章中并无这样叙述,而是令两个指针重合并将停留的数与基准数交换。但你仍须要知道他的这一作法是为了帮助你更好地了解快速排序算法。而若是待排序数列中的基准数是一个众数,那么这种交换反而是没有意义的。实际上,不管是先移动指针a仍是先移动指针b,只要容许指针a位于指针b的右侧,二者中先移动哪个的最终结果都是异曲同工。

从新观察a、b指针停留的位置。咱们能够发现:

a停留位置左侧的全部数一定不大于基准数;b停留位置右侧的全部数一定不小于基准数。

这两个结论适用于快速排序的任何阶段。

进一步地说,若是指针b在某次停留时的位置位于指针a的左侧,那么指针b一定紧邻指针a。

而当指针b位于指针a的左侧时,就能够将这个数组一分为二:

13, 25, 6, 1, 7, 28
------------------b
96, 71, 49, 52, 30, 63, 84, 74, 93, 69, 40
-a----------------------------------------

而指针b则成为前一个数列的后端指针,指针a则成为后一个数列的前端指针。

在新的数列中设置新的基准数并重复执行上述排序步骤,直至最后的每一个数列除了该数列的基准数之外没有任何其余数,就完成了对这个数组的排序。

可是,有一种状况须要注意:

当a和b停留的数值都是基准数时,必须令指针a再向右移动一个单位。这样才能保证分割的两个数列不包含重复的值(重复的数即基准数)。

相比渐进式比较相邻两个数的冒泡排序算法,以跳跃式比较两个数的快速排序算法无疑具备冒泡排序算法不可比拟的优势。固然,前者与后者在最坏状况下的时间复杂度是相等的。

不过,原始的快速排序算法不能保证相等的数的相对顺序不变,它们的顺序可能被打乱。若是须要保留这种相对位置,必须对算法进行优化。

相关文章
相关标签/搜索