快排

来源:互联网 发布:聊天机器人软件 编辑:程序博客网 时间:2024/04/30 03:10

在谈到快排之前,我们先来看一个猜数字游戏。

有两个小孩,在玩一个猜数字游戏,由其中一个小孩心中默念一个[0,100]之间的数字,让另一个小孩来猜测这个数字是多少。为了保证在什么情况下都能以最小的次数猜中,那么,另外一个小孩应该采取什么样的策略呢?很显然,二分。先猜测是不是位于[0,50],排除掉一半的可能性,然后继续对区间进行二分,每次都排除掉一半的可能性,最终我们肯定能猜测到这个数字,无论让你猜的数字是多少,你最终都能在O(lgn)的时间复杂度里面得出结果。其实,这么一个小小的猜数字游戏,体现了程序设计里面的一种思想:二分,在程序设计中,二分的思想用的太多了,几乎涉及到lgn的,都跟二分有关,而这个二分跟快排有什么关系呢?因为快排中也用到了二分的思想,下面我们来看看快排吧。

快排

快速排序(Quicksort)是对冒泡排序的一种改进。由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

从上面对快排的解释中,我们可以看出,快排中所用的思想类似于猜数字游戏的思想,它将一个序列划分成两个部分,其中一部分的所有数据都比另外的一部分的所有数据小,然后不断对这两个区间进行递归划分,最终我们能得到一个有序序列,那么,我们每次进行划分的依据是什么呢?也就是怎么确定一部分的所有数据比另外一部分的所有数据小,一般的做法是选取该区间第一个元素或者是最后一个元素为枢纽元 ,然后以此为划分的依据,将比枢纽元 小的数放到左边,大于等于枢纽元 的数放到其右边,然后递归进行调用,最终,整个序列就已经排好序了。下面我们来看个例子:

假设现有序列为44,22,30,88,51,51,89,88,3,18,那么,我们来看看快排是怎么做的(以下均是选取首元素作为枢纽元)



  1. 选取44作为枢纽元,将区间划分为两部分:{3,22,30,18}和{51,51,89,88,88},此时对于44这个数来说,我们就已经确定了它在排好序中的位置了,因为44的左边都是比它小的,右边都是大于或者等于它的,所以对于44,已经确定了它的最终位置,不用在将其与其他数做比较了。
  2. 对于区间{3,22,30,18},我们选取3作为枢纽元,将区间划分为{22,30,18},由于已经没有比3更小的元素了,所以此时对于3来说,3已经确定好了它最终排好序的位置了,不再对其进行操作,对于区间{51,51,89,88,88},选取51作为枢纽元,将序列划分为{51,89,88,88},对于枢纽元51来说,在这段区间中没有比其更小的元素了,所以51的位置也确定了,
  3. 不断重复上面的操作,最终我们就能得到一个排好序的序列。

快排的实现
上面讲述了快排的具体做法,下面看下具体的代码实现:
第一种:
void quickSort(int* a,int l,int r){    if(l >=r)        return ;    int i,j;    for(i=j=l+1;i<=r;++i)    {        //找到比枢纽元小的元素,将其放入        if(a[i]<a[l])        {            swap(a[i],a[j++]);        }    }    swap(a[l],a[--j]);    //分别对左右部分进行递归调用    quickSort(a,l,j-1);    quickSort(a,j+1,r);}

对于上述这种实现方法,优点是:代码实现简单,不容易出错,能很快的实现编码,缺点就是:对于i来说,移动的长度为r-l,而对j来说,移动的长度不确定,要看具体的数据,介于0到r-l之间,所以i和j总共移动的长度总是大于或等于r-l的。
第二种:
void quickSort(int* a,int left,int right){    if(left>=right)        return ;    int l=left,r=right,temp = a[left];    while(l < r)    {        while(l<r && a[r]>=temp)            r--;        a[l] = a[r];        while(l<r && a[l]<temp)            l++;        a[r] = a[l];    }    a[l] = temp;    quickSort(a,left,l-1);    quickSort(a,l+1,right);}

这是一个比较常用的实现方法,它克服了上面第一种移动的长度大于等于r-l的缺点,不过实现起来比第一种麻烦一点。

好了,以上对快排做了个大概的概述,顺带说一声,快排不是严格的O(NlgN)的时间复杂度,当序列为有序时,会退化成线性结构,所以它的最坏复杂度为O(N^2),富有弹性。各位可以想想怎么用快排在一个数组中求k个最小的数,例如:序列1,9,8,7,4,23,4,当k为3时,结果为1,4,4