冒泡排序整型
使用冒泡排序完成所有类型的数据排序之前,先学习一下快速排序(qsort)函数,为什么这个函数能够排序所有类型的数据。
快速排序(qsort)函数
MSDN查看函数的结构以及函数具体使用情况。
返回类型:void类型。参数:void*、两个无符号的整型、函数指针。函数指针的返回类型是int类型,参数是两个const void*。qsort函数的头文件是stdlib.h。
base:待排序数据的起始位置。
num:数组的元素个数。
width:一个元素的字节大小。
compare:比较函数(使用者根据实际情况实现一个比较函数)。
elem1、elem2:待比较两个元素的地址。
qsort函数的设计
void* base,为了qsort能够排序任何类型的数据,要接收任何类型的数据,只能用void*指针来接收。
size_t num,排序需要知道要排序的元素有多少个。
size_t width,接收的指针是void*指针,void*无法知道指向的对象数据占多少个字节,该指针无法进行解引用,不清楚访问下一个元素的时候需要跳过多少字节的大小,所以需要知道每个元素的大小,通过num和width就能知道全部元素已经元素的大小进行排序。
int (*cmpare)(const void* elem1, const void* elem2),设计qsort的人,并不知道使用者需要排序的数据类型,不同的类型的数据比较的方法是有所差异的,就需要使用者来实现比较函数,这个比较函数是回调函数,将函数的地址传给qsort函数,qsort使用函数指针调用比较函数,完成数据的比较。比较函数的参数为const void*类型,不知道排序的数据类型,用void*接收;为了避免错误操作,修改内容,用const修饰指针,指针不能通过*解引用修改内容。
<0:不变
=0:不变
>0:交换
qsort排序整型
#include <stdio.h>
#include <stdlib.h>
int cmp(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
int main()
{
int arr[5] = { 4,1,5,3,2 };
int num = sizeof(arr) / sizeof(arr[0]);
int width = sizeof(arr[0]);
qsort(arr, num, width, cmp);
for (int i = 0; i < num; ++i)
{
printf("%d ", arr[i]);
}
return 0;
}
void*是一种特殊的指针,可以接收任何类型指针。但是void*指针不能通过*解引用访问数据;void*指针+-整数也是不允许的,因为不知道void*指针的大小,要使用指针的时候,需要将void*指针强制类型转换后使用。
比较函数体内交换e1和e2的位置,升序变降序。
int cmp(const void* e1, const void* e2)
{
return *(int*)e2 - *(int*)e1;
}
注:前比后,升序;后比前,降序。
qsort排序结构体
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct student
{
char name[20];
int age;
};
// 比较年龄
int cmp_age(const void* e1, const void* e2)
{
return ((struct student*)e1)->age - ((struct student*)e2)->age;
}
// 比较名字
int cmp_name(const void* e1, const void* e2)
{
return strcmp(((struct student*)e1)->name, ((struct student*)e2)->name);
}
int main()
{
struct student arr[] = { {"zhangsan", 15}, {"lisi", 18},
{"wangwu", 17}, {"zhaoliu", 21} };
int num = sizeof(arr) / sizeof(arr[0]);
int width = sizeof(arr[0]);
// 按照结构体成员变量名字排序
qsort(arr, num, width, cmp_name);
int i = 0;
for (i = 0; i < num; i++)
{
printf("%s ", arr[i].name);
}
printf("\n");
// 按照结构体成本变量年龄
qsort(arr, num, width, cmp_age);
for (i = 0; i < num; i++)
{
printf("%d ", arr[i].age);
}
return 0;
}
字符串的比较不能直接相减比较,比较的是字符串每个字符的ASCII码值,比较字符串可以使用strcmp库函数,头文件是string.h。功能是比较字符串大小,strcmp(str1, str2),如果str1的字符串大于str2,返回一个正整数;如果两个字符串相同,返回0,;如果str1的字符串小于str2,返回一个负整数。
冒泡排序
排升序,依次比较两个相邻的元素,如果当前的元素比后面一个大,交换数据,每一趟排序,都会正确排好一个位置(最后一个,排一趟少一个),直到没有相邻元素需要交换,排序完成。核心思想是交换数据。
排序整型的升序冒泡排序 。
#include <stdio.h>
#include <stdbool.h>
// 需要传数组元素个数
void bubble_sort(int arr[], int sz)
{
// sz个元素需要排序sz-1趟
for (int i = 0; i < sz - 1; ++i)
{
bool flag = true;
// sz个数,排序sz-1次,每次排序一个数,下一趟少排一个数
for (int j = 0; j < sz - 1 - i; ++j)
{
// 前一个比后一个
if (arr[j] > arr[j + 1])
{
flag = false;
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
if (flag)
break;
}
}
int main()
{
int arr[] = { 5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
// 打印数据
for (int i = 0; i < sz; ++i)
printf("%d ", arr[i]);
return 0;
}
需要将冒泡排序的参数改为跟快速排序一样的四个参数。
// 原
void bubble_sort(int arr[], int sz)
// 新
void bubble_sort(void* base, int num, int width,
int (*cmp)(const void* e1, const void* e2))
参数类型没必要完全一样,size_t改为int类型也可以,只是能够接收的范围变少了。函数设计观念:不会出现负数的地方都使用无符号类型size_t。例如计算字符串长度的返回类型就是size_t类型。
排序不同的数据,都是两两数据进行比较,不同的数据就需要不同的比较方法,将这一点差异提取出来,设计成一个函数,让使用者自己实现,通过传参的方式,调用回调函数,实现比较数据的大小,冒泡排序根据比较结果决定是否排序。
比较函数的相邻数据的地址传参。
// 旧
if (arr[j] > arr[j + 1])
// 新
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
base需要先进行强制类型转换,才能使用+往后找到下一个元素。使用(char*)强制类型转换,+元素大小,跳过该元素找到下一个元素。需要依次比较,所以需要j*width,找到第j个元素。
冒泡排序需要交换元素的数据内容,如何交换?形参有一个是元素的大小,实现一个函数,将两个元素的数据按照字节交换width次,就可以完成两个元素的交换。
void swap(char* e1, char* e2, int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *e1;
*e1 = *e2;
*e2 = tmp;
e1++;
e2++;
}
}
冒泡排序排序任何类型的数据为升序。
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
struct student
{
char name[20];
int age;
};
//比较结构体整型
int cmp_age(const void* e1, const void* e2)
{
return ((struct student*)e1)->age - ((struct student*)e2)->age;
}
//比较结构体字符串
int cmp_name(const void* e1, const void* e2)
{
return strcmp(((struct student*)e1)->name, ((struct student*)e2)->name);
}
//每一个字节交换,width是元素大小,限制交换次数
void swap(char* e1, char* e2, int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *e1;
*e1 = *e2;
*e2 = tmp;
e1++;
e2++;
}
}
//冒泡排序
void bubble_sort(void* base, int num, int width,
int (*cmp)(const void* e1, const void* e2))
{
int i = 0;
// num个数需要排num-1趟
for (i = 0; i < num - 1; i++)
{
bool flag = true;
int j = 0;
// num个数排序num-1次,每次排好一个,下一趟少排一个
for (j = 0; j < num - 1 - i; j++)
{
// base+j*width之前需要先将base强制转换为(char*)类型,void*指针无法+-整数
// 根据比较函数的返回值判断,如果返回值大于0,交换数据
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
flag = false;
//传width,表示元素大小
swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
// 如果没有进行交换,说明数据本身就有序
if (flag)
break;
}
}
int main()
{
struct student arr[] = { {"zhangsan", 15}, {"lisi", 18},
{"wangwu", 17}, {"zhaoliu", 21} };
//计算数组大小
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_name);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s ", arr[i].name);
}
printf("\n");
bubble_sort(arr, sz, sizeof(arr[0]), cmp_age);
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i].age);
}
return 0;
}