万能排序qsort(快排)or万能冒泡

目录

 

前言:

Qsort函数

"万能"冒泡排序

总结

 

前言:

在c语言中排序的方式有很多种,哪有没有一种排序可以应对绝大部分情况的排序呢,在c语言中的内置函数qsort就是一种"万能排序",那么借用他的逻辑还可以衍生出万能"冒泡排序".

 

Qsort函数:  头文件为

void qsort(void *base, int nelem, unsigned int width, int (* pfCompare)(const void *,const void *))

qsort函数的底层逻辑是快排,所以时间复杂度与快排相同。上面为qsort的使用方式,我会为大家用小"栗子"来讲述如何使用和操作.

 void qsort :显然qsort函数的返回值是void 

 void *base:是将要进行排序数组

 int nelem :将排序的数组长度传过去

 unsigned int width :待排序的数组每个元素的大小

 int (* pfCompare)(const void *,const void *): 往简单的说就是使用者自己提供的比较函数

 

#include
int main()
{
	int arr[] = { 2,6,1,4,7,9,12,8,3 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cap_int);
	return 0;
}

​

 这里讲一下 sz ,sizeof(arr)是整个数组的大小,那么sizeof(arr[0])就是数组元素中第一个元素的大小,那么 sz 其实就代表了 数组 的宽度.

那么这个时候cap_int怎么写呢(这个需要用户自己提供):如下

int cap_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

①-->为什么cap_int 接收时候需要const void *而不能使用其他的来接收呢?

这是因为我们void好像一个垃圾桶,他什么类型的数据他都接收保存,这样就大大的增强了代码的通用性质.

②-->在return 时将void*强制转化成int*

③-->cap_int 形参接收的到底是什么的值呢?

形参中接收的是数组arr中带比较的两个数,譬如e1是arr[0]的地址,e2是arr[1]的地址,而强制转化的作用就是让接受到的两个数据可以相减.

④-->这么设置是qsort的升序,而降序只需要更改成 return *(int*)e1-*(int*)e2 ;

 

上面讲述的是使用qsort给整形数组排序,那么接下来我们来给结构体排排;

struct stu
{
	char name[20];
	int year; 
};
int cam_stu_by_year(const void* e1, const void* e2)
{
	return ((struct stu*)e1)->year - ((struct stu*)e2)->year;
}
void text1()
{
	struct stu arr[3] = { { "张三",8 },{"李四",6},{"王五",1} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cam_stu_by_year);
}

这里的sizeof就有奇效了!

道理就和上面的一模一样!

利用这种思想我们来试试万能冒泡!

 

"万能"冒泡排序

底层逻辑还是冒泡排序,所以时间复杂度还是O(n^2),但是它稳定(doge)

先来写一个冒泡:

int main()
{
	int i,j;
	int arr[] = { 9,7,5,4,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz - 1; i++)
	{
		for (j = 0; j < sz - i - 1; j++)
		{
			if (arr[j] > arr[j+1])
			{
				int tmp = arr[i];
				arr[i] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
	return 0;
}

冒泡的外层for循环决定趟数,内存for循环来比较数组中两个数的值,但这种冒泡排序并不能比较非整形数据.

那么我们就要创造一个万能的冒泡排序,也是因此bubble_sort的形参就很有考究了

int cap_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
void bubble_sort(void* base, int num, int width, int* (cmp)(const void* e1, const void* e2))
{
	int i, j;
	for (i = 0; i < num-1; i++)
	{
		for (j = 0; j < num - i - 1; j++)
		{
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
			}
		}
	}
}

void text4()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cap_int);
	int i;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}

}

还是老规矩,*base待排序数组num是整个数组大小,width是每个数据的大小(宽度)

*(cmp)就是函数指针了,他是由我们用户来自定义的也是用来判断传参数据的大小,他的作用和cap_int 一样,就是换了个名字使代码易读。

让我们理解一下这个代码 : cmp((char*)base + j * width, (char*)base + (j + 1) * width)

cmp就是函数cap_int,那么函数cap_int的作用就是比较两个参数的大小.

 

Q1:那么为什么要把参数都强制转化成char(*)?

char(*)在内存中占用一个字节是int*是四个字节

char*是精度最高的,如果你强制转化成int(*),在本题中是没有问题,但是如果比较7个字节为width的数据,那么int(*)的准确度显然不够高

 

Q2:(char*)base + j * width这段话怎么解释?

如果j=0 那么(char*)base + j * width就等于(char*)base,指向数组中第一个元素

如果j=1 那么(char*)base + j * width就指向了数组中第二个元素了

 

最后一步就是实现Swap函数了,先上代码:

void Swap(char* buf1, char* buf2,int width)
{
	int i;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

 这时候就突出强调了转化成char(*)的重要性了,如果是int(*)那么++将会跳过四个字节

swap函数在内存中是这么操作:

把arr[1](值为9)和arr[2](值为8)交换

09 00 00 00 和 08 00 00 00交换时就是将09和08交换,再继续将后面的数组交换

02 03 04 05 和 06 07 08 09交换就是将02和06、03和07、04和08、05和09交换

从而达到交换值的效果

 

总结

qsort和万能冒泡的思想都是一样的,多看看我相信一定能看懂,对于对指针有一定基础的人理解起来应该是更容易的,void*是一个垃圾桶他可以接受任意类型数据的指针,这也是能实现"万能"的原因

 

 

 

 

你可能感兴趣的:(心得,算法,c语言,c++,数据结构)