数据结构与算法C++之快速排序(续)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/majinlei121/article/details/83901350

上一篇博客数据结构与算法C++之快速排序介绍了快速排序算法。
但是上面实现的快速排序有两个缺点:
(一)对于近乎有序的数组,算法的计算复杂度由O(nlogn)退化到O(n2)
(二)如果数组中存在大量重复的元素,那么算法的计算复杂度也会退化到O(n2)

(一)对于近乎有序的数组,算法的计算复杂度由O(nlogn)退化到O(n2)

使用上篇博客里的快速排序算法做测试,测试程序为

int main()
{
    int n = 50000;
    //int *arr = generateRandomArray(n, 0, n);
    int *arr = generateNearlyOrderedArray(n, 100);//生成只有200个无序元素的数组
    int *arr2 = copyIntArray(arr, n);
    testSorting("MergeSorting", MergeSorting, arr, n);
    testSorting("quickSorting", quickSorting, arr2, n);
    delete[] arr;//最后删除数组开辟的空间
    delete[] arr2;
    return 0;
}

测试结果
在这里插入图片描述
可以看出对于一个近乎有序的5万个元素的数组,快速排序用了 0.545s,比归并排序慢了很多倍快速排序算法中,我们一般将数组最左边的元素作为参考元素,这样的话,当数组近乎有序的时候,对整个数组进行partition后,左边小于参考元素的个数会很少,近乎没有,右边大于参考元素的个数将会接近整个数组元素个数,在递归过程中,每层的partition都将近乎只有一部分,如下图所示
在这里插入图片描述
解决方案很简单,就是随机选取参考元素,具体实现如下

#include <iostream>

#ifndef _SORTINGHELP_H_
#define _SORTINGHELP_H_
#include "SortingHelp.h"
#endif // _SORTINGHELP_H_

#include "MergeSorting.h"

using namespace std;

//对arr[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){

    //随机找一个元素与最左边元素进行交换位置
    swap(arr[l], arr[rand()%(r-l+1)+l]);
    T v = arr[l];
    int j = l;
    //arr[l+1...j] < v; arr[j+1...i) > v
    for(int i = l + 1; i <= r; i++){
        if (arr[i] < v){
            swap(arr[i], arr[j + 1]);
            j++;
        }
    }
    swap(arr[l], arr[j]);
    return j;
}

//对arr[l...r]部分进行排序
template<typename T>
void __quickSorting(T arr[], int l, int r){
    if (l >= r)
        return;

    srand(time(NULL));
    int p = __partition(arr, l, r);
    __quickSorting(arr, l, p - 1);
    __quickSorting(arr, p + 1, r);

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

    __quickSorting(arr, 0, n-1);

}
int main()
{
    int n = 50000;
    //int *arr = generateRandomArray(n, 0, n);
    int *arr = generateNearlyOrderedArray(n, 100);//生成只有200个无序元素的数组
    int *arr2 = copyIntArray(arr, n);
    testSorting("MergeSorting", MergeSorting, arr, n);
    testSorting("quickSorting", quickSorting, arr2, n);
    delete[] arr;//最后删除数组开辟的空间
    delete[] arr2;
    return 0;
}

输出为
在这里插入图片描述
随机设定参考元素后,对于近乎有序的数组,快速排序算法的速度恢复正常了

(二)如果数组中存在大量重复的元素,那么算法的计算复杂度也会退化到O(n2)

首先测试一下当数组中有大量重复元素时,算法的运行时间

int main()
{
    int n = 50000;
    //生成5万个只有0-10的数组,这样肯定会有很多重复的元素
    int *arr = generateRandomArray(n, 0, 10);
    int *arr2 = copyIntArray(arr, n);
    testSorting("MergeSorting", MergeSorting, arr, n);
    testSorting("quickSorting", quickSorting, arr2, n);
    delete[] arr;//最后删除数组开辟的空间
    delete[] arr2;
    return 0;
}

