当前位置:首页 » 《随便一记》 » 正文

C语言实现冒泡排序的三种优化和qsort函数的解析与模拟实现_平凡的指针的博客

15 人参与  2022年03月01日 14:16  分类 : 《随便一记》  评论

点击全文阅读


在这里插入图片描述

文章目录

  • 冒泡排序
    • 普通版本
    • 对外层循环进行优化
    • 对内循环进行优化
    • 双向冒泡排序
  • qsort函数
    • 分析与使用
    • 模拟实现qsort函数

冒泡排序

当我们第一次提起冒泡排序的时候,我们总是要问什么是冒泡排序呢?
简单的说,就是重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。

而这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。

普通版本

我们先对整数类型的数据进行排序,比如我们要排 5 6 1 3 4 2 7 8 这8个数字,令它们变成有序数组。
图片分析如下:
在这里插入图片描述
代码如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

void BubbleSort(int* p, int sz)
{
	int i = 0, j = 0;
	for (int i = 0; i < sz - 1; i++) //比较的总趟数为元素个数减1
	{
		//控制每一趟过程比较的个数
		//每一趟结束后,下一趟比较的最后一个元素就是前面那一位
		for (int j = 0; j < sz - 1 - i; j++) 
		{
			//前面元素大于后面元素就交换
			if (p[j] > p[j + 1])
			{
				int tmp = p[j];
				p[j] = p[j + 1];
				p[j + 1] = tmp;
			}
		}
	}
}

void Print(int* p, int sz) //打印数组内的元素
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", p[i]);
	}
	printf("\n");
}

int main()
{
   //用两个数组来验证
	int arr[] = { 5,6,1,3,4,2,7,8 };
	int arr2[] = { 10,9,8,7,6,5,4,3,2,1 };
	//求出数组内的元素个数为sz
	int sz = sizeof(arr) / sizeof(arr[0]);
	int sz2 = sizeof(arr2) / sizeof(arr2[0]);
	//排序数组arr
	BubbleSort(arr, sz);  //普通的冒泡排序
	Print(arr, sz);  //打印函数
	
    //排序数组arr2
    BubbleSort(arr2, sz2);
	Print(arr, sz);
	return 0;
}

程序执行结果如下: 在这里插入图片描述

对外层循环进行优化

大家请先看第三轮到最后一轮的排序:
在这里插入图片描述
不知道大家发现一个问题没有,上面从第四趟开始就已经是有序的了,但是我们的程序还是要跑一次,我们可以从这个地方入手来优化。
思路如下:
我们可以设一个变量flag等于0,在每一趟过程中;如果程序验证到前面的数比后面的数大,说明数组还没有序,并且令变量flag置为1。如果在一趟过程中都没有数比后面的数更大了,变量flag不作任何修改仍然为0,说明数组已经有序。
最后我们在每一趟结束后判断变量flag是否为0;如果flag为1,说明数组还没有排序好,程序仍然继续下一趟排序;如果flag为0,说明数组已经排序好了,不进行下一趟排序,退出。从而达到减少趟数的效果。
代码如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

//对外循环进行优化1
void BubbleSort1(int* p, int sz)
{
	int i = 0; 
	int j = 0;
	for (i = 0; i < sz - 1; i++)  // 控制趟数
	{
		//变量flag一定要放在每一趟的开头,这样flag每一趟才会置为0
		int flag = 0;
		for (j = 0; j < sz - 1 - i; j++)  //控制每一趟比较的次数
		{
			if (p[j] > p[j + 1])
			{
				//发现前面的数比后面的数大,数组没有序
				//把flag置为1
				int tmp = p[j];
				p[j] = p[j + 1];
				p[j + 1] = tmp;
				flag = 1;
			}
		}
		// 在每一趟结束后判断flag是否改变
		if (flag == 0)
		{
			//flag没有变,数组已经有序,退出排序即可
			return;
		}
	}
}

void Print(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", p[i]);
	}
	printf("\n");
}

int main()
{
   //用两个数组来验证
	int arr[] = { 5,6,1,3,4,2,7,8 };
	int arr2[] = { 10,9,8,7,6,5,4,3,2,1 };
	//求出数组内的元素个数为sz
	int sz = sizeof(arr) / sizeof(arr[0]);
	int sz2 = sizeof(arr2) / sizeof(arr2[0]);
	//排序数组arr
	BubbleSort1(arr, sz);  //优化版本1的冒泡排序
	Print(arr, sz);  //打印函数
	
    //排序数组arr2
    BubbleSort1(arr2, sz2);
	Print(arr, sz);
	return 0;
}

对内循环进行优化

