探讨冒泡和qsort及冒泡模拟实现qsort(含源码,讲解详细)

目录

1.普通冒泡

1.1总结冒泡 

2.qsort函数

2.2qsort总结 

3.利用冒泡模仿实现qsort函数


      本次运用的知识点(分支循环,数组,指针,结构体,自定义函数,时间复杂度,空间复杂度)

        提升代码能力最好的方法就是看一些源码,尝试理解并模仿源码。其目的是向大牛的思路靠近。也能加深我们对于语言的理解。提高代码的能力。

1.普通冒泡

        话不多说搞起。带着问题找答案,什么的冒泡?冒泡是用来干什么的。冒泡的本质是什么?

冒泡的时间复杂度和空间复杂度是多少。

        冒泡就是和它相邻的元素进行比较,如果左边大于右边,那就进行交换,它是用来排序的。我们这个代码的以数组为例子。在冒泡里面我们定义了一个 int tep变量,用来实现两个变量的交换。这导致了一个限制,那就是我们只能对 int 类型的变量进行排序。下面我们来看一下冒泡排序是如果实现的。两两元素进行比较,不满足交换,满足找下一对。

int arr[] = { 9,8,7,6,5};

第一趟(最大数9冒泡到末尾)

  • 比较 9 和 8,交换(8,9,7,6,5)
  • 比较 9和 7,交换(8,7,9,6,5)
  • 比较 9 和 6,交换(8,7,6,9,5))
  • 比较 9 和 5,交换(8,7,6,5,9)

第二趟(次大数8冒泡到倒数第二个位置)

  • 比较 8 和 7,交换(7,8,6,5,9)
  • 比较 8 和 6,交换(7,6,8,5,9)
  • 比较 8 和 5,交换(7,6,5,8,9)
  • 比较 8 和 9,不交换(7,6,5,8,9)

第三趟 ........ 这样一趟一趟的进行交换,最终排序完成 {5,6,7,8,9}

        我们发现这时候就需要两个循环一个是趟的循环,一个是交换的循环。我们虽然有五个元素但当我们交换到第四趟的时候 6 就不用和其他进行比较了。因为它是最小的元素,并且在最前面了。

#include <stdio.h>

bubble(int arr[], int sz)//普通版本的冒泡
{
	int i = 0;
	for (i = 0; i < sz; i++)//一趟冒泡
	{
		int j = 0;
		for (j = 1; j < sz - 1 - i; j++)
		{
			int tep = 0;
			if (arr[j - 1] > arr[j])
			{
				tep = arr[j - 1];
				arr[j - 1] = arr[j];
				arr[j] = tep;
			}
		}
	}
}

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

int main()
{
	int arr[] = { 4,2,63,23,65,-23,43,60 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble(arr, sz);
	printf_arr(arr, sz);
	return 0;
}

1.1总结冒泡 

        总结: 冒泡是用来排序数组的,但它对于排序的类型有限制。此外它的时间复杂度比较高  (最好:O(N) 最坏:O(N^2)空间复杂度:O(1)由于时间复杂度较高并不适用,但冒泡较为简单可以帮助初学者对于C语言的逻辑有个很好的了解。

 

2.qsort函数

        我们知道了什么是冒泡,那么下面就要了解一下什么是qsort函数。qsort的限制是什么。它该如何使用。当自身提供的比较函数返回值为多少进行交换?时间复杂度和空间复杂度是多少?

        qsort函数,它是排列的库函数 (默认是升序)。它在排序的时候会要求你提供一个比较函数。通过这个比较函数它就可以对你提供的数组进行排序

        void qsort (void* base, size_t num, size_t size,
            int (*compar)(const void*,const void*));

        void qsort(void* base,  //指向待排序数组的第一个元素的指针
         size_t num,  //base指向数组中的元素个数
         size_t size,  //base指向的数组中一个元素的大小,单位是字节

          // 需要自身提供一个比较函数,对返回值有要求。返回值为多少进行交换
         int (*compar) (const void*, const void*)  //函数指针-传递函数的地址
         );

         这里我们用结构体进行排序,同时对名字进行排序。其排序是用ASCII码的大小进行的排序

#include <stdio.h>

struct Stu //创建一个结构体
{
	char name[20];
	int age;
};

int cmp_stu_by_name(const void* p1, const void* p2)
{
	//由于返回的是void先强转 (struct Stu*)p1, 在用强转指向在加一个()表示这是强转的指针再进行指向 ((struct Stu*)p1)->name
	// 比较的是字符串,应该使用 strcmp 函数
    // p1<p2      return -1
    // p1 == p2   return 0
    // p1 > p2    return 1
	return strcmp( ((struct Stu*) p1)->name, ((struct Stu*) p2)->name );
}

void test()
{
	struct Stu arr[] = { {"wang", 20}, {"su", 22}, {"fu", 18} };
	int sz = sizeof(arr) / sizeof(arr[0]);//元素个数
	//先传首元素地址,第二个参数传元素个数,第三个传一个元素有多大,第四个传递一个函数用来比较结构体对象(按照名字进行比较,创建一个函数)
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);//对结构体进行排序
	//打印排序后的结构体类型
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s,%d\n", arr[i].name, arr[i].age);
	}
}

