之前我们系统学习了指针,这一章我们挑战一下指针编程项目中的————冒泡排序 并尝试利用冒泡排序实现
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
这避免了已经就位的元素被再次进行无意义的比较,是原始版本中唯一的“优化”
首先我们先了解什么叫动态边界 向我们之前的野蛮生长版,它是固定边界并且没办法检测是否发生交换 如果我们碰到1000个元素,只有一对无序 如果我们使用原始版本,必须完成999轮循环,每轮比较次数递减,非常繁琐!!! 动态边界可以彻底解决这两个问题
以数组 [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
有效的数组索引范围是: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;
}调酒师调制鸡尾酒时,会左右摇晃调酒器 鸡尾酒排序也是从左到右、从右到左交替进行的排序过程
从左到右:→ → → → →
从右到左:← ← ← ← ←
从左到右:→ → → → →
这种双向交替的运动方式很像调鸡尾酒时的摇晃动作#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;
}
qsort是 C 语言标准库中的通用快速排序函数,可以对任意类型的数组进行排序,只要你提供比较函数。
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 类似 |
comparint 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;
}