当前位置:编程学习 > C/C++ >>

经典算法研究系列:十二、快速排序算法所有版本的c/c++实现

作者:July、二零一一年三月二十日。
出处:http://blog.csdn.net/v_JULY_v。
--------------------------------------------------

 

前言:

    相信,经过本人之前写的前俩篇关于快速排序算法的文章:第一篇、一、快速排序算法,及第二篇、一之续、快速排序算法的深入分析,各位,已经对快速排序算法有了足够的了解与认识。但仅仅停留在对一个算法的认识层次上,显然是不够的,即便你认识的有多透彻与深入。最好是,编程实现它。
    而网上,快速排序的各种写法层次不清,缺乏统一、整体的阐述与实现,即,没有个一锤定音,如此,我便打算自己去实现它了。

    于是,今花了一个上午,把快速排序算法的各种版本全部都写程序一一实现了一下。包括网上有的,没的,算法导论上的,国内教材上通用的,随机化的,三数取中分割法的,递归的,非递归的,所有版本都用c/c++全部写了个遍。
    鉴于时间仓促下,一个人考虑问题总有不周之处,以及水平有限等等,不正之处,还望各位不吝赐教。不过,以下,所有全部c/c++源码,都经本人一一调试,若有任何问题,恳请指正。

    ok,本文主要分为以下几部分内容:

第一部分、递归版
一、算法导论上的单向扫描版本
二、国内教材双向扫描版
      2.1、Hoare版本
      2.2、Hoare的几个变形版本
三、随机化版本
四、三数取中分割法
第二部分、非递归版

    好的,请一一细看。


第一部分、快速排序的递归版本
一、算法导论上的版本
在我写的第二篇文章中,我们已经知道:
“再到后来,N.Lomuto又提出了一种新的版本,此版本....,即优化了PARTITION程序,它现在写在了 算法导论 一书上”:

快速排序算法的关键是PARTITION过程,它对A[p..r]进行就地重排:

PARTITION(A, p, r)
1  x ← A[r]         //以最后一个元素,A[r]为主元
2  i ← p - 1
3  for j ← p to r - 1    //注,j从p指向的是r-1,不是r。
4       do if A[j] ≤ x
5             then i ← i + 1
6                  exchange A[i] <-> A[j]
7  exchange A[i + 1] <-> A[r]    //最后,交换主元
8  return i + 1

然后,对整个数组进行递归排序:

QUICKSORT(A, p, r)
1 if p < r
2    then q ← PARTITION(A, p, r)   //关键
3         QUICKSORT(A, p, q - 1)
4         QUICKSORT(A, q + 1, r)

    根据上述伪代码,我们不难写出以下的c/c++程序:
首先是,PARTITION过程:

int partition(int data[],int lo,int hi)
{
 int key=data[hi];  //以最后一个元素,data[hi]为主元
 int i=lo-1;
 for(int j=lo;j<hi;j++)   ///注,j从p指向的是r-1,不是r。
 {
  if(data[j]<=key)
  {
   i=i+1;
   swap(&data[i],&data[j]);
  }
 }
 swap(&data[i+1],&data[hi]);   //不能改为swap(&data[i+1],&key)
 return i+1;
}

补充说明:举个例子,如下为第一趟排序(更多详尽的分析请参考第二篇文章):
第一趟(4步):
   a:3   8   7   1   2   5   6   4   //以最后一个元素,data[hi]为主元

   b:3   1   7   8   2   5   6   4

   c:3   1   2   8   7   5   6   4

   d:3   1   2   4   7   5   6   8    //最后,swap(&data[i+1],&data[hi])

  而其中swap函数的编写,是足够简单的: 

void swap(int *a,int *b)
{
 int temp=*a;
 *a=*b;
 *b=temp;
}

    然后是,调用partition,对整个数组进行递归排序:

void QuickSort(int data[], int lo, int hi)
{
    if (lo<hi)
    {
        int k = partition(data, lo, hi);
        QuickSort(data, lo, k-1);
        QuickSort(data, k+1, hi);
    }
}

     现在,我有一个问题要问各位了,保持其它的不变,稍微修改一下上述的partition过程,如下:

int partition(int data[],int lo,int hi)   //请读者思考
{
 int key=data[hi];  //以最后一个元素,data[hi]为主元
 int i=lo-1;
 for(int j=lo;j<=hi;j++)   //现在,我让j从lo指向了hi,不是hi-1。
 {
  if(data[j]<=key)
  {
   i=i+1;
   swap(&data[i],&data[j]);
  }
 }
 //swap(&data[i+1],&data[hi]);   //去掉这行
 return i;                       //返回i,非i+1.
}

    如上,其它的不变,请问,让j扫描到了最后一个元素,后与data[i+1]交换,去掉最后的swap(&data[i+1],&data[hi]),然后,再返回i。请问,如此,是否可行?
    其实这个问题,就是我第二篇文章中,所提到的:
    “上述的PARTITION(A, p, r)版本,可不可以改成这样咧?以下这样列”:

PARTITION(A, p, r)   //请读者思考版本。
1  x ← A[r]
2  i ← p - 1
3  for j ← p to r      //让j 从p指向了最后一个元素r
4       do if A[j] ≤ x
5             then i ← i + 1
6                  exchange A[i] <-> A[j]
//7  exchange A[i + 1] <-> A[r]   去掉此最后的步骤
8  return i      //返回 i,不再返回 i+1.

    望读者思考,后把结果在评论里告知我。

    我这里简单论述下:上述请读者思考版本,只是代码做了以下三处修改而已:1、j从 p->r;2、去掉最后的交换步骤;3、返回 i。首先,无论是我的版本,还是算法导论上的原装版本,都是准确无误的,且我都已经编写程序测试通过了。但,其实这俩种写法,思路是完全一致的。

    为什么这么说列?请具体看以下的请读者思考版本,

int partition(int data[],int lo,int hi)   //请读者思考
{
 int key=data[hi];  //以最后一个元素,data[hi]为主元
 int i=lo-1;
 for(int j=lo;j<=hi;j++)   //....
 {
  if(data[j]<=key)           //如果让j从lo指向hi,那么当j指到hi时,是一定会有A[j]<=x的
  {
   i=i+1;
   swap(&data[i],&data[j]);
  }
 }
 //swap(&data[i+1],&data[hi]);   //事实是,应该加上这句,直接交换,即可。
 return i;                       //
}

    我们知道当j最后指到了r之后,是一定会有A[j]<=x的(即=),所以这个if判断就有点多余,没有意义。所以应该如算法导论上的版本那般,最后直接交换swap(&data[i+1],&data[hi]);   即可,返回i+1。所以,总体说来,算法导论上的版本那样写,比请读者思考版本更规范,更合乎情理。ok,请接着往下阅读。

   

    当然,上述partition过程中,也可以去掉swap函数的调用,直接写在分割函数里:

int partition(int data[],int lo,int hi)
{
 int i,j,t;
 int key = data[hi];   //还是以最后一个元素作为哨兵,即主元元素

补充:软件开发 , C++ ,
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,