#引言
想必快速排序对于大家来说并不陌生,也许大家熟悉快速排序的使用方法但是对于快速排序的设计思想想必大多数人是不了解的,所以我们今天就来了解一下快速排序的设计思想。
#正文
#cmp函数的理解
对于快速排序其在C语言库函数中的调用方式如下:
int cmp(const void* e1, const void* e2){ return *(int*)e1 - *(int*)e2; } #include<stdio.h> int main(){ qsort(arr,len,sizeof(arr[0]),cmp); return 0; }
也许大多数人对于其中的cmp函数不太了解,其实快速排序是可以排序任意类型的数据的所以我们就需要用void*类型的指针来接收任意类型的数据(此时我们将让其排序整形),所以在之后将其转化为int(如果是排序其他类型则转换为其他类型),其中e1 - e2是为了比较两数的大小,此时为升序,那么e2 - e1则为降序)。
#设计思想的理解
我们可以看到,在快排函数的参数列表中包含了四个元素。其中包括数组首地址,数组长度,数组中元素类型的大小以及前面提到的比较函数cmp。对于排序我们需要数组地址,数组长度以及比较函数都可以理解,但是为什么还需要数组中元素的类型大小呢?这是因为快速排序是可以排序任意数据类型的,不同类型的字节长度不同所以我们需要通过其类型的大小来判断需要排序的类型同时在cmp中将未定义的void*指针转化为所排序类型的指针。
#函数参数类型的接收
对于前三个参数的接收想必大家应该是没有问题的,那么对于一个函数cmp我们是如何接收的呢?
答案是用函数指针接收。函数指针的表达方式也很简单,例如有一个函数int Add(int x ,int y),那么他多对应的函数指针则为int (*pAdd)(int,int)。同理,对于cmp函数其对应类型的指针为int (*pcmp)(const void*, const void*);(其中(*pAdd)与(*pcmp)其命名并不是一定要使用函数名这里只是为了便于理解)
void My_Qsort(void* base, int len, int width, int(*cmp)(const void*, const void*))
#具体使用冒泡排序模拟其内部实现方式
对于冒泡排序想必大家应该是很熟悉了,在此我就不在多说,冒泡排序的实现如下:
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
Swap(arr[j],arr[j+1]]);
}
}
}
如今我们需要使用冒泡排序来实现快速排序内部的设计思想,在冒泡排序中我们使用if语句中的条件来判定大小并进行交换排序。那么在模拟中我们依然使用这种方法来进行排序,具体函数设计如下:
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - 1 - i; j++) {
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0) {
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
这与冒泡排序并无多大区别,这里我们只是使用cmp函数判断相邻俩数的大小,重要的是如何通过其判断。在这里我们可以看到之前cmp中的void*指针被我们转化为了char*,这是因为快速排序可以排序多种类型导致的。因为cahr*指针只指向一个字节的空间,在这里我们就可以通过之前传的sizeof(arr[0])(在图示中为width)来判断其原本应该占据多少字节,这就好比在冒泡排序中比较相邻两个数的大小。同时可以看到Swap函数中也多了一个参数width,它的存在就是为了在不理解具体排序类型时能进行排序,Swap函数的具体实现如下:
void Swap(char* p1, char* p2, int width) {
int i = 0;
for (i = 0; i < width; i++) {
char tmp = *p1;
*p1 = *p2;
*p2 = tmp;
p1++;
p2++;
}
}
其中width表示元素类型的大小,比如int为4,double为8,而其中的交换则是将相邻两个元素的每一个字节从而达到交换元素的目的,最后即可完成排序。
#对于不同类型的排序
对于不同类型的排序也只需改变cmp函数中的具体实现,以下给出几个例子:
typedef struct Stu {
char name[20];
int age;
int score;
}Stu;
int Cmp_Int(const void* e1, const void* e2) {
return *(int*)e1 - *(int*)e2;
}
int Cmp_Float(const void* e1, const void* e2) {
if (*(float*)e1 > *(float*)e2)
return 1;
else if (*(float*)e1 < *(float*)e2)
return -1;
else
return 0;
}
int Cmp_Stu_Age(const void* e1, const void* e2) {
return ((Stu*)e1)->age - ((Stu*)e2)->age;
}
其中对于浮点数不能用直接相减的方法来判定大小,因为浮点数在内存中的存储是存在误差的,相减可能造成精度损失。