排序算法学习——快速排序

前言

上一篇我们已经实现了一个O(NlogN)级别的归并排序,这一篇的话,我们就来实现另一个O(NlogN)级别的排序算法,鼎鼎大名的快速排序

快速排序

原理

我们先在数组中找到一个数,然后以这个数为基准,分成两部分,一部分小于这个数,另一部分大于这个数。完成之后,这个数就位于了正确的位置,接下来采用递归的方法对左右两边的两个数组继续进行快速排序。
快排演示

代码实现

代码的图片解释:
图片解释

/**
对arrp[L...R]部分进行partition操作
返回整型p,使得arr[L...p-1] < arr[p] && arr[p+1...R] > arr[p]
*/
template <typename T>
int __partition(T arr[], int L, int R)
{

    //取第一个元素为标准
    T v = arr[L];

    //arr[L+1...j] < v && arr[j+1...i) > v
    int j = L;
    for(int i = L + 1; i <= R; i++)
    {
        if(arr[i] < v)
        {
            swap(arr[j+1], arr[i]);
            j++;
        }
    }

    //最后将标准元素放置到中间
    swap(arr[L], arr[j]);

    return j;
}

/**
对arr[L...R]部分进行快速排序
*/
template <typename T>
void __quickSort(T arr[], int L, int R)
{

    //跳出条件
    if(L >= R)
        return;

    int p = __partition(arr, L, R);
    __quickSort(arr, L, p-1);
    __quickSort(arr, p+1, R);
}

template <typename T>
void quickSort(T arr[], int n)
{
    __quickSort(arr, 0, n-1);
}

优化

优化1.0

我们可以看到我们是以第一个数字作为基准的,这样带来的坏处是:当数组几乎有有序的情况下,快速排序的时间复杂度将会下降到O(n^2)。
其实快速排序归并排序有一点是相似的,他们都是使用递归将过程不断地分层,不同点在于归并排序是很稳定地将数组进行二分,所以最后的层数应该是:

log2N

快速排序的切割方法并不稳定,所以导致了 近乎有序的情况下效率较低。
有什么办法解决这个问题呢?答案当然是有的。如果我们将参考的数字从第一个改成完全随机的,那么是不是就解决有序的情况下效率低的问题,当然你说随机之后还是有可能,这是当然的,但是完全随机之后在有大量数据的情况下是几乎不可能有序的。

优化1.0修改代码

首先设定随机种子,修改quickSort函数:

template <typename T>
void quickSort(T arr[], int n)
{

    //改进1.0,随机选择标准而不是第一个,解决近乎有序情况下的问题
    srand(time(NULL));

    __quickSort(arr, 0, n-1);
}

接着修改随机取的数字,只需要添加一行代码,随机取一个数和第一个数字进行交换:

/**
对arrp[L...R]部分进行partition操作
返回整型p,使得arr[L...p-1] < arr[p] && arr[p+1...R] > arr[p]
*/
template <typename T>
int __partition(T arr[], int L, int R)
{

    //改进1.0,随机选择一个数
    swap(arr[L], arr[rand()%(R-L+1) + L]);

    //取第一个元素为标准
    T v = arr[L];

    //arr[L+1...j] < v && arr[j+1...i) > v
    int j = L;
    for(int i = L + 1; i <= R; i++)
    {
        if(arr[i] < v)
        {
            swap(arr[j+1], arr[i]);
            j++;
        }
    }

    //最后将标准元素放置到中间
    swap(arr[L], arr[j]);

    return j;
}

优化2.0

递归的跳出条件同样可以做一些修改,比如在数组较小的时候可以进行插入排序。这里我就不重复解释了,有兴趣可以看一下我之前归并排序是如何修改的。排序算法学习——归并排序

优化3.0

刚刚解决了近乎有序的情况,但是还有一种情况,那就是当存在大量相同大小的数据时,会导致我们的排序层数不平衡,我们的算法就又会退化到O(N^2)级别的算法。
为了解决这个问题我们提出一种新的partition方式,从数组的两端同时开始扫描,左边扫描到大于v停止,右边扫描到小于v停止,两者进行一次交换,然后继续。因为我们在相等时仍然选择了交换,因此不会产生一端过长的情况。
原理演示如图:
3.0-1
3.0-2
代码修改如下:

