【基础算法】少女时代的快速排序

    BigMoyan准备做一个连续的文章,以算法导论为蓝本,尽量用亲切生动的语言把一些经典的算法讲清楚,欢迎来捧场~

    本人并非计算机专业学生,文章有疏漏错误之处,还望专家们指正,在此谢过

-------------------------------------------------------------------------------------------------------------------------

【少女时代从左到右:徐珠贤,金泰妍,金孝渊,林允儿,权侑莉,郑秀妍,李顺圭,黄美英,崔秀英】               

  排序虽然是件小事,但由于这个操作在算法中使用太过频繁,而显得尤其重要,我记得上C语言课的时候学了第一个排序算法,名字很可爱叫冒泡泡(划掉)排序。原理很简单,比方说要按身高给图片中的妹子升序排序,那就比个子呗,徐珠贤1.68,金泰妍1.58,那她俩一比,金泰妍换左一,徐珠贤换左二,然后接着2号位的金泰妍和3号金孝渊比。这样一轮过后以后,最高的妹子排在了最后,左边是水底,右边是水面,金泰妍就像一只气泡咕嘟咕嘟冒了上去。

  类似于冒泡排序这样简单粗暴的排序方法还有插入排序,选择排序,因为这些算法过于简单,而且效率也不太好,我们就不一一介绍了,毕竟今天的主题是快速排序。

  快速排序(Quicksort)是一种基于分治策略的排序方法。所谓分治策略,就是分而治之。要把大象装冰箱,一共分三步,一个分治算法也是这样:

  Step1:分解(Divide)把要解决的问题划分为一系列子问题,子问题的形式与原问题一样。

  Step2:解决(Conquer)递归地解决子问题,如果问题规模足够小,则停止递归直接求解。

  Step3:合并(Combine)将子问题的解组合为原问题的解。

  大致了解了分治策略,我们首先给出快速排序的算法的思路,照葫芦画瓢,我们试着把大象装冰箱。

  Step1:分解:待排序的数组A[p..r]被分解为两个子数组A[p…q-1]和A[q+1,r](可能为空),使得a[p…q-1]中的每个元素均小于等于A[q],而A[q+1,r]中的每个元素都大于等于A[q]。这里写成A[p…r]而不是A[0…r]是为了一般情况,因为子数组并非都是从0开始的,例如对A[q+1,r]快速排序时p=q+1.

  Step2:解决:对每个子数组进行快速排序

  Step3:合并:不需要合并,子数组排序好后原数组自然就有序了

  稍微解释一下,快速排序其实就做了两件事,第一,找一个点把数组分成满足要求的两个子数组。第二,对子数组快速排序。当然在对子数组进行快速排序的时候又要重复这两件事——为子数组找一个点将其分为两个孙数组(瞎编的名词,理解就好),第二,对孙数组快速排序。如此子子孙孙无穷尽也。

  啊不对,当然是有穷的,当一个重孙数组只有一个数的时候就不需要排序了,这个就叫做分治策略里的”基本情况“,直接解决就好了。

  好了,下面进入愉快的”给妹子排身高“(坏笑)环节咯,据我所查资料(我也是蛮较真的),来自少女时代的妹子们从左到右的身高依次是:

  A=[168,158(金泰妍),160,167(允儿),167(权侑莉),162,158(李顺圭),163,170]

  好了我确保这些数据是真的你就不要翻回去来回看照片确认了= =(以为我不知道你在干嘛吗?)

  身高相同的妹子我在括号里记了它的名字以做区分,因为快速算法是一个不稳定算法,我们随后会通过名字看到它的不稳定性。

  在愉快的排序之前,我们发现快速排序的核心其实是那个神奇的q,要求A[p…q-1]都小于等于A[q],而A[q+1…r]都要大于等于A[q],那这个q其实蛮不好找的,在少女时代中,对于初始序列,满足这个条件的q只有排在最后的崔秀英,因为她170厘米是最高的,她左边的人都低于她,而她右边——抱歉没有了,就是个空序列。

  事实上,这个q并不是我们寻找的,而是构造出来的。我们首先规定A[q]就是左一的徐珠贤,然后遍历整个序列,以徐珠贤为中心(key),凡是比她低的都站在她左边,比她高的都站在她右边,这样整理过后,徐珠贤就成了满足条件的A[q]。当然选择谁为key是随便的事情,为了方便,一般而言我们总是选择序列的第一项为key。

    这个过程我们起个名字叫Partition,它的伪代码如下:

  

  虽然看这种文章出现代码很恼火,但搞科研的你们,不要被几行伪代码带走哟~

  有了Partition,我们就可以愉快的看妹子了,还记得她们的身高吗,是这样的:

    [168,158(金泰妍),160,167(允儿),167(权侑莉),162,158(李顺圭),163,170]

  试着自己走一遍,对于初始序列p=0,r=8,这里我们用C语言从0计数的习惯。

  OK,key=168,i=9,j=8,这是初始状态

