数据结构与算法——快排序

先上浙大陈姥姥的视频连接,我觉得看这个视频讲的很好。浙大数据结构与算法——快排序

原理部分还可以参考《大话数据结构》程杰【著】。

1. 算法实现

单看陈姥姥的视频,算法实现的伪码描述部分有一点点问题,照着写程序的话会出问题。

就是这里for(;;)循环,有点问题是,如果for(;;)跳出之后,此时i可能大于right-1,这样会造成错误,因此最外层的swap应该判断一下i和right-1的大小。代码如下:

快排序主函数,提供通用的外部接口。

//快排序主函数,提供通用的外部接口
void QSort(int* arr, int num){
    QuickSort(arr,0,num-1);
}

递归函数,快排序具体算法实现。

//递归函数,快排序具体算法实现
void QuickSort(int* arr,int left,int right){
    if(left>=right)return;
    int pivot=GeneratePivot(arr,left,right);
    int i=left,j=right-1;//由于GeneratePivot函数已经完成了首尾位置元素的有序,因此j从right-1开始
    for(;;){
        while(arr[++i]<pivot){}
        while(arr[--j]>pivot){}
        if(i<j)swap(&arr[i],&arr[j]);
        else break;
    }
    if(i<right){
        if(arr[i]==arr[right-1]){}//如果arr[i]==arr[right-1],两数相等,不交换
        else swap(&arr[i],&arr[right-1]);//此时主元pivot在整个序列中正确位置是i,因此交换arr[right-1]和arr[i]
    }
    //将arr从主元的位置(i)分成左右两部分,调用递归函数
    QuickSort(arr,left,i-1);
    QuickSort(arr,i+1,right);
}

生成pivot的函数。

//生成pivot的函数
int GeneratePivot(int* arr,int left,int right){
    int center=(left+right)/2;
    if(arr[left]>arr[center])swap(&arr[left],&arr[center]);
    if(arr[left]>arr[right])swap(&arr[left],&arr[right]);
    if(arr[center]>arr[right])swap(&arr[center],&arr[right]);
    //以上三次交换,将arr[left],arr[center],arr[right]三个位置变成升序有序的
    //即交换完成之后,arr[center]应该设为pivot
    swap(&arr[right-1],&arr[center]);//pivot大于等于自己,所以先放到最右侧right-1的位置
    return arr[right-1];//此时right-1存放的是pivot
}

一份包含创建随机数数组进行输入输出测试的完整程序如下:

//快排序算法(最基本)
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//宏定义
#define MAX 1000
#define MIN 0
#define ARRAYLEN 10
const int Cutoff=10;

//工具函数
int* generateArray(int num);        //随机数数组生成函数
void displayArray(int* arr,int num);//数组打印函数
void swap(int* a,int* b);           //交换两个元素函数

//快排序使用函数
void QSort(int* arr, int num);//主函数,提供通用的外部接口
void QuickSort(int* arr,int left,int right);//递归函数,快排序具体算法实现
int GeneratePivot(int* arr,int left,int right);//生成pivot的函数