/**
对arrp[L...R]部分进行partition操作
返回整型p,使得arr[L...p-1] < arr[p] && arr[p+1...R] > arr[p]
*/
template <typename T>
int __partition2(T arr[], int L, int R)
{

    //改进1.0,随机选择一个数
    swap(arr[L], arr[rand()%(R-L+1) + L]);

    //取第一个元素为标准
    T v = arr[L];

    //arr[L+1...i) < v && arr(j...r] > v i,j初始值保证一开始两个集合都不存在
    int i = L + 1, j = R;

    while(true)
    {
        //扫描
        while(i <= R && arr[i] < v) i++;
        while(j >= L + 1 && arr[j] > v) j--;
        //跳出条件
        if(i > j) break;
        //交换
        swap(arr[i], arr[j]);
        i++;
        j--;
    }

    //最后将标准元素放置到中间
    //最后完成j位于<=v的最后一个,i位于>=v的第一个
    swap(arr[L], arr[j]);

    return j;
}

/**
对arr[L...R]部分进行快速排序
*/
template <typename T>
void __quickSort2(T arr[], int L, int R)
{

    //跳出条件,可以改进
    if(L >= R)
        return;

    int p = __partition2(arr, L, R);
    __quickSort2(arr, L, p-1);
    __quickSort2(arr, p+1, R);
}

template <typename T>
void quickSort2(T arr[], int n)
{

    //改进1.0,随机选择标准而不是第一个,解决近乎有序情况下的问题
    srand(time(NULL));

    __quickSort2(arr, 0, n-1);
}

三路快排

原理

其实这可以说是优化4.0,因为第二种快速排序方法优化3.0已经很完善了。但是三路快排还是值得单独拿出来讲一下的,因为它在处理具有大量相同大小的数据时更加迅速。同时,比如一些语言(比如Java)的内置排序算法实现也是三路快排。
三路快排,哪三路比较快??当然是上路(小于v),中路(等于v),下路(大于v)一起才比较快啦。
我们最初的快速排序其实讲道理是两条路,一条小于v,另一条其实是大于等于 v,或者等号的位置换一下。这样的问题在于我们经常会重复处理相同大小的数据,这样降低了整个算法的效率。三路快排将等于v的数据单独拿出来,这样我们只要处理大于和小于v这两种数据了。
这就是中间状态,索引i不断向后推进,同时维护三个索引lt、gt、i
中间状态
最后状态,进行交换之后完成排序:
最后状态

代码实现

/**
三路快排处理arr[L...R]
将arr[L...R]分为<v; ==v; >v三部分
之后递归对<v ; >v两部分继续进行三路快速排序
*/
template <typename T>
void __quickSort3Ways(T arr[], int L, int R) {

    //跳出条件
    if(R - L <= 15) {
        insertionSort(arr, L, R);
        return;
    }

    //partition
    //随机取标准
    swap(arr[L], arr[rand()%(R-L+1) + L]);
    T v = arr[L];

    int lt = L; //arr[L+1...lt] < v
    int gt = R + 1; //arr[gt...R] > v
    int i = L + 1; // arr[lt...i) == v

    while(i < gt) {
        if(arr[i] < v) {
            swap(arr[i], arr[lt+1]);
            lt++;
            i++;
        }
        else if(arr[i] > v) {
            swap(arr[i], arr[gt-1]);
            gt--;
        }
        else{
            //arr[i] == v
            i++;
        }
    }

    swap(arr[L], arr[lt]);

    __quickSort3Ways(arr, L, lt-1);
    __quickSort3Ways(arr, gt, R);
}

template <typename T>
void quickSort3Ways(T arr[], int n) {

    srand(time(NULL));
    __quickSort3Ways(arr, 0, n-1);
}

当然优化是无止境,这里就先介绍到这啦!!欢迎交流。


图片引用百度图片
代码实现参照liuyubobobo慕课网教程

猜你喜欢

转载自blog.csdn.net/blueblueskyz/article/details/79374764
今日推荐