大家再来看看原来数组排序的过程:
在这里插入图片描述
大家发现一个问题没有,那就是在排序过程中后面一小段本来已经是有序的了,不需要我们再进行排序,我们只需要排序到最后元素交换的地方就可以了
思路如下:
我们设个变量max来记录,最后一次元素交换的下标,从而我们在下一趟的排序中,只需要排序到变量max的地方即可,并且加上上面分析的趟数优化即可。
代码如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

void BubbleSort2(int* p, int sz)
{
	int i = 0, j = 0;
	int pos = sz - 1;  //第一次排序的极限先给到最后一个元素
	int max = 0;   //来保存最后一次交换的下标
	for (i = 0; i < sz - 1; i++)
	{
		int flag = 0; 
		for (j = 0; j < pos; j++)
		{
			if (p[j] > p[j + 1])
			{
				int tmp = p[j];
				p[j] = p[j + 1];
				p[j + 1] = tmp;
				flag = 1;
				//每一次交换都把下标保存到max中
				max = j; 
			  //不能在这里直接把j赋值给pos,
			  //这里不能确定是最后一次元素交换的下标
			  //要等到每一趟结束后才行
			}
		}
		// 每一趟过后把保存的下标赋值给pos
		//这里也不能直接把j赋值给pos,
		//因为最后一次交换的下标j还会继续变化
		pos = max;
		if (flag == 0)
		{
			//flag没有变,数组已经有序,退出排序即可
			return;
		}
	}
}

void Print(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", p[i]);
	}
	printf("\n");
}

int main()
{
   //用两个数组来验证
	int arr[] = { 5,6,1,3,4,2,7,8 };
	int arr2[] = { 10,9,8,7,6,5,4,3,2,1 };
	//求出数组内的元素个数为sz
	int sz = sizeof(arr) / sizeof(arr[0]);
	int sz2 = sizeof(arr2) / sizeof(arr2[0]);
	//排序数组arr
	BubbleSort2(arr, sz);  //优化版本2的冒泡排序
	Print(arr, sz);  //打印函数
	
    //排序数组arr2
    BubbleSort2(arr2, sz2);
	Print(arr, sz);
	return 0;
}

双向冒泡排序

我们在排序的过程中有时候会碰到特殊的例子,比如排序:9 1 2 3 4 5 6 0 。最大数9在最左边,而最小数0却在最右变,如果还是按照上面的方法优化是节省不了多少步数的。我们还需要另外一种方法才行。
思路如下:
这时候我们会想如果把最大数9和最小数0调换位置不就可以了嘛,所以我们想到了既然数组可以从左向右遍历,那也可以从右往左遍历。从左向右把最大的数移到最右;从右往左把最小的数移到最左。我们设两个变量left和right,当left小于right排序才执行,不然就退出。然后再加上前面的两种优化退出方式。

图片分析如下:
在这里插入图片描述
代码如下:

//最终版本优化
void BubbleSort3(int* p, int sz)
{
	int left = 0;  //左边起始下标为0
	int right = sz - 1;   //右边起始下标为最后一个元素
	int max = 0;    
	int min = 0;
	while (left < right)
	{
		int i = 0;
		int flag = 0;
		// 从左边到右边排序
		for (i = left; i < right; i++)
		{
			if (p[i] > p[i + 1])
			{
				int tmp = p[i];
				p[i] = p[i + 1];
				p[i + 1] = tmp;
				flag = 1;
				// 保存交换的下标
				max = i;  
			}
		}
		//把最后交换的下标赋值给right
		right = max;
		if (flag == 0)
		{
			return ;
		}

		// 从右边往左边排序
		flag = 0;  //要把flag重新置为0
		for (i = right; i > left; i--)
		{
			if (p[i] < p[i - 1])
			{
				int tmp = p[i];
				p[i] = p[i - 1];
				p[i - 1] = tmp;
				flag = 1;
				min = i;
			}
		}
		//把最后交换的下标赋值给right
		left = min;
		if (flag == 0)
		{
			return ;
		}
	}
}

qsort函数

大家发现没有,前面分析的冒泡排序只能对整数进行排序,当我们想对字符串或者字符类型排序时就实现不了,那么有什么方法来解决呢?
我们就得提起qsort函数了,它能够对任何类型进行排序,下面是它的用法与解析。

分析与使用

在这里插入图片描述
值得注意的是最后一个参数是函数指针类型,它要我们写一个函数来实现比较两个元素,然后qsort函数会自动帮我们进行排序。
使用举例:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//比较整形类型大小的函数
int compar1(const void* a, const void* b)
{
	if ((*(int*)a - *(int*)b) > 0)
	{
		return 1;
	}
	else if ((*(int*)a - *(int*)b) == 0)
	{
		return 0;
	}
	else
	{
		return -1;
	}
	//return ( *(int*)a - *(int*)b );
	//这样有可能会产生溢出
}

