初步认识qsort函数以及利用冒泡排序进行初步模拟

 

首先初步认识一下

qsort 是一个库函数,包含在头文件stdlib.h中

作用:基于快速排序算法实现的一个排序函数

初步认识qsort函数以及利用冒泡排序进行初步模拟_第1张图片

接下来认识这个函数所需要的参数有四个。接下来分别进行分析。

初步认识qsort函数以及利用冒泡排序进行初步模拟_第2张图片

首先我们发现数据起始位置的指针用的是void*来接受,为啥呢???

因为我们不知道我们将来比较的数据类型为什么,有可能是int*  或者char*  又或者是结构体指针,所以我们在这放置了一个void*来接受,void*可理解为通用类型指针,可能就有人问了,既然有这个万金油的指针类型,那我们还要其他指针类型干什么??下面给出了原因。

我们发现我们自己需要定义一个比较函数,参数类型为两个指针变量,返回类型为int  。

但是为什么这里的指针类型需要定义为void* 类型呢???

这里就需要我们明白指针类型的意义。

指针类型其实决定了,对指针解引用的时候有多大的权限(能操作几个字节)。

void*  可以接受任意类型的指针,但是确定也比较明显,就是在对指针解引用操作的时候,计算机不知道它有多大的权限(可以向后访问几个字节)。

由于我们并不知道我们所要比较的元素是哪种类型的,就采用空类型来接受。而在比较的时候,我们需要将其强制转化成比较的类型,方可进行比较。具体请看下面代码展示。

 对这个函数有一个初步了解之后,我们开始尝试使用这个函数。

int cmp_int(const void* e1, const void* e2)
{
	if (*(int*)e1 > *(int*)e2)
		return 1;
	else if (*(int*)e1 < *(int*)e2)
		return -1;
	else
		return 0;
	//return *(int*)e1 - *(int*)e2;
    //这种方式其实更好,利用前者减去后者,差返回就好。
    //这样刚好满足这个函数的定义
    //e1>e2 返回 大于0 <==>  e1-e2>0
    //e1  e1-e2<0
    //e1=e2 返回    0 <==>  e1-e2=0

}
void my_print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(arr+i));
	}
}