int main()
{
	test();//测试结构体类型
	return 0;
}

2.2qsort总结 

        总结:返回值>0交换,返回值=0不交换。时间复杂度:O(logN) 空间复杂度:取决于调用函数。但空间复杂度在如今基本上不算一个问题。qsort是一直快速排序。它对比较类型没有限制。

        3.利用冒泡模仿实现qsort函数

        我们在前面对于冒泡排序和qsort函数都有一定的了解。那么如何利用冒泡排序进行模仿实现呢?我们知道了qsort函数的特性。在传参的时候我们模仿qsort函数,也用void来接收。我们的for循环不用变,但里面的 if 判断需要变换。我们将它变成char类型,指向它的起始地址,通过传过来的类型大小确定一个返回(通过指针加偏移量来实现)。当我们判断之后就要交换。既然我们要比较和交换各种类型。那么直接将它一个字节一个字节的转换。所以我们还需要定义一个函数交换字节,里面通过for循环一个字节一个字节的转换。它需要两个元素的地址,并且变成char型,知道元素类型几个字节,就可以通过这进行交换。在下面代码中我们既可以通过 (if)来决定排序方向,也可以通过自身提供的比较函数返回值来决定排序大小

Swap(char* buf1, char* buf2, size_t sz)//交换字节函数,既然不知道类型,那就根据你字节的大小进行交换
{
	int i = 0;
	char tmp = 0;
	for (i = 0; i < sz; i++)//sz为交换类型的大小
	{
		//交换一个字节
		tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		//指向下一个字节的起始位置,根据类型来交换几个字节
		buf1++;
		buf2++;
	}
}
// 将原来接受数组换成指针,返回类型变成void 这样指针可以接受任何类型的地址
// 第二个传过来元素个数是没有错的
// 如果我们的类型不是整形是字符串,是小数呢,一个元素有多长是不确定的,所以我们要引入传进来元素个数是多少sizeof(int)
//我们不知道将来要比较的数据,所以我们要返回为void类型
up_bubble(void* base, int sz, size_t se,int(*cmp)(const void*p1, const void*p2))
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			int tep = 0;
			// 如果我们放进来的是一个字符串是不能进行比较的,所以我们要把这个给取消
			//换成一个函数
			//我们知道int有4个字节,char有一个字节,我们给cmp传地址,虽然是void指针,但是我们可以强转为char
			//我们传过来的地址为base 当(char*)base 传过来首元素地址,我们的 size是类型,也就说有几个字节,还是以int为例子
			//当我们传过来int size就是4 我们令((char*)base +size 不就行了找一个+size 找两个 +2*size 依次类推
			//if (cmp((char*)base + j * se, (char*)base + (j + 1) * se) < 0) //降序排列
			if (cmp((char*)base + j * se, (char*)base + (j + 1) * se) > 0)//会在cop函数里面进行比较,如果返回值>0说明前面的大于后面的需要交换顺序。
			{
				//交换两个元素
				Swap((char*)base + j * se, (char*)base + (j + 1) * se, se);
			}
		}
	}
}

int int_cmp(const void* p1, const void* p2)//比较 p1指向和p2指向的函数的大小
{
	return (*( int*)p1 - *( int*)p2);//由于是void返回类型所以要进行强制类型转换
	//return (*(int*)p2 - *(int*)p1);//这样就可以实现降序,在比较函数里面改变一下逻辑顺序
}

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

int main()
{
	int arr[] = { 4,2,63,23,65,-23,43,60 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	up_bubble(arr, sz, sizeof(int), int_cmp);//升级版冒泡
	printf_arr(arr, sz);
	return 0;
}

        

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值