快速排序

1 快速排序法

Alt
通常使用第一个元素作为分界点;
1)i处的元素大于v,直接添加在大于v的后面;
2)i处的元素小于v,则将其和j后面一个位置的元素进行交换;
3)最后,将v和j后面一个位置的元素进行交换。

排序过程:
Alt当i处的元素<e时,交换j+1和i处的元素:
Alt
最后,将元素v和j+1处元素交换:
Alt

2 随机化快速排序

Alt如上图,这是快速排序的思路。与归并排序相比,归并排序则是直接从中间切割,一分为二;但是快速排序的分割点是我们选定的标定点v,前面我们选择第一个元素作为标定点v。

这样做存在一个问题?
如果我们面对的是一个高度有序的序列,那么就会出现下面这种情况:极端情况就是这个序列已经有序了,我们每次选取的标定点都是第一个,就会退化成下图这样。
Alt面对上面这种情况,我们可以考虑标定点v的选取,我们并不是每次都选择第一个元素作为标定点。而是从序列中随机选择一个点作为标定点,

请注意,这里我们只需要将随机选择的这个点和第一个元素进行交换,就达到了随机化标定点的意图。

partition操作:

int __partition(T arr[], int l, int r) {
// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
    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[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;
    if(r-l<=15){ //优化1,小部分数据直接使用插入排序
        insertionSort(arr,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){
    srand(time(NULL));
    __quickSort(arr,0,n-1);
}
3 双路快速排序

存在大量重复的元素,也会导致 O ( n 2 ) O(n^2) O(n2)

情况如下:
AltAlt参照前面的快速排序算法,我们可以知道,不管我们将等于v 的情况放在哪一侧,只要序列中存在大量重复元素,就会出现极度不平衡的现象。出现上图的情况。

新的思路:
使用双路快速排序,如下图,当存在等于v的情况,不需要进行位置的交换,只需要移动指针。
Alt
AltAlt我们注意上图中发生交换时包含了e=v的情况,i、j都包含了等于的情况,这样就会将e=v的数据分到两侧,而不是都归于一侧。如此,可以在一定程度上避免极度不平衡的情况发生。
Alt

// 双路快速排序的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){

    // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
    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, j = r;
    while( true ){
        // 注意这里的边界, arr[i] < v, 不能是arr[i] <= v
        // 思考一下为什么?
        while( i <= r && arr[i] < v )
            i ++;
        // 注意这里的边界, arr[j] > v, 不能是arr[j] >= v
        // 思考一下为什么?
        while( j >= l+1 && arr[j] > v )
            j --;
        if( i > j )
            break;
        swap( arr[i] , arr[j] );
        i ++;
        j --;
    }
    swap( arr[l] , arr[j]);
    return j;
}

4 三路快速排序

存在大量等于v的情况,这种方式就比较快。
AltAltAltAltAlt

// 递归的三路快速排序算法
template <typename T>
void __quickSort3Ways(T arr[], int l, int r){

    // 对于小规模数组, 使用插入排序进行优化
    if( r - l <= 15 ){
        insertionSort(arr,l,r);
        return;
    }

    //partition
    // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
    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+1...i) == v
    while( i < gt ){//终止条件
        if( arr[i] < v ){
            swap( arr[i], arr[lt+1]);
            i ++;
            lt ++;
        }
        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);
}
5 总结

归并排序和快速排序体现了分治算法的思想:将原问题分割成同等结构的子问题,之后将子问题逐一解决后,原问题也就得到了解决。

归并排序:体现在上,对于分割,归并排序是一刀切,一分为二,重点在治理数据上,使用了归并操作;
快速排序:体现在上,对于分割,需要设置一个标定点,对于标定点的选择要仔细考虑,而在治理数据上,快速排序则比较简单,主要就是分情况讨论一下就可以。

求解的典型问题:
AltAltAlt

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值