先上浙大陈姥姥的视频连接,我觉得看这个视频讲的很好。浙大数据结构与算法——快排序
原理部分还可以参考《大话数据结构》程杰【著】。
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的时候,调用插入排序。