开始循环:

  j=8,判断170>168 于是 i=8,exchange(A[8],A[8]) 也就是不交换

  j=7,判断 163<168,于是什么都不做

  j=6,判断 158<168,于是什么都不做

  因为徐珠贤是除了170的崔秀英以外最高的,所以循环其实什么都没做,循环结束后j=1,i=8

  然后exchange(A[7],A[0]),return i-1=7

  结果0号的徐珠贤(168)与7号黄美英(163)交换位置,返回指标7

      [163,158(金泰妍),160,167(允儿),167(权侑莉),162,158(李顺圭),168,170]

  怎么样,找到了q=7了吧,验证一下对于A[0..6]所有人身高均小于168,而对于A[8],A[8]>A[7]

  之后对于分好的子序列递归的调用就可以了,代码很简单,理解递归的同学都能写出来,至于怎么理解递归,有一句话说得好,想要理解递归,首先你要理解递归。

  我用Python写了这个程序,最后返回的结果是这样的。

[158(李顺圭),158(金泰妍),160,162,163,167(林允儿),167(权侑莉),168,170]

  啊,看起来好像很不错嘛。

  可是等等,好像哪里不太对

  对比一下原序列:

      A=[168,158(金泰妍),160,167(允儿),167(权侑莉),162,158(李顺圭),163,170]

  原来允儿是在权侑莉的左边,而金泰妍在李顺圭的左边。经过排序后,虽然允儿和权侑莉的相对顺序没有变,但李顺圭和金泰妍的相对顺序却反过来了!也就是说,如果我们把括号后面的名字去掉,只看身高,是判断不出来158cm的是金泰妍还是李顺圭!

  还记得我们最开始说的冒泡算法吗?事实证明,如果用冒泡算法来排这个序,最后的结果并没有这种相对顺序的变化。这种排序后相同指标的记录相对顺序不改变的性质称为排序算法的稳定性,即冒泡排序是一个稳定的算法,而快速排序是不稳定算法。

排序算法的稳定性在某些应用中是有要求的,尤其是当参加排序的指标是某个类实体的一个属性时,不稳定算法往往会带来意外的结果,使得原本不希望交换的两个类实体发生交换。

  此外,快速排序之所以称为快速排序,是因为其平均情况下的复杂度是O(nlogn),这是现存的所有排序算法中最高效的算法了。

    ps.python的代码附在下面~

 1 A=[['xzx',168],['jty',158],['jxy',160],['lye',167],['qyl',167],['zxy',162],['lsg',158],['hmy',163],['cxy',170 ]]
 2 
 3 def partition(A,p,r):
 4     key=A[p][1]
 5     i=r+1
 6     for j in range(r,p,-1):
 7         if A[j][1]>=key:
 8             i=i-1
 9             A[i],A[j]=A[j],A[i]
10     A[i-1],A[p]=A[p],A[i-1]
11     return i-1
12 
13 
14 def quicksort(A,p,r):
15     if p<r:
16         q=partition(A,p,r)
17         quicksort(A,p,q-1)
18         quicksort(A,q+1,r)
19     return 1
20 
21 quicksort(A,0,8)
22 print A
23  

 

posted @ 2015-05-22 10:22  BigMoyan  阅读(937)  评论(0编辑  收藏  举报