一、 冒泡排序
什么是冒泡排序?
“冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。它重复地走访过要排序>的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。”
请看图解:
(注:图片来源于博客作者:烟雨倾城);
实现思路:
- 由上面图解可知,每一趟冒泡排序都完成对一个数的排序;所以有n个数,就要执行n-1趟冒泡排序,而每一趟冒泡,都是相邻两个数比较,大的数放右边;由此可知,我们需要设置两个for循环,外层循环控制要执行几趟冒泡排序,内层循环来实现两数之间比较和交换;
- 重点是控制两个for循环执行次数的语句:外层循环只需要执行n-1次;而对于内层循环,因为我们每一趟排序好一个数,所以我们交换两个数的次数是在减少的;所以内层for循环的控制语句应该是n-1-i次,第一趟需要交换n-1次,而第二趟排序,已经排序好了一个数,所以只需要交换n-1-1次,即n-1-i次;
算法实现:
//冒泡排序算法
#include<stdio.h>
//实现排序
void BubbleSort(int arr[], int sz)
{
int i = 0;
int j = 0;
//第一步:外层for循环确定冒泡排序有几趟
for (i = 0; i < sz - 1; i++)
{
//内层for循环来执行每一趟排序,交换两个元素
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;
}
}
}
}
//打印排序数组
Print(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("% d ", arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = { 8,2,3,6,7,9,10 };
int sz = sizeof(arr) / sizeof(arr[1]);
//先打印数组
Print(arr,sz);
//实现冒泡排序
BubbleSort(arr, sz);
Print(arr, sz);
return 0;
}
结果:
二、qsort()函数
1. qsort()函数介绍:
函数原型:
- qsort函数是什么类型的数据都可以排序
- qsort函数四个参数分别为:base中存放的是待排序数据的首地址,size_t num 表示排序数据元素的个数,size_t size 表示排序数据中每个元素所占大小,比较排序数据的两元素大小的方法(函数)
- 使用qsort函数要提供一个你所要比较的两个元素的比较方法
1. 使用qsort排序整型数据
//提供所要比较的两个操作数的比较方法:
int cop_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
Print(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("% d ", arr[i]);
}
printf("\n");
}
int main() {
//整型数据排序
int arr[10] = { 9,7,6,8,2,1,5,4,3,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
//排序
qsort(arr, sz, sizeof(arr[0]), cop_int);
//打印
Print(arr, sz);
return 0;
}
结果:
2. 使用qsort函数来排序结构体数据
//定义结构体类型
struct Stu {
char name[20];
int age;
};
//①编写年龄比较的方式函数
int sort_age(void* e1, void* e2)
{
//先把无类型指针e1强制转换成结构体指针
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//②编写名字比较方式的函数
int sort_name(void* e1, void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
void test_2() {
//使用qsort来排序结构体数据
//初始化结构体
struct Stu s[3] = { {"zhangsan",50},{"lisi",30},{"wangwu",20} };
//计算结构体的大小
int sz = sizeof(s) / sizeof(s[0]);
//①按照年龄来排序
qsort(s, sz, sizeof(s[0]), sort_age);
//②按照名字来排序
qsort(s, sz, sizeof(s[0]), sort_name);
}
int main() {
test_2();
return 0;
}
运行结果:
- 按照年龄排序:
- 按照姓名排序:
2.进阶
2.1 自己编写函数模仿qsort函数的通用算法
//编写交换元素的函数
void Swap(char* buf1, char* buf2, int width) {
int i = 0;
for (i = 0; i < width; i++) {
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
//分析:我们交换时,是每一个字节的数据相交换,直到把这个元素所占的字节数都交换;
void imitate_qsort(void* base, int sz, int width, int (*compare)(const void* e1, const void* e2))
{
int i = 0;
//排序的趟数
for (i = 0; i < sz - 1; i++)
{
int j = 0;//定义每趟要排几个
for (j = 0; j < sz - 1 - i; j++)
{
//两个元素比较,因为我们不知道具体要比较啥类型数据
//所有我们要接收一个用户自己编写的比较函数的地址,然后调用这个函数完成排序;
if (compare((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
//因为我们知道char类型数据在内存中只占一个字节
// 所以我们把定义的无类型指针base强制转换成char*
// 之后base*每次就可以一个字节的访问数据
// 然后,我们又收到了用户所要比较的数据中每个元素的大小sz
// 所以当我们拿已经知道起始地址的base*,加上元素的大小,这就可以访问每个元素了;
//交换元素,调用交换函数
}
}
}
}