算法【排序三】

高效排序(快速排序)

快速排序可能是应用最广泛的排序算法,实现简单,适用于各种不同的输入数据且在一般应用中比其他排序算法都要快的多。

快速排序是一种分治的方法。将一个数组分成两个子数组,将两部分独立地排序。快速排序和归并排序互补:归并排序将数组分成两个子数组分别排序,并将有序的子数组归并以将整个数组排序;而快速排序则是当两个子数组都有序时整个数组也就自然有序。归并排序,递归调用发生在处理整个数组之前;快速排序,递归调用发生在处理数据之后。 在归并排序中,一个数组被等分两半;在快速排序中,切分位置取决于数组的内容。

快速排序的大致过程如图所示。利用切分元素,将数组分割成两部分,左半部分元素小于等于切分元素,右半部分元素大于等于切分元素。递归地对子数组进行同样的切分,实现排序。

快速排序

快速排序的关键在于切分,这个过程使得数组满足下面三个条件:

  • 对于某个j, a[j] 已经排定;
  • a[lo] 到 a[j-1] 中的所有元素都不大于a[j];
  • a[j+1] 到 a[hi] 中的所有元素都不小于a[j].

代码

排序实现分三个部分,主排序方法,递归调用函数及切分方法。

template <class T> class SortMethod
{
public:
    static void quick_sort(T a[], int N);
private:
    static int partition(T a[], int lo,int hi);
    static void quick_sort(T a[], int lo, int hi);
}

// 切分
template<class T> int SortMethod<T>::partition
(T a[], int lo,int hi)
{
    // 将数组切分为 a[lo,...,i-1],a[i],a[i+1,...,hi]
    int i = lo, j = hi + 1;
    T temp = a[lo]; // 切分元素

    while(true)
    {
        // 扫描左右,检查扫描是否结束并交换元素
        while( less(a[++i],temp) ) if( i == hi ) break;
        while( less(temp,a[--j]) ) if( j == lo ) break;

        if( i >= j ) break;
        exch(a,i,j);
    }

    exch(a,lo,j);

    return j;
}

//递归调用
template<class T> void SortMethod<T>::quick_sort
(T a[], int lo, int hi)
{
    if( lo >= hi ) return;
    int j = partition(a,lo,hi); // 切分

    quick_sort(a,lo,j-1);  // 左半部分排序
    quick_sort(a,j+1,hi);  // 右半部分排序
}

template<class T> void SortMethod<T>::quick_sort
(T a[], int N)
{
    quick_sort(a,0,N-1);
}

////////////////////////////////////////////
string str = "QUICKSORTEXAMPLE";
char* data = (char*)str.c_str();

int N = str.length();
SortMethod<char>::print(data,N);
SortMethod<char>::quick_sort(data,N);
SortMethod<char>::print(data,N);
bool sorted = SortMethod<char>::isSortd(data,N);
cout<<"sorted: "<<sorted<<endl;

>>>
Q U I C K S O R T E X A M P L E
A C E E I K L M O P Q R S T U X
sorted: 1   

为了避免快速排序的最坏情况,一般进行数组打乱操作。排序过程如图:
排序过程

结论:将长度为N的无重复数组排序,快速排序平均需要~2NlnN次比较(以及1/6的交换)。

优化

  • 切换到插入排序
    在排序小数组时切换到插入排序,避免大量的递归调用,同时对于小数组,快速排序比插入排序慢;
  • 三取样切分
    使用子数组的一小部分元素的中位数来切分数组。代价是需要计算中位数。将采样大小设为3并用大小居中的元素切分的效果最好。
  • 熵最优的排序
    一个元素重复的子数组不需要继续排序,但算法还会继续将它切分为更小的数组。一个简单的想法是将数组切分为三部分,分别对应小于、等于和大于切分元素的数组元素。

三向切分的快速排序实现代码如下:

// 三向切分
template<class T> void SortMethod<T>::quick3_sort(T a[], int lo, int hi)
{
    if( hi <= lo ) return;
    int lt = lo, i = lo + 1, gt = hi;

    T temp = a[lo];
    while(i <= gt )
    {
        if( less(a[i],temp ) )
            exch(a,lt++,i++);
        else if( less( temp,a[i]))
            exch(a ,i, gt--);
        else
            i++;
    } // a[lo...lt-1] < temp = a[lt...gt] < a[gt+1...hi]

    quick3_sort(a, lo, lt-1);
    quick3_sort(a, gt+1, hi);

}

三向切分的轨迹如图:
这里写图片描述

猜你喜欢

转载自blog.csdn.net/liujiabin076/article/details/79781447
今日推荐