//比较字符类型大小的函数
int compar2(const void* a, const void* b)
{
	if ((*(char*)a - *(char*)b) > 0)
	{
		return 1;
	}
	else if ((*(char*)a - *(char*)b) == 0)
	{
		return 0;
	}
	else
	{
		return -1;
	}
}

int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1 };
	char arr2[] = "gfedcba";
	int sz = sizeof(arr) / sizeof(arr[0]);
	int sz2 = strlen(arr2);

	//对整形类型进行排序
	qsort(arr, sz, sizeof(arr[0]), compar1); 
	//对字符类型进行排序
	qsort(arr2, sz2, sizeof(arr2[0]), compar2);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");

	printf("%s\n", arr2);
	return 0;
}

程序执行结果如下:
在这里插入图片描述

模拟实现qsort函数

经过上面的举例使用我们大概了解qsort函数的特性,那么再结合我们前面所分析的冒泡排序,我们能不能使用冒泡排序来模拟实现qsort函数呢?
在这里插入图片描述

分析如下:
我们实现这个函数有两个难点:第一是怎么找到我的下一位元素呢?因为数组的第一个元素指针是void* 类型的,从而加减的步伐大小就不能确定。
第二个难点,就算确定两个元素了,因为数组的第一个元素指针是void* 类型的,就不能进行解引用,从而就不能进行元素之间进行交换了。
那么有没有解决的办法呢?
我们再来看看qsort函数的四个参数,其中第三个参数比较有特点,要把数组中的元素大小传过去,这有什么用处呢?这个参数是实现qsort函数的关键。

我们知道在所有指针类型当中,char* 类型指针的步伐是最小的,每次加一就跳过一个字节,所以当我们知道元素的大小即宽度之后,我们把第一个元素指针是void* 强制转化为char* 类型,在循环体中用个变量j乘上宽度再加上第一个元素指针,这样可以随便找到数组中的任意元素。
而我们知道元素的宽度,在两个元素交换的时候我们可以一个字节一个字节的交换,交换的次数就是元素的宽度。这样两个难点就解决了。

代码实现如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct people
{
	char name[10];
	int age;
	char fun[10];
	double grade;
};


//比较整形类型大小的函数
int compar1(const void* a, const void* b)
{
	if (((struct people*)a)->age - ((struct people*)b)->age > 0)
	{
		return 1;
	}
	else if (((struct people*)a)->age - ((struct people*)b)->age == 0)
	{
		return 0;
	}
	else
	{
		return -1;
	}
}
int compar3(const void* a, const void* b)
{
	return strcmp(((struct people*)a)->name, ((struct people*)b)->name);
}

void Swap(char* str1, char* str2, int width)
{
	//交换元素,一个字节一个字节进行交换
	//交换的次数为宽度大小
	while (width--)
	{
		char tmp = *(str1);
		*(str1) = *(str2);
		*(str2) = tmp;
		str1++;
		str2++;
	}
}

void my_qsort(void* base, int sz, int width, int compar(const void* a, const void* b))
{
	//冒泡排序的方式实现qsort
	int i = 0, j = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int flag = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			//把要比较的元素地址传给比较函数compar
			if (compar((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//交换元素
				Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
				flag = 1;
			}
		}
		if (flag == 0)
		{
			return;
		}
	}
}

void Print(struct people* pc,int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s ", pc[i].name);
		printf("%d ", pc[i].age);
		printf("%s ", pc[i].fun);
		printf("%f ", pc[i].grade);
		printf("              ");
	}
	printf("\n\n");
}


int main()
{
	struct people arr[] =
	{ {"zhangsan",22,"add",3.14},{"lisi",18,"sub",6.67},{"wangw",28,"ghe",8.84} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//对字符串大小进行排序
	printf("对字符串大小进行排序:\n");
	my_qsort(arr, sz, sizeof(arr[0]), compar3);
	Print(arr, sz);

	// 对整形大小进行排序
	printf("对整形大小进行排序:\n");
	my_qsort(arr, sz, sizeof(arr[0]), compar1);
	Print(arr, sz);
	return 0;
}

注意:qsort函数可以帮我们实现很多类型的排序,但是我们要自己写一个能比较两个元素大小的函数,并且把函数地址传给qsort。
程序执行结果:
在这里插入图片描述
在这里插入图片描述


点击全文阅读


本文链接:http://zhangshiyu.com/post/35587.html

排序  元素  数组  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1