首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >初探算法的魅力——手把手带你实现最经典的冒泡排序

初探算法的魅力——手把手带你实现最经典的冒泡排序

作者头像
枫亭湖区
发布2025-11-13 09:16:01
发布2025-11-13 09:16:01
70
举报

之前我们系统学习了指针,这一章我们挑战一下指针编程项目中的————冒泡排序 并尝试利用冒泡排序实现qsort函数


冒泡排序是一种简单的排序算法。它重复地“遍历”要排序的数列,一次比较两个相邻的元素,如果它们的顺序错误(例如,从小到大排序时,前一个比后一个大),就把它们交换过来。

请添加图片描述
请添加图片描述

野蛮生长版:基础版(理解概念)

代码语言:javascript
复制
#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 → 提前终止!

代码思路解析
  • 首先我们需要判断数组是否经过交换,这里我们用到布尔类型
代码语言:javascript
复制
//定义头文件
#include <stdio.h>
#include <stdbool.h>
  • 第一轮需要比较所有元素,所以边界设为数组最后一个索引
代码语言:javascript
复制
int lastSwapIndex = n - 1; // 初始化最后交换位置为数组末尾
  • 我们定义一个布尔标志位,检测是否发生交换
代码语言:javascript
复制
bool hasSwapped = false;
  • 我们观察基础版的冒泡排序函数
代码语言:javascript
复制
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函数里面创建一个临时变量,并修改一下循环条件
代码语言:javascript
复制
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");
}
  • 冒泡排序发生在循环里面,在外层循环创建布尔类型
代码语言:javascript
复制
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条件判断里面写上:
代码语言:javascript
复制
hasSwapped = true;

在上面函数经过一次大循环后,数组里面最大的数已经被退到最右边,所以我们就把边界定义为倒数第二位

  • 更新边界信息:

我们通过创建临时变量来存储边界,避免和lastSwapIndex产生冲突

代码语言:javascript
复制
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 明显在这个范围之外,不会与任何有效索引混淆

  • 只有真正交换过才能才更新
代码语言:javascript
复制
if (currentSwapBoundary != -1) {  // 只有真正交换过才更新
    lastSwapIndex = currentSwapBoundary;
}
  • 如果没有这个检查或者为其它值:
代码语言:javascript
复制
// 假设本轮没有发生任何交换
// currentSwapBoundary 保持初始值

// 如果是随机初始值:
lastSwapIndex = 随机值;  // 可能导致数组越界!

// 如果是0:
lastSwapIndex = 0;  // 错误地将边界设为0,下轮无法正确比较
代码语言:javascript
复制
// 其他可能但不好的选择:
int currentSwapBoundary = 0;     // 可能被误认为是有效位置
int currentSwapBoundary = n;     // 可能越界,语义不清晰
int currentSwapBoundary = -999;  // 魔数,不够直观

最后的代码为

代码语言:javascript
复制
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);
    }
}

完整代码展示
代码语言:javascript
复制
#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)鸡尾酒排序

调酒师调制鸡尾酒时,会左右摇晃调酒器 鸡尾酒排序也是从左到右、从右到左交替进行的排序过程

代码语言:javascript
复制
从左到右:→ → → → → 
从右到左:← ← ← ← ← 
从左到右:→ → → → → 
这种双向交替的运动方式很像调鸡尾酒时的摇晃动作
代码语言:javascript
复制
#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)函数原型
代码语言:javascript
复制
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
代码语言:javascript
复制
int compare_ints(const void *a, const void *b) {
    return (*(int *)a - *(int *)b);  // 升序
}

完整代码
代码语言:javascript
复制
#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;
}

我们已经将冒泡排序算法讲解透彻,重点掌握其循环与数组比较的知识点

如果你觉得对你有帮助,请给文章来波三连哦

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-10-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 野蛮生长版:基础版(理解概念)
  • 效率性能优:优化版
    • 1)标志位 + 动态边界
      • 代码可视化执行
      • 代码思路解析
      • 完整代码展示
    • 2)鸡尾酒排序
    • 3)利用冒泡排序实现qsort函数
      • qsort函数复习
      • 完整代码
  • 我们已经将冒泡排序算法讲解透彻,重点掌握其循环与数组比较的知识点
  • 如果你觉得对你有帮助,请给文章来波三连哦
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档