FORWARD
迷茫中摸索着前进

快速排序

         对于包含n个数的输入数组来说,快速排序是一种最坏情况时间复杂度为 的排序算法,虽然最坏情况时间复杂度很差,但是快速排序通常是实际排序应用中最好的选择,因为它的平均性能非常好:它的期望时间复杂度是 ,另外,它能够进行原址排序,甚至在虚拟环境中也能很好地工作。随机化版本在任何的输入情况(包括最坏情况)下获得均匀的运行时间。

算法描述

         分解:数组A[p..r]被划分为两个(可能为空)子数组A[p..q-1]和A[q+1..r],使得A[p..q-1]中的每一个元素都小于等于A[q],而A[q]也小于等于A[q+1..r]中的每个元素,其中,计算下标q也是划分过程的一部分。

         解决:通过递归调用快速排序,对子数组A[p..q-1]和A[q+1..r]进行排序

         合并:因为子数组都是原址排序,所以不需要合并操作:数组A[p..r]已经有序

        

伪代码

         QUICKSORT( A, p, r)

                   if  p < r

                            q = PARTITION(A, p, r)

                            QUICKSORT(A, p, q-1)

                            QUICKSORT(A, q+1, r)

         为了排序一个数组A的全部元素,初始调用时QUICKSORT(A, 1, A.length)

         数组的划分:

         算法的关键部分是PARTITION过程,实现了对子数组A[p..r]的原址重排

         PARTITION(A, p, r)

         x = A[r]

         i = p – 1

         for  j = p  to  r – 1

                   if  A[j] <= x

                            i = i +1

                            exchange A[i] with A[j]

         exchange A[i+1] with A[r]

         return i+1

性能分析

  时间的复杂度可以表示为:T(n) = T(k) + T(n-k-1) + \theta(n)

  其中 T(k), T(n-k-1) 对应程序中的递归调用,k是在划分后比基准元素小的元素个数。 \theta(n) 就是partition函数的复杂度。

  算法的效率对不同的输入数据会有所不同,可以分为下面3中情况:

  1) 最坏的情况。当分区过程总是挑选最大或最小的元素作为支点。如果我们考虑以上的分区策略,其中最后一个元素总是被挑为支点,当数组已经排序的递增或递减顺序时,会发生最坏的情况:

      T(n) = T(0) + T(n-1) +\theta(n), 即 T(n) = T(n-1) +\theta(n)

      此时的复杂度为 O(n^2)

  2) 最好的情况。发生在最好的情况下,当分区过程总是挑选最中间元素为支点。

      T(n) = 2 T(n/2) +\theta(n)

      此时的递归表达式和归并排序是一样的。时间时间复杂度为 O(n lg n).

  3) 平均情况

    要做到平均情况分析,我们需要考虑数组中的所有可能的排列,并计算每一个排列所需要的时间,这种计算是比较麻烦的。

    可以这么分析,如果划分的结果是这样的两部分 O(n/10) 和 O(9n/10)。递归式为:  T(n) =  T(n/10) + T(n9/10) +\theta(n)

    最终的计算结果也为:O(n lg n).

    虽然快速排序的最坏情况下复杂度为O(N^2),这比很多其他的排序算法,如归并排序和堆排序,在实践中更快,因为它的内部循环可以在大多数架构有效地实现,

代码实现

#include<stdio.h>

void swap(int *, int ,int);
int partion_1(int *, int, int);
int partion_2(int *, int, int);
void quicksort(int *, int, int);

main(){
    int array[]={23,3,35,5,75,2,34,45,73,21,9,33};
    int i, len;
    
    for (i = 0; i < 12; i++)
        printf("%d\t",array[i]);
    putchar('\n');
    quicksort(array, 0, 11);
    for (i = 0; i < 12; i++)
        printf("%d\t",array[i]);
    putchar('\n');    
}

void swap(int *array, int p, int q){
    int temp;
    
    temp = array[p];
    array[p] = array[q];
    array[q] = temp;
}

int partion_1(int *array, int p, int q){
    //选择第一个元素作为主元 
    int i, last;
    int piviot;
    
    piviot = array[p];
    
    for (last = p, i = p+1; i <= q; i++){
        if (array[i] <= piviot)
            swap(array, ++last, i);
    }
    swap(array, p, last);
    return last;
}

int partion_2(int *array, int p, int q){
//选择最后一个元素作为主元
    int piviot;
    int i,j;
    
    piviot = array[q];
    
    for (i = p, j = p-1; i < q; i++){
        if (array[i] <= piviot){
            swap(array, i, ++j);
        }
    }
    swap(array, q, ++j);
    return j;
}

void quicksort(int *array, int begin, int end){
    int medium;
    
    if (begin < end){
        medium = partion_1(array, begin, end);
        //medium = partion_2(array, begin, end);
        quicksort(array, begin, medium-1);
        quicksort(array, medium+1, end);
    }
}

 

 

改进版本

         如果要排列的数组本身就有序,那么上述的程序会发生较多次自己和自己交换的情况,此时会造成效率低下问题。下面的程序使用两个“指针”(i和j),一前一后地同时进行扫描交换,效率会更好

int partion(int *array, int p, int q){
    int i, j, piviot;
    
    i = p;
    j = q+1;
    piviot = array[p]; 
    
    while(1){
        while(i < q && array[++i] < piviot );//略过比piviot小的数
        while(j > p &&array[--j] > piviot); //略过比piviot大的数
        if (i >= j)
            break;
        swap(array, i, j);
    }
    array[p] = array[j];
    array[j] = piviot;
    return j;
}

 

 

随机化版本伪代码(算法改进)

         为了对所有输入的情况都获得平均运行时间,在算法中引入随机性,从而使得算法对于所有的输入都能获得较好的期望性能。在这里采用一种随机抽样的随机化技术,与之前始终采用A[r]作为主元的方法不同,随机抽样时从子数组A[p..r]中随机选择一个元素作为主元,为了达到这一目的,首先将A[r]与从A[p..r]中随机选取的一个元素交换。因为主元元素时随机选取的,我们期望在平均情况下对输入数组的划分是比较平衡的。

         对PARTITION和QUICKSORT的代码的改动非常小,在新的划分程序中,我们只是在真正进行划分前进行一次交换。

         RANDOMIZED-PARTITION(A, p, r)

                   i = RANDOM(p, r)

                   exchange A[r] with A[i]

                   return PARTITION(A, p, r)

 

         RANDOMIZED-QUICKSORT(A, p, r)

                   if p < r

                            q = RANDOMIZED-PARTITION(A, p, r)

                            RANDOMIZED-QUICKSORT(A, p, q-1)

                            RANDOMIZED-QUICKSORT(A, q+1, r)

 

posted on 2014-07-27 16:17  Lin GH  阅读(711)  评论(0编辑  收藏  举报