之前我们系统学习了指针,这一章我们挑战一下指针编程项目中的————冒泡排序
并尝试利用冒泡排序实现qsort函数
冒泡排序是一种简单的排序算法。它重复地“遍历”要排序的数列,一次比较两个相邻的元素,如果它们的顺序错误(例如,从小到大排序时,前一个比后一个大),就把它们交换过来。
冒泡排序
野蛮生长版:基础版(理解概念)
#include <stdio.h>
// 冒泡排序函数 (原始版本)
void bubbleSort(int arr[], int n) {
// 外层循环:控制排序的轮数,需要进行 n-1 轮
for (int i = 0; i < n - 1; i++) {
// 内层循环:负责每一轮中的相邻元素比较和交换
// 随着轮数 i 的增加,最后 i 个元素已经有序,所以 j 的范围逐渐减小
for (int j = 0; j < n - 1 - i; j++) {
// 如果前面的元素比后面的大,就交换它们
if (arr[j] > arr[j + 1]) {
// 交换 arr[j] 和 arr[j+1]
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
printf("\n");
}
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++)
printf("%d ", arr[i]);
}
// 主函数来测试
int main() {
int arr[] = { 64, 34, 25, 12, 22, 11, 90 };
int n = sizeof(arr) / sizeof(arr[0]); // 计算数组长度
printf("Original array: \n");
printArray(arr, n);
bubbleSort(arr, n); // 调用冒泡排序函数
printf("Sorted array: \n");
printArray(arr, n);
return 0;
}
上面代码中,我们以数组
arr为研究对象
我们观察外层循环:控制排序的轮数
这个数组内一共有七个元素,每每两个元素进行比较,总共有六轮排序
我们观察内层循环:负责每一轮中的相邻元素比较和交换
这样第一个大循环都会把最大的那个数推到最右边
第二次大循环,数组末尾的 i 个元素都已经是排好序的最大值了,内层循环无需再处理它们,所以比较的范围每次减少 1
这避免了已经就位的元素被再次进行无意义的比较,是原始版本中唯一的“优化”
效率性能优:优化版
1)标志位 + 动态边界
首先我们先了解什么叫动态边界
向我们之前的野蛮生长版,它是固定边界并且没办法检测是否发生交换
如果我们碰到1000个元素,只有一对无序
如果我们使用原始版本,必须完成999轮循环,每轮比较次数递减,非常繁琐!!!动态边界可以彻底解决这两个问题
1. 标志位优化 (宏观感知)
检测整个数组是否已经有序
如果某一轮没有发生任何交换,说明数组已经完全有序,立即终止排序2. 动态边界优化 (微观优化)
智能调整每轮的比较范围,跳过已经有序的尾部元素
不再使用固定的 n-1-i 边界
代码可视化执行
以数组 [5, 3, 8, 6, 2] 为例:
第1轮执行初始: [5, 3, 8, 6, 2], lastSwapIndex = 4
比较范围: j=0到3
交换发生: 位置0(5↔3), 位置2(8↔6), 位置3(8↔2)
currentSwapIndex = 3 (最后一次交换位置)
更新: lastSwapIndex = 3
结果: [3, 5, 6, 2, 8]第2轮执行
比较范围: j=0到2 (因为lastSwapIndex=3)
交换发生: 位置2(6↔2)
currentSwapIndex = 2
更新: lastSwapIndex = 2
结果: [3, 5, 2, 6, 8]第3轮执行
比较范围: j=0到1 (lastSwapIndex=2)
交换发生: 位置1(5↔2)
currentSwapIndex = 1
更新: lastSwapIndex = 1
结果: [3, 2, 5, 6, 8]第4轮执行
比较范围: j=0到0 (lastSwapIndex=1)
交换发生: 位置0(3↔2)
currentSwapIndex = 0
更新: lastSwapIndex = 0
结果: [2, 3, 5, 6, 8]第5轮执行
比较范围: j=0到-1 → 循环不执行
swapped = false → 提前终止!
代码思路解析
- 首先我们需要判断数组是否经过交换,这里我们用到布尔类型
//定义头文件
#include <stdio.h>
#include <stdbool.h>
- 第一轮需要比较所有元素,所以边界设为数组最后一个索引
int lastSwapIndex = n - 1; // 初始化最后交换位置为数组末尾
- 我们定义一个布尔标志位,检测是否发生交换
bool hasSwapped = false;
- 我们观察基础版的冒泡排序函数
void bubbleSort(int arr[], int n) {
// 外层循环:控制排序的轮数,需要进行 n-1 轮
for (int i = 0; i < n - 1; i++) {
// 内层循环:负责每一轮中的相邻元素比较和交换
// 随着轮数 i 的增加,最后 i 个元素已经有序,所以 j 的范围逐渐减小
for (int j = 0; j < n - 1 - i; j++) {
// 如果前面的元素比后面的大,就交换它们
if (arr[j] > arr[j + 1]) {
// 交换 arr[j] 和 arr[j+1]
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
printf("\n");
}
- 我们需要在
bubbleSort函数里面创建一个临时变量,并修改一下循环条件
void bubbleSort(int arr[], int n) {
int lastSwapIndex = n - 1;
for(int i = 0; i < n - 1; i++) {
for (int j = 0; j < lastSwapIndex; j++) {
// 如果前面的元素比后面的大,就交换它们
if (arr[j] > arr[j + 1]) {
// 交换 arr[j] 和 arr[j+1]
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
printf("\n");
}
- 冒泡排序发生在循环里面,在外层循环创建布尔类型
void bubbleSort(int arr[], int n) {
int lastSwapIndex = n - 1;
for(int i = 0; i < n - 1; i++) {
bool hasSwapped = false;
for (int j = 0; j < lastSwapIndex; j++) {
// 如果前面的元素比后面的大,就交换它们
if (arr[j] > arr[j + 1]) {
// 交换 arr[j] 和 arr[j+1]
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
printf("\n");
}
- 内层循环在
if条件判断里面写上:
hasSwapped = true;
在上面函数经过一次大循环后,数组里面最大的数已经被退到最右边,所以我们就把边界定义为倒数第二位
- 更新边界信息:
我们通过创建临时变量来存储边界,避免和lastSwapIndex产生冲突
void smartBubbleSort(int arr[], int n) {
int lastSwapIndex = n - 1; // 动态边界:记录有效比较范围
for (int round = 0; round < n - 1; round++) {
bool hasSwapped = false; // 标志位:检测是否发生交换
int currentSwapBoundary = -1;
// 智能比较:只遍历到动态边界
for (int j = 0; j < lastSwapIndex; j++) {
if (arr[j] > arr[j + 1]) {
// 交换元素
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
hasSwapped = true; // 标记发生交换
currentSwapBoundary = j; // 更新本轮交换边界
}
}
为何currentSwapBoundary初始化为-1
这里讲解一下为什么将currentSwapBoundary初始化为-1
- 尚未记录有效位置
有效的数组索引范围是:0 到 n-1
-1 明显在这个范围之外,不会与任何有效索引混淆
- 只有真正交换过才能才更新
if (currentSwapBoundary != -1) { // 只有真正交换过才更新
lastSwapIndex = currentSwapBoundary;
}
- 如果没有这个检查或者为其它值:
// 假设本轮没有发生任何交换
// currentSwapBoundary 保持初始值
// 如果是随机初始值:
lastSwapIndex = 随机值; // 可能导致数组越界!
// 如果是0:
lastSwapIndex = 0; // 错误地将边界设为0,下轮无法正确比较
// 其他可能但不好的选择:
int currentSwapBoundary = 0; // 可能被误认为是有效位置
int currentSwapBoundary = n; // 可能越界,语义不清晰
int currentSwapBoundary = -999; // 魔数,不够直观
最后的代码为
void smartBubbleSort(int arr[], int n) {
int lastSwapIndex = n - 1; // 动态边界:记录有效比较范围
for (int i = 0; i < n - 1; i++) {
bool hasSwapped = false; // 标志位:检测是否发生交换
int currentSwapBoundary = -1; // 本轮实际交换边界
// 智能比较:只遍历到动态边界
for (int j = 0; j < lastSwapIndex; j++) {
if (arr[j] > arr[j + 1]) {
// 交换元素
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
hasSwapped = true; // 标记发生交换
currentSwapBoundary = j; // 更新本轮交换边界
}
}
// 更新下一轮的边界
if (currentSwapBoundary != -1) {
lastSwapIndex = currentSwapBoundary;
}
// 如果没有发生交换,提前结束
if (!hasSwapped) {
printf("🚀 性能碾压:第%d轮提前终止!\n", i + 1);
break;
}
printf("第%d轮后边界更新为:%d\n", i + 1, lastSwapIndex);
}
}
完整代码展示
#include <stdio.h>
#include <stdbool.h>
// 冒泡排序性能优化版(标志位 + 动态边界)
void smartBubbleSort(int arr[], int n) {
int lastSwapIndex = n - 1; // 动态边界:记录有效比较范围
for (int i = 0; i < n - 1; i++) {
bool hasSwapped = false; // 标志位:检测是否发生交换
int currentSwapBoundary = -1; // 本轮实际交换边界
// 智能比较:只遍历到动态边界
for (int j = 0; j < lastSwapIndex; j++) {
if (arr[j] > arr[j + 1]) {
// 交换元素
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
hasSwapped = true; // 标记发生交换
currentSwapBoundary = j; // 更新本轮交换边界
}
}
// 更新下一轮的边界
if (currentSwapBoundary != -1) {
lastSwapIndex = currentSwapBoundary;
}
// 如果没有发生交换,提前结束
if (!hasSwapped) {
printf("🚀 性能碾压:第%d轮提前终止!\n", i + 1);
break;
}
printf("第%d轮后边界更新为:%d\n", i + 1, lastSwapIndex);
}
}
// 打印数组函数
void printArray(int arr[], int n) {
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
// 复制数组函数(用于测试多个用例)
void copyArray(int source[], int dest[], int n) {
for (int i = 0; i < n; i++) {
dest[i] = source[i];
}
}
int main() {
printf("=== 动态边界冒泡排序测试 ===\n\n");
// 测试用例1:普通乱序数组
printf("测试1: 普通乱序数组\n");
int arr1[] = {64, 34, 25, 12, 22, 11, 90};
int n1 = sizeof(arr1) / sizeof(arr1[0]);
printf("排序前: ");
printArray(arr1, n1);
smartBubbleSort(arr1, n1);
printf("排序后: ");
printArray(arr1, n1);
printf("\n");
// 测试用例2:完全有序数组(展示提前终止)
printf("测试2: 完全有序数组\n");
int arr2[] = {1, 2, 3, 4, 5, 6};
int n2 = sizeof(arr2) / sizeof(arr2[0]);
printf("排序前: ");
printArray(arr2, n2);
smartBubbleSort(arr2, n2);
printf("排序后: ");
printArray(arr2, n2);
printf("\n");
// 测试用例3:几乎有序数组
printf("测试3: 几乎有序数组\n");
int arr3[] = {1, 2, 3, 5, 4, 6, 7, 8};
int n3 = sizeof(arr3) / sizeof(arr3[0]);
printf("排序前: ");
printArray(arr3, n3);
smartBubbleSort(arr3, n3);
printf("排序后: ");
printArray(arr3, n3);
printf("\n");
// 测试用例4:完全逆序数组
printf("测试4: 完全逆序数组\n");
int arr4[] = {9, 8, 7, 6, 5, 4, 3, 2, 1};
int n4 = sizeof(arr4) / sizeof(arr4[0]);
printf("排序前: ");
printArray(arr4, n4);
smartBubbleSort(arr4, n4);
printf("排序后: ");
printArray(arr4, n4);
printf("\n");
// 测试用例5:包含重复元素的数组
printf("测试5: 包含重复元素的数组\n");
int arr5[] = {5, 2, 8, 2, 5, 1, 8, 1};
int n5 = sizeof(arr5) / sizeof(arr5[0]);
printf("排序前: ");
printArray(arr5, n5);
smartBubbleSort(arr5, n5);
printf("排序后: ");
printArray(arr5, n5);
printf("\n");
// 性能对比演示
printf("=== 性能优化演示 ===\n");
int test1[] = {1, 2, 3, 4, 5, 6}; // 完全有序
int test2[] = {6, 5, 4, 3, 2, 1}; // 完全逆序
int n = sizeof(test1) / sizeof(test1[0]);
printf("完全有序数组: ");
printArray(test1, n);
smartBubbleSort(test1, n);
printf("完全逆序数组: ");
printArray(test2, n);
smartBubbleSort(test2, n);
return 0;
}
2)鸡尾酒排序
调酒师调制鸡尾酒时,会左右摇晃调酒器
鸡尾酒排序也是从左到右、从右到左交替进行的排序过程
从左到右:→ → → → →
从右到左:← ← ← ← ←
从左到右:→ → → → →
这种双向交替的运动方式很像调鸡尾酒时的摇晃动作
#include <stdio.h>
// 鸡尾酒排序函数
void cocktailSort(int arr[], int n) {
int swapped = 1; // 标记是否发生交换
int start = 0; // 左边界
int end = n - 1; // 右边界
while (swapped) {
swapped = 0;
// 从左到右的冒泡排序(将最大元素移到右边)
for (int i = start; i < end; i++) {
if (arr[i] > arr[i + 1]) {
// 交换元素
int temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;
swapped = 1;
}
}
// 如果没有发生交换,说明数组已经有序
if (!swapped) {
break;
}
swapped = 0;
end--; // 右边界减1,因为最后一个元素已经有序
// 从右到左的冒泡排序(将最小元素移到左边)
for (int i = end - 1; i >= start; i--) {
if (arr[i] > arr[i + 1]) {
// 交换元素
int temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;
swapped = 1;
}
}
start++; // 左边界加1,因为第一个元素已经有序
}
}
// 打印数组函数
void printArray(int arr[], int n) {
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
// 测试代码
int main() {
int arr[] = {5, 1, 4, 2, 8, 0, 2};
int n = sizeof(arr) / sizeof(arr[0]);
printf("原始数组: ");
printArray(arr, n);
cocktailSort(arr, n);
printf("排序后数组: ");
printArray(arr, n);
return 0;
}
3)利用冒泡排序实现qsort函数
qsort函数复习
qsort是 C 语言标准库中的通用快速排序函数,可以对任意类型的数组进行排序,只要你提供比较函数。
1)函数原型
void qsort(void *base, size_t nitems, size_t size,
int (*compar)(const void *, const void *));
| 参数名 | 类型 | 说明 |
|---|---|---|
| base | void * | 指向数组起始位置(任意类型) |
| nitems | size_t | 数组元素的个数 |
| size | size_t | 每个元素的字节大小(通常使用 sizeof(…)) |
| compar | 函数指针 | 比较函数,返回值与 strcmp 类似 |
比较函数 compar
int compare_ints(const void *a, const void *b) {
return (*(int *)a - *(int *)b); // 升序
}
完整代码
#include <stdio.h>
#include <stdlib.h>
int compare_ints(const void *a, const void *b) {
return (*(int *)a - *(int *)b); // 升序
}
int main() {
int arr[] = {5, 2, 9, 1, 3};
size_t len = sizeof(arr) / sizeof(arr[0]);
qsort(arr, len, sizeof(int), compare_ints);
for (size_t i = 0; i < len; i++) {
printf("%d ", arr[i]);
}
return 0;
}
我们已经将冒泡排序算法讲解透彻,重点掌握其循环与数组比较的知识点
如果你觉得对你有帮助,请给文章来波三连哦





2095

被折叠的 条评论
为什么被折叠?