int main(void){
    //生成随机的int数组
    int* array=generateArray(ARRAYLEN);
    //int array[ARRAYLEN]={1,1,2,2,2,3,3,7,1,4};
    printf("Raw data:\n");
    displayArray(array,ARRAYLEN);
    
    //归并排序
    QSort(array,ARRAYLEN);
    printf("Sorted data:\n");
    displayArray(array,ARRAYLEN);

    return 0;
}
//快排序主函数,提供通用的外部接口
void QSort(int* arr, int num){
    QuickSort(arr,0,num-1);
}
//递归函数,快排序具体算法实现
void QuickSort(int* arr,int left,int right){
    if(left>=right)return;
    int pivot=GeneratePivot(arr,left,right);
    int i=left,j=right-1;//由于GeneratePivot函数已经完成了首尾位置元素的有序,因此j从right-1开始
    for(;;){
        while(arr[++i]<pivot){}
        while(arr[--j]>pivot){}
        if(i<j)swap(&arr[i],&arr[j]);
        else break;
    }
    if(i<right){
        if(arr[i]==arr[right-1]){}//如果arr[i]==arr[right-1],两数相等,不交换
        else swap(&arr[i],&arr[right-1]);//此时主元pivot在整个序列中正确位置是i,因此交换arr[right-1]和arr[i]
    }
    //将arr从主元的位置(i)分成左右两部分,调用递归函数
    QuickSort(arr,left,i-1);
    QuickSort(arr,i+1,right);
}
//生成pivot的函数
int GeneratePivot(int* arr,int left,int right){
    int center=(left+right)/2;
    if(arr[left]>arr[center])swap(&arr[left],&arr[center]);
    if(arr[left]>arr[right])swap(&arr[left],&arr[right]);
    if(arr[center]>arr[right])swap(&arr[center],&arr[right]);
    //以上三次交换,将arr[left],arr[center],arr[right]三个位置变成升序有序的
    //即交换完成之后,arr[center]应该设为pivot
    swap(&arr[right-1],&arr[center]);//pivot大于等于自己,所以先放到最右侧right-1的位置
    return arr[right-1];//此时right-1存放的是pivot
}


//下面三个函数是工具函数,不是快排序的主体
//生成MAX和MIN范围内的包含num个元素的数组
int* generateArray(int num){
    srand((unsigned)time(NULL));
    int* arr=(int*)malloc(sizeof(int)*num);
    for(int i=0;i<num;i++)
        arr[i]=((MAX-MIN+1)*rand()/(RAND_MAX+1.0)+MIN);
    return arr;
}
//打印数组
void displayArray(int* arr,int num){
    for(int i=0;i<num;i++)
        printf("%d ",arr[i]);
    printf("\n");
}
//通过异或运算,交换两个整型数(注意:浮点数不能用异或进行交换)
void swap(int* a,int* b){
    if(a==b)return;
    *a=*a^*b;
    *b=*a^*b;
    *a=*a^*b;
    return;
}

2. 一点点优化

参考《大话数据结构》这本书,将QuickSort函数for循环中的交换进行了优化,不使用交换,而是替换,这样将swap操作变成了简单的赋值操作。同时循环外面的swap,先判断两个数是否相等,不相等的话再进行交换。

//递归函数,快排序具体算法实现
void QuickSort(int* arr,int left,int right){
    if(left>=right)return;
    int pivot=GeneratePivot(arr,left,right);
    int i=left,j=right-1;//由于GeneratePivot函数已经完成了首尾位置元素的有序,因此j从right-1开始
    while(i<j){
        while(arr[++i]<pivot&&i<j){}//循环结束时,arr[i]>pivot
        arr[j]=arr[i];         //此时将arr[i]挪到arr[j]的位置
        while(arr[--j]>pivot&&i<j){}//循环结束时,arr[j]<pivot,
        arr[i]=arr[j];   //此时将arr[j]挪到arr[i]的位置
    }
    if(arr[i]==pivot){}//如果arr[i]==arr[right-1],两数相等,不交换
    else swap(&arr[i],&pivot);//此时主元pivot在整个序列中正确位置是i,因此交换arr[right-1]和arr[i]
    //将arr从主元的位置(i)分成左右两部分,调用递归函数
    QuickSort(arr,left,i-1);
    QuickSort(arr,i+1,right);
}

3. 使用函数指针传入用户自定义的比较函数

受到C语言标准库中的快排序算法的接口的启发,觉得自己也可以做一个类似的接口,通过用户定义比较函数,来决定排序是按照升序还是降序。

相关函数的声明:

//快排序使用函数
void QSort(int* arr, int num,int (*p)(int*,int*));//主函数,提供通用的外部接口
void QuickSort(int* arr,int left,int right,int (*p)(int*,int*));//递归函数,快排序具体算法实现
int GeneratePivot(int* arr,int left,int right,int (*p)(int*,int*));//生成pivot的函数
void InsertSort(int* arr,int num,int (*p)(int*,int*));//简单插入排序,当待排序元素比较少的时候使用
//自定义的比较函数
int Ascend(int* a,int* b);  //升序排序时使用的比较函数
int Descend(int* a,int* b); //降序排序时使用的比较函数

函数的定义:

//快排序主函数,提供通用的外部接口
void QSort(int* arr, int num,int (*p)(int*,int*)){
    QuickSort(arr,0,num-1,p);
}
//递归函数,快排序具体算法实现,使用函数指针传递比较函数
void QuickSort(int* arr,int left,int right,int (*p)(int*,int*)){
    if(left>=right)return;
    if(Cutoff<=right-left){
        int pivot=GeneratePivot(arr,left,right,p);
        int i=left,j=right-1;//由于GeneratePivot函数已经完成了首尾位置元素的有序,因此j从right-1开始
        while(i<j){
            while(((*p)(&arr[++i],&pivot)<0)&&i<j){}//升序排序时:循环结束,arr[i]>pivot
            arr[j]=arr[i];         //此时将arr[i]挪到arr[j]的位置
            while(((*p)(&arr[--j],&pivot)>0)&&i<j){}//升序排序时:循环结束时,arr[j]<pivot,
            arr[i]=arr[j];   //此时将arr[j]挪到arr[i]的位置
        }
        if(arr[i]==pivot){}//如果arr[i]==arr[right-1],两数相等,不交换
        else swap(&arr[i],&pivot);//此时主元pivot在整个序列中正确位置是i,因此交换arr[right-1]和arr[i]
        //将arr从主元的位置(i)分成左右两部分,调用递归函数
        QuickSort(arr,left,i-1,p);
        QuickSort(arr,i+1,right,p);
    }else InsertSort(arr+left,right-left+1,p);//对于待排序元素较少的情况,使用简单插入排序
}
//生成pivot的函数
int GeneratePivot(int* arr,int left,int right,int (*p)(int*,int*)){
    int center=(left+right)/2;
    if((*p)(&arr[left],&arr[center])>0)swap(&arr[left],&arr[center]);
    if((*p)(&arr[left],&arr[right])>0)swap(&arr[left],&arr[right]);
    if((*p)(&arr[center],&arr[right])>0)swap(&arr[center],&arr[right]);
    //以上三次交换,将arr[left],arr[center],arr[right]三个位置变成有序的(升序还是降序取决于比较函数)
    //即交换完成之后,arr[center]应该设为pivot
    swap(&arr[right-1],&arr[center]);//pivot先放到最右侧right-1的位置(如果是升序就是靠近大端,降序靠近小端)
    return arr[right-1];//此时right-1存放的是pivot
}
void InsertSort(int* arr,int num,int (*p)(int*,int*)){
    int i,j;
    for(i=1;i<num;i++){//从位置1开始,到数组的最后
        int tmp=arr[i];//先保存i位置的元素
        for(j=i;j>0&&(*p)(&arr[j-1],&tmp)>0;j--)//根据p指向的比较函数,决定插入排序是升序还是降序
            arr[j]=arr[j-1];    
        arr[j]=tmp;//上面for循环结束后,arr[j]应该是tmp的合适位置
    }
}
//升序排序时使用的比较函数
int Ascend(int* a,int* b){
    return (*a)-(*b);
}
//降序排序时使用的比较函数
int Descend(int* a,int* b){
    return (*b)-(*a);
} 

注意由于快排序使用递归当待排序序列中元素比较少的时候,用简单的插入排序可以获得较好的性能。这里定义了一个cutoff变量(注意C语言const定义的是变量!只不过是只读变量而已(不能用const变量作为数组的长度)),当待排序序列中的元素小于cutoff的时候,调用插入排序。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值