快速排序及其优化和快速选择算法

快速排序及其优化和快速选择算法

本文主要内容是快速排序的代码编写及其优化,还有快速选择算法(在无序的数中寻找第k大或小的元素)

上代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <stack>
#include <algorithm>
#include <string>
#include <map>
#include <set>
#include <vector>
using namespace std;

#define MAX_N 10000


// arr    :用来存储待排序的元素
// n      :代表元素数量
// output :输出过程中的调试信息
// DEBUG = 1 开启调试信息
// DEBUG = 0 关闭调试信息

int arr[MAX_N + 5];
int n;
#define DEBUG 1
void output(int,int,int);

// 快速排序: 对arr中 l 到 r 位进行排序
// arr: 待排序数组
// l  : 待排序区间起始坐标
// r  : 待排序区间结束坐标
void quick_sort(int *arr,int l,int r){
    //递归结束条件
    if(l >= r) return;
    
    int x = l, y = r, pivot = arr[l];
    while(x < y){
        //从后向前扫描,找到比基准值小的值,移动到前面
        while(x < y && arr[y] >= pivot) y--;
        if(x < y) arr[x++] = arr[y];
        //从前向后扫描,找到比基准值大的值,移动到后面
        while(x < y && arr[x] <= pivot) x++;
        if(x < y) arr[y--] = arr[x];
    }

    //将基准值pivot放到数组x位
    arr[x] = pivot;
    output(l,x,r);
    quick_sort(arr,l,x-1);
    quick_sort(arr,x+1,r);
    return;
}

void output(int l,int x,int r){
    if(!DEBUG)    return;
    printf("\n待排序区间范围 [%d, %d]\n",l,r);
    printf("基准值: %d\n", arr[x]);

    char str[30];
    int cnt = 1;
    for(int i = 1; i < x; i++){
        cnt += sprintf(str,"%d ",arr[i]);
    }

    for(int i = 1; i < l; i++) printf("%d ",arr[i]);
    printf("[");
    for(int i = l; i <= r; i++){
        printf("%d ",arr[i]);
    }
    printf("]");
    
    for(int i = r + 1; i <= n; i++) printf("%d ",arr[i]);
    printf("\n");
    
    for(int i = 0; i < cnt; i++) printf(" ");
    printf("^\n");
    for(int i = 0; i < cnt; i++) printf(" ");
    printf("|\n");
    printf("\n");
    return;
}

void read_data(){
    printf("请输入元素数量: ");
    scanf("%d",&n);
    printf("请输入%d个整数: \n",n);
    for(int i = 1; i <= n; i++){
        scanf("%d",arr + i);
    }

    while(getchar() != '\n');
    return;
}

int main(){
    read_data();
    quick_sort(arr,1,n);
    for(int i = 1; i <= n; i++){
        printf("%d ",arr[i]);
    }

    printf("\n");
    return 0;
}



下面再来看下对快速排序的优化。

主要分三点优化:

1)单边递归优化--本层函数调用中完成本层的partition后,依旧在本层对左半边进行partition,对右半边进行递归调用,这样一来,函数调用的次数会减半,大大降低了算法的时间复杂度.

2)基准值选择优化--三点取中,对排序区间最左边的值、中间值、最右边的值进行取中值作为基准值,这样会使得算法的时间复杂度稳定在O(nlogn)

3)对partition操作进行优化,利用两个指针分别从前向后、从后向前,交换比基准值小和比基准值大的元素,直到两指针相遇.

看代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <stack>
#include <algorithm>
#include <string>
#include <map>
#include <set>
#include <vector>
#include <ctime>
using namespace std;

#define TIME(func) ({ \
    int b = clock(); \
    func; \
    int e = clock(); \
    (e - b) * 1000.0 / CLOCKS_PER_SEC; \
})

void quick_sort_old(int *arr, int l, int r) {
    if (l >= r) return ;
    int x = l, y = r, z = arr[l]; 
    while (x < y) {
        while (x < y && arr[y] >= z) --y;
        if (x < y) arr[x++] = arr[y];
        while (x < y && arr[x] <= z) ++x;
        if (x < y) arr[y--] = arr[x];
    }
    arr[x] = z;
    quick_sort_old(arr, l, x - 1);
    quick_sort_old(arr, x + 1, r);
    return ;
}

int select_value(int a, int b, int c){
    if(a > b) swap(a,b);
    if(a > c) swap(a,c);
    if(b > c) swap(b,c);
    return b;
}

void quick_sort_new(int *arr, int l, int r){
    while(l < r){

        //三点取中
        int x = l, y = r, z = select_value(arr[l],arr[r],arr[(l + r) >> 1]);
        
        //对partition的优化
        do{
            while(arr[x] < z) x++;
            while(arr[y] > z) y--;
            if(x <= y){
                swap(arr[x],arr[y]);
                x++, y--;
            }
        }while(x <= y);
        quick_sort_new(arr,x,r);
        
        //单边递归优化
        r = y;
    }

    return;
}


#define MAX_N     1000000
int a1[MAX_N + 5], a2[MAX_N + 5];

void test_one(int t){
    for(int i = 0; i < MAX_N; i++){
        a1[i] = rand();
        a2[i] = a1[i];
    }

    int t1 = TIME(quick_sort_old(a1,0,MAX_N - 1));
    int t2 = TIME(quick_sort_new(a2,0,MAX_N - 1));
    printf("第%d轮测试,quick_sort_old(%dms), quick_sort_new(%dms)\n", t, t1, t2);
    return;
}

void test_random(int n){
    printf("测试次数: %d\n每轮测试数据量:%d\n",n,MAX_N);
    for(int i = 1; i <= n; i++){
        test_one(i);
    }
    return;
}


int main(){
    srand(time(0));
    test_random(10);
    return 0;
}









看完上述代码,相信你对快速排序算法有了一定的认知,如果我问你,如何在无序的数中选择第k大的元素呢,我们的第一反应可能是对所有元素排序,然后取第k大的元素,仔细想想,有必要吗?

其实,在我们完成一次partition操作之后,我们就可以知道基准值的排名, 假设为ind,我们可以比较ind和k的大小,如果ind和k相等,那么基准值就是第k大的值,如果ind比k大,那么我们只需在左半边递归查找第k大的值即可,如果ind比k小,那么我们只需在右半边递归查找第k-ind大的值即可,因为已经可以排除左半边的元素,而左半边的元素有ind个。

这样说你理解了吗?接下来看代码如何实现.

//arr :待查找数组
//l--r:待查找区间
//k   :待查找元素的排名
//在arr数组的l到r区间内,查找排名为k的元素
int quick_select(){
    int x = l, y = r, z = arr[l];
    while(x < y){
        while(x < y && arr[y] >= z) --y;
        if(x < y) arr[x++] = arr[y];
        
        while(x < y && arr[x] <= z) ++x;
        if(x < y) arr[y--] = arr[x];
    }

    arr[x] = z;
    
    //ind为当前基准值的排名
    int ind = x - l + 1;
    if(ind == k) return arr[x];
    if(ind > k) return quick_select(arr,l,x-1,k);
    return quick_select(arr,x+1,r,k-ind);
}

好了,今天的文章就介绍到这里了。有不对的地方欢迎指正。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云镛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值