1.什么是冒泡排序

要想用c语言实现冒泡排序,就要先知道什么是冒泡排序。

它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序错误就把他们交换过来。走访元素的工作是重复地进行,直到没有相邻元素需要交换,也就是说该元素列已经排序完成。

这段话是什么意思呢? 

简单的来说,就是先将数组下标为0与下标为1的数相比较,然后是下标为1的与下标为2的数相比较,一直比较到数组的最后一个元素。我们拿正序做例子,这样比较一趟下来就可以将数组中最大的元素换到数组的最后一个位置,然后再从较改变后数组下标为0的位置继续这个流程,直到排序结束。这里我们写出来可能更好理解。

//我们定义一个逆序的数组
int arr[] = { 10,9,8,7,6,5,4,3,2,1 };
//第一趟冒泡排序完成后,数组就会变成
//arr = { 9,8,7,6,5,4,3,2,1,10 };
//第二趟
//arr = { 8,7,6,5,4,3,2,1,9,10 };
//
//······
//
//第九趟
//arr = { 1,2,3,4,5,6,7,8,9,10 };

通过观察,这样一趟下来只需要比较元素的个数-1次,第二趟则需要比较元素的个数-1-1次,第三趟比较元素的个数-1-2次······一共要循环数组元素个数-1趟。大概了解了冒泡排序后,下面我们来用c语言实现冒泡排序。

2.冒泡排序的c语言实现

我们用三种方式来实现冒泡排序,分别是数组传参实现,指针传参实现,类qsort方式实现,事实上,

qsort是用快速排序的方式来实现的,并不是冒泡排序。

(1)数组传参实现

首先用数组传参的方式。

上文提到,我们需要知道数组中的元素个数,所以定义了sz这个变量来表示数组中元素的个数。

int main()
{
	test_arr();//冒泡(数组实现)
	return 0;
}

//数组的传参方式
void test_arr()
{
	int arr[] = { 10,9,8,15,17,52,7,6,5,4,101,3,-7,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	bubble_sort(arr, sz);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

void bubble_sort(int arr[], int sz)
{
	//冒泡数组实现
	int i = 0;
	int j = 0;
	for (i = 0; i < sz; i++)
	{
		int left = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (arr[left] > arr[left + 1])
			{
				int tmp = arr[left];
				arr[left] = arr[left + 1];
				arr[left + 1] = tmp;
				flag = 0;
			}
			left++;
		}
	}

然而这样做有一个很大的问题,即使数组已经是正序,这个函数还是会循环完成后才退出。我们可以对这个代码进行优化

void bubble_sort(int arr[], int sz)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < sz; i++)
	{
		int left = 0;
		int flag = 1;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (arr[left] > arr[left + 1])
			{
				int tmp = arr[left];
				arr[left] = arr[left + 1];
				arr[left + 1] = tmp;
				flag = 0;
			}
			left++;
		}
		if (flag)
		{
			break;
		}
	}
}

这个flag可以帮我们判断这一趟排序的过程中有没有交换,如果没有发生交换,则说明数组已经是正序了,而没有交换的话flag的值还是为0,进入下面的if语句,跳出for循环。这样如果数组已经正序后函数将直接返回。

(2)指针传参实现

理解了上面的实现方式后,指针传参的方式也很容易理解了

int main()
{
	test_point();//冒泡(指针实现)
	return 0;
}

void test_point()
{
	int arr[] = { 10,9,8,15,17,52,7,6,5,4,101,3,-7,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	bubble_sort_point(arr, sz);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

void bubble_sort_point(int* arr, int sz)
{
	int* arr1 = arr;
	int i = 0;
	int j = 0;
	for (i = 0; i < sz; i++)
	{
		arr = arr1;
		int flag = 1;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (*arr > *(arr + 1))
			{
				int tmp = *arr;
				*arr = *(arr + 1);
				*(arr + 1) = tmp;
				flag = 0;
			}
			arr++;
		}
		if (flag)
		{
			break;
		}
	}
}

 这就是用指针传参的形式实现了。

(3)类qsort传参方式实现

我们要用类似qsort传参的方式实现冒泡排序,就要知道qsort的传参方式。

关于冒泡排序的c语言实现_排序算法

void qsort (void* base, size_t num, size_t size,int (*compar)(const void*, const void*));
//void* base — 要排序的数组的地址
//size_t num — 要排序的数组的元素个数
//size_t size — 要排序的数组中每个元素的所占字节个数
//int (*compar)(const void*, const void*) — 排序方式

这是qsort函数的传参方式,size_t实际上是unsigned int类型(见下图)。

关于冒泡排序的c语言实现_冒泡排序_02

通过传参我们可以知道qsort这个函数可以排序任何类型的数组,那我们可不可以用冒泡排序来实现排列任何类型的数组呢?

int main()
{
	test1();
	return 0;
}

void test1()
{
	int arr[] = { 10,9,8,15,17,52,7,6,5,4,101,3,-7,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	bubble_sort_Plus(arr, sz, sizeof(arr[0]), cmp_int);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

我们用bubble_sort_Plus这个函数来实现冒泡排序。

void bubble_sort_Plus(void* base, int sz, int width, int (*cmp)(const void*, const void*))

看到这个定义后你可能有很多问题,下面我们来一一解答。 

为什么要用void* 来接收?

首先,这个函数的目的是可以排序任何类型的数组,而只有void* 可以接收任何类型的指针,所以我们用void* 来接收用户传来的任何指针。


 用void* 接收后如何处理?

首先我们要知道用void* 指针接收后是不能直接对接收来的数据进行处理的。这时我们可以将base强制类型转化为char* ,为什么要用char* 来处理?因为char* 指针一次可以访问1个字节的长度,同时也是c语言中最短的指针,强制类型转化为char* 后我们就可以一个字节一个字节的处理元素,同时它又兼容所有类型,所有类型都可以处理到,所以要将base* 强制类型转化为char* 。


为什么要接收每个字节的长度?

理解了上个问题后这个问题就不难理解了,我们要一个字节一个字节的访问元素,所以要知道一个元素有多少字节。

int j = 0;
for(j=0;j<sz-1;j++)
{
    (char*)base + j * width;
    (char*)base + (j + 1) * width;
}

我们可以用类似这样的代码来访问数组中的每个元素和下一个元素。然后调用用户传来的cmp(判断)函数来实现排序。


c语言实现

int main()
{
	test1();
	return 0;
}

void test1()
{
	int arr[] = { 10,9,8,15,17,52,7,6,5,4,101,3,-7,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	bubble_sort_Plus(arr, sz, sizeof(arr[0]), cmp_int);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

void bubble_sort_Plus(void* base, int sz,
     int width, int (*cmp)(const void*, const void*))
{
	int i = 0;
	int j = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int flag = 1;
		for (j = 0; j < sz - 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);
				flag = 0;
			}
		}
		if (flag)
		{
			break;
		}
	}
}

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

如果要比较整形数据,我们可以传入一个这样的cmp函数:

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

同理,我们可以用这个函数来对任何类型的数组用用户传入的方法实现排序。

如需查看完整源码请访问:

bubble_sort_summary · 骑小羊过大河/C langua - 码云 - 开源中国 (gitee.com)


本章完。