输出为
在这里插入图片描述
可以看出使用上面随机设定参考元素的方法,计算时间还是很长
由下图可以看出,当存在大量重复元素时,还是会出现partition后左右不平衡的现象
在这里插入图片描述
在上篇博客的算法中遍历数组元素时是从左到右依次遍历,遇到大于参考元素的值不操作,遇到小于参考元素的值将其与第一个大于参考元素的值相交换,下面换一种策略
在这里插入图片描述
如上图所示,同时从数组两边进行遍历,首先还是随机设定一个参考元素 v v ,然后与最左边元素进行位置交换
(1)索引为 i i 的元素从左向右遍历,如果遍历的元素小于 v v ,那么就继续遍历,如果大于等于 v v ,那么就停止
(2)此时索引为 j j 的元素从右向左遍历,如果遍历的元素大于 v v ,那么就继续遍历,如果小于等于 v v ,那么就停止
(3)将索引为 i i 的元素与索引为 j j 的元素交换位置,此时左右两边又都符合条件了, i i j j 就继续开始遍历
这样的话,即使有大量重复的元素,也会不断交换分散到左右两边( i i j j 交换时有三种可能,一、 i i j j 都等于 v v ;二、 i i 等于 v v j j 不等于 v v ;三、 i i 不等于 v v j j 等于 v v ),不会出现左右不平衡的现象

上述改进原理也可查看博客

实现程序如下

#include <iostream>

#ifndef _SORTINGHELP_H_
#define _SORTINGHELP_H_
#include "SortingHelp.h"
#endif // _SORTINGHELP_H_

#include "MergeSorting.h"

using namespace std;

//对arr[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){

    //随机找一个元素与最左边元素进行交换位置
    swap(arr[l], arr[rand()%(r-l+1)+l]);
    T v = arr[l];
    int j = l;
    //arr[l+1...j] < v; arr[j+1...i) > v
    for(int i = l + 1; i <= r; i++){
        if (arr[i] < v){
            swap(arr[i], arr[j + 1]);
            j++;
        }
    }
    swap(arr[l], arr[j]);
    return j;
}

//对arr[l...r]部分进行排序
template<typename T>
void __quickSorting(T arr[], int l, int r){
    if (l >= r)
        return;

    srand(time(NULL));
    int p = __partition(arr, l, r);
    __quickSorting(arr, l, p - 1);
    __quickSorting(arr, p + 1, r);

}


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

    __quickSorting(arr, 0, n-1);

}

////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
//下面是改进后的快速排序算法

//对arr[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){

    //随机找一个元素与最左边元素进行交换位置
    swap(arr[l], arr[rand()%(r-l+1)+l]);
    T v = arr[l];

    //arr[l+1...i)<=v; arr(j...r]>=v
    int i = l + 1;
    int j = r;
    while(true){
        while(arr[i] < v && i <= r) i++;
        while(arr[j] > v && j >= l + 1) j--;
        if (i > j) break;
        swap(arr[i], arr[j]);
        i ++;
        j --;
    }
    swap(arr[l], arr[j]);
    return j;
}

//对arr[l...r]部分进行排序
template<typename T>
void __quickSorting2(T arr[], int l, int r){
    if (l >= r)
        return;

    srand(time(NULL));
    int p = __partition2(arr, l, r);
    __quickSorting2(arr, l, p - 1);
    __quickSorting2(arr, p + 1, r);

}

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

    __quickSorting2(arr, 0, n-1);

}

int main()
{
    int n = 50000;
    int *arr = generateRandomArray(n, 0, 10);
    //int *arr = generateNearlyOrderedArray(n, 100);//生成只有200个无序元素的数组
    int *arr2 = copyIntArray(arr, n);
    int *arr3 = copyIntArray(arr, n);
    testSorting("MergeSorting", MergeSorting, arr, n);
    testSorting("quickSorting", quickSorting, arr2, n);
    testSorting("quickSorting2", quickSorting2, arr3, n);
    delete[] arr;//最后删除数组开辟的空间
    delete[] arr2;
    delete[] arr3;
    return 0;
}

输出为
在这里插入图片描述
可以看出使用左右两个方向都遍历,已经可以对有大量重复元素的数组进行快速排序

猜你喜欢

转载自blog.csdn.net/majinlei121/article/details/83901350