快速排序算法的几种版本及实现

写在前面的话

        最近系统地学习了快速排序算法,在此作一笔记。主要包括快排的各种版本:普通版本,改进的普通版本,随机化版本,三数值取中分割版本和Stooge版本。对各版本进行了简要分析,并给出了具体实现。

《算法导论》对快排的描述

        快速排序是基于分治模式的,下面是对一个典型子数组A[p..r]排序的分治过程的三个步骤:
        分解:数组A[p..r]被划分成两个(可能空)子数组A[p..q-1]和A[q+1..r],使得A[p..q-1]中的每个元素都小于等于A(q),而且,小于等于A[q+1..r]中的元素。下标q也在这个划分过程中计算。
        解决:通过递归调用快速排序,对子数组A[p..q-1]和A[q+1..r]排序。
        合并:因为两个子数组是就地排序的,它们的
合并不需要操作:整个数组A[p..r]已排序

        快速排序的关键是PARTITION(分解)过程,它对子数组A[p..r]进行就地重排。PARTITION结果的好坏直接影响快排的效率。

        下面用具体代码来分析各种版本的快排。首先是《算法导论》7.1节描述的,普通版的快排。

 #include"stdio.h" 

/*普通版快速排序*/

//函数声明

void QuickSort(int A[],int n);

void QSort(int A[],int l, int r);

int Partition(int A[],int l, int r);

 

int Partition(int A[],int p, int r) //分割过程

{

    int t;

    int x = A[r];

    int i = p - 1;

    for(int j = p; j < r; j++)

    {

        if(A[j] <= x)

        {

             i++;

             t = A[j]; A[j] = A[i]; A[i] = t;

        }

    }

    t = A[i+1]; A[i+1] = A[r]; A[r] = t;

    return i+1;

}

 

void QSort(int A[],int l, int r) //三个参数的快排子模块

{

    int p;

    if(l < r)

    {

        p = Partition(A, l, r);

        QSort(A, l, p-1);

        QSort(A, p+1, r);

    }

}

 

void QuickSort(int A[],int n)   //快排

{

    QSort(A, 0, n-1);

}

 

void main()    //验证排序

{

    int a[5]={6,2,4,8,5};

    QuickSort(a, 5);

 

    for(int i=0;i<5;i++)

    {

        printf("%d ",a[i]);

    }

    printf("\n");

 

}

    这个版本的PARTITION方法简单易懂,但有时会有一些不必要的交换发生,如排序 2 8 1 3 5 6 4

2 8 1 3 5 6 4
第一次交换8和1
2 1 8 3 5 6 4
第二次交换8和3
2 1 3 8 5 6 4
最后一次交换8和4
2 1 3 4 5 6 8
一次分割完成

    对PARTITION过程稍加修改,得到优化的分解过程:

int Partition(int A[], int l, int r) //稍加优化的分割过程

{

    int t;

    int x = A[r];

    int i = l-1, j = r;

    while(1)

    {

        while(A[++i] < x)

        ;

        while(A[--j] > x)

        ;

        if(i < j)

        {

             t = A[i]; A[i] = A[j]; A[j] = t;

        }

        else

             break;

    }

    t = A[i]; A[i] = A[r]; A[r] = t;

    return i;

}

   这一PARTITION过程通过比较,减少了交换次数。

    上面两种方法都把输入数组的最后一个元素作为主元,即哨兵元素。如果输入是随机的,那么这是可以接受的,但是如果输入是预排序的或是反序的,那么这样的主元就产生一个劣质的分割,因为所有元素不是都被划入A[p..q-1]就是都被划入A[q+1..r]。下面给出两种可以避免产生劣质分割的PARTITION过程。

/*随机分割法*/

int Partition(int A[], int p, int r)

{

    int t;

    int rd = rand() % (r-p+1) + p;

    assert(rd >= p && rd <= r);

 

    t = A[r]; A[r] = A[rd]; A[rd] = t;/*将主元放在位置r */

 

    int x = A[r];

    int i = p-1, j = r;

    while(1)

    {

        while(A[++i] < x)

        ;

        while(A[--j] > x)

        ;

        if(i < j)

        {

             t = A[i]; A[i] = A[j]; A[j] = t;

        }

        else

             break;

    }

    t = A[i]; A[i] = A[r]; A[r] = t;

    return i;

}

 

/*三数值取中分割法*/

int Partition(int A[], int p, int r)

{

    int t;

    int c = (p+r)/2;

    if(A[p] > A[c])

    {

        t = A[p]; A[p] = A[c]; A[c] = t;

    }

    if(A[p] > A[r])

    {

        t = A[p]; A[p] = A[r]; A[r] = t;

    }

    if(A[c] > A[r])

    {

        t = A[c]; A[c] = A[r]; A[r] = t;

    }

    /*使得A[l] <= A[c] <= A[r] */

 

    int k = r - 1;

    t = A[c]; A[c] = A[k]; A[k] = t; /*将主元放在r-1的位置*/

 

    int x = A[k];

    int i = p-1, j = k;

    while(1)

    {

        while(A[++i] < x)

        ;

        while(A[--j] > x)

        ;

        if(i < j)

        {

             t = A[i]; A[i] = A[j]; A[j] = t;

        }

        else

             break;

    }

    t = A[i]; A[i] = A[k]; A[k] = t;

    return i;

}

   另外,Howard、Fine教授提出了一种“漂亮的”排序算法,称为Stooge排序。它将待排序数组分为3段,递归地分别对输入数组的前2/3、后2/3和前2/3进行排序。这是一个号称很厉害的排序算法。说它厉害并不是因为它有多么的快,事实上它比插入排序还要慢。它的厉害之处在于,用一般的掰手指头的方法绝对无法证明它的正确性。关于其证明,详见http://apps.hi.baidu.com/share/detail/19522201。具体算法如下:

<

/*Stooge_sort Stooge排序 */ 

void stooge(int A[], int i, int j)

{

    if(A[i] > A[j])

    {// exchange

        int temp = A[i];

        A[i] = A[j];

        A[j] = temp;

    }

 

    if(i+1 >= j)

    return;

 

    int k = (j-i+1)/3;// first 2/3

    stooge(A, i, j-k);// last 2/3

    stooge(A, i+k, j);// first 2/3

    stooge(A, i, j-k);

}

 

int partition(int A[], int p, int r)

{

    int x = A[r];

    int i = p-1;

    for(int j=p; j<r; j++)

    {

        if(A[j] <= x)

        {

             i++;

             int temp = A[i];

             A[i] = A[j];

             A[j] = temp;

        }

    }

    int temp = A[i+1];

    A[i+1] = A[r];

    A[r] = temp;

    return i+1;

}

 

int main()

{

int A[] = {3,5,2,7,4,10,1,9};

 

for(int i=0;i<8;i++)

        printf("%d ",A[i]);

    printf("\n");

 

int x = partition(A, 0, 3);

 

int len =sizeof(A)/sizeof(int);

stooge(A, 0, len-1);

 

for(int i=0;i<8;i++)

        printf("%d ",A[i]);

    printf("\n");

 

return 0;

}

/p> 

        Stooge排序简单易懂,但遗憾的是其效率很低,因为递归和比较的次数太多。

 

 

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值