int main()
{
	int arr[] = { 10,9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	my_print(arr, sz);

	return 0;
}

在这个过程中,我们需要比较的是int类型的元素,故将额e1和e2强制类型转换成int* 。 

然后我们用qsort函数排序一下结构体。

int cmp_stu_by_age(const void* e1, const void* e2)
{
 	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
void test3()
{
	struct Stu arr[3] = { {"zhangsan", 20, 55.5},{"lisi", 30, 88.0},{"wangwu", 10, 90.0} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}
int main()
{
	test3();
	
	return 0;
}

经过排序后的结果如下图:初步认识qsort函数以及利用冒泡排序进行初步模拟_第3张图片

 我们发现年龄有原来的无序变为升序。

可以得出一个结论:利用qsort函数可以将一个无序的元素变为升序。那这里有人好奇了,那怎样才能变为降序呢???

其实我们只需要改变逻辑即可!原来我们创建了一个比较函数,其逻辑为

初步认识qsort函数以及利用冒泡排序进行初步模拟_第4张图片

 我们颠倒逻辑改为 初步认识qsort函数以及利用冒泡排序进行初步模拟_第5张图片

 这样原来是升序,逻辑转换一下,就变为将序了。

经过前面的解析,已经初步明白了qsort函数的使用,我们发现qsort可以排列各种类型的数据,

那我们可以使用冒泡排序进行一个模拟,使我们的冒泡排序也可以万能呢?

首先观察一下我们原有的冒泡排序

void bubble_sort(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		for (j = 0; j < sz-1-i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}
void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz =  sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	print_arr(arr,sz);
	return 0;
}

发现要使冒泡排序可以排序任何元素,要进行一些改动。

初步认识qsort函数以及利用冒泡排序进行初步模拟_第6张图片

 经过初步的分析,我们发现我们的冒泡排序还缺少某些参数。例如每个元素的宽度,如若知道这个整体的起始地址,再知道每个元素的宽度和元素的个数。

我们注意到,一旦冒泡排序的元素个数确定了,冒泡排序的趟数也就确定了。

其次,我们发现不同元素之间的比较方式不同,所以我们应该将比较方式提取出来,不同的数据类型采用不同的比较方式。所以就个人自行定义比较方式。

所以我们 的冒泡排序的整体大致为

void bubble_sort(void* base, int num, int width, int (*cmp)(const void*e1, const void*e2))

//base为数据的起始地址
//num为元素的个数
//width为每个元素的大小(字节)
//cpm为比较函数

由于我们无法提前得知比较类型的数据,故我们的比较函数的指针类型也应该为void*,以便存放各种类型的指针。

开始模拟

先写出整体的大致思路,然后进行修改。


//冒泡排序
void my_qsort(void* base, int num, int width, int(*com_int)(const void* e1, const void* e2))
{
	
	
		int i = 0;
		int j = 0;
		for (i = 0; i < num - 1; i++)
		{
			for (j = 0; j < num - 1 - i; j++)
			{
				/*
                 *if(>0)
                 *swap
                 *
                 *这里就是需要我们进行改变的。
                 *
                 */
			}
		}
	
}

int main()
{
	int arr[10] = { 10,9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	my_qsort(arr, sz, sizeof(arr[0]), com_int);
	//打印数组
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

我们思考如何应该如何让判断当前两个元素的大小???

先写出我们的一个比较函数

 首先观察我们原来的比较函数,如若前者大于后者,则进行交换。

初步认识qsort函数以及利用冒泡排序进行初步模拟_第7张图片

类比与这个,我们可以写出


//比较函数
int com_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

 初步认识qsort函数以及利用冒泡排序进行初步模拟_第8张图片

 如果cmp-int(,)大于0,则前面的数据大于后面的数据,则进行交换。

 如果cmp-int(,)小于或者等于0,则前面的数据小于后面的数据,则不进行交换。

 

if(cmp(,)>0)
{
    //交换;
}

我们发现这个比较函数需要那两个待比较元素的首地址,可是我们该如何拿到这两个元素的首地址呢????

我们需要计算出待比较元素的地址

如何计算呢??

我们以整形数组为例进行演示:

初步认识qsort函数以及利用冒泡排序进行初步模拟_第9张图片

 那么如何寻找相邻的待比较元素的地址呢??

初步认识qsort函数以及利用冒泡排序进行初步模拟_第10张图片

再看一下这张图,我们来分析一下。其实这个过程中,相邻元素的下表一个是就 j ,一个是 j+1 。

//arr[j] <==>(char*)base + width*j
//arr[j+1] <==>(char*)base + width*(j+1)

所以判断函数可以写成

if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				
				/*
                 *
                 * 此处是交换函数;
                 * 
                 */
			}

现在我们来编写交换函数swap

首先交换函数需要传输  待交换元素的地址  , 如果传输   待交换元素的值     ,无法达到交换的效果。但是需要注意的是我们传输过去的是一个元素的首地址(char*),但是我们不知道一个元素有几个字节(就是一个元素的宽度)?我们知道,交换元素意味着元素的整体都进行交换,而不能只交换部分。故我们也应该将宽度(大小)传过去。

if (com_int((char*)a + j * width, (char*)a + (j + 1) * width)>0)
				{
					swap((char*)a + j * width, (char*)a + (j + 1) * width, width);

				}

以整形举例,方便大家理解这个交换过程。

初步认识qsort函数以及利用冒泡排序进行初步模拟_第11张图片

由于 swap的参数类型为cahr* 类型,一次只能交换一个字节,故需要交换width(即元素的宽度或者大小)次 。故函数可以写为:

void swap(char* p1,char*p2,int width)
{
	for (int n = 0; n < width; n++)
	{
		char tmp = *(p1 + n);
		*(p1 + n) = *(p2 + n);
		*(p2 + n) = tmp;

	}
}

初步认识qsort函数以及利用冒泡排序进行初步模拟_第12张图片

 通过单个字节的交换,实现元素的交换。

最终实现了利用冒泡排序实现qsort万能排序的功能。整体代码在下

//比较函数
int com_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

void swap(char* p1,char*p2,int width)
{
	for (int n = 0; n < width; n++)
	{
		char tmp = *(p1 + n);
		*(p1 + n) = *(p2 + n);
		*(p2 + n) = tmp;

	}
}
void my_qsort(void* base, int num, int width, int(*com_int)(const void* e1, const void* e2))
{
	
	
		int i = 0;
		int j = 0;
		for (i = 0; i < num - 1; i++)
		{
			for (j = 0; j < num - 1 - i; j++)
			{
				
				if (com_int((char*)a + j * width, (char*)a + (j + 1) * width)>0)
				{
					swap((char*)a + j * width, (char*)a + (j + 1) * width, width);

				}
			}
		}
	
}
int main()
{
	int arr[10] = { 10,9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	my_qsort(arr, sz, sizeof(arr[0]), com_int);
	//打印数组
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

你可能感兴趣的:(c语言)