C语言排序问题详解:从冒泡到快排的完整指南

1. 引言

排序是C语言学习中最基础也最重要的算法之一。无论是处理学生成绩、商品价格还是任何需要有序展示的数据,排序算法都扮演着核心角色。本文将带你系统梳理C语言中常见的排序算法,从原理到代码实现,帮助你彻底掌握排序问题。

2. 冒泡排序(Bubble Sort)

2.1 原理

冒泡排序通过重复遍历待排序序列,依次比较相邻两个元素,如果顺序错误就交换它们。每一轮遍历都会把当前未排序部分的最大值"冒泡"到末尾。

2.2 代码实现

c 复制代码
#include <stdio.h>

void bubbleSort(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        // 每轮遍历,将最大值冒泡到末尾
        for (int j = 0; j < n - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                // 交换相邻元素
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

int main() {
    int arr[] = {64, 34, 25, 12, 22, 11, 90};
    int n = sizeof(arr) / sizeof(arr[0]);
    
    bubbleSort(arr, n);
    
    printf("排序后的数组:\n");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

2.3 优化技巧

可以增加一个标志位,如果某一轮没有发生任何交换,说明序列已经有序,提前结束排序:

c 复制代码
void bubbleSortOptimized(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int swapped = 0;  // 标志位
        for (int j = 0; j < n - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                swapped = 1;
            }
        }
        // 如果没有交换,说明已有序
        if (swapped == 0) break;
    }
}

3. 选择排序(Selection Sort)

3.1 原理

选择排序每一轮从未排序部分选出最小值,放到已排序部分的末尾。它的交换次数比冒泡排序少,但比较次数相同。

3.2 代码实现

c 复制代码
void selectionSort(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int minIndex = i;  // 记录最小值的下标
        for (int j = i + 1; j < n; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;
            }
        }
        // 将最小值交换到当前位置
        if (minIndex != i) {
            int temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
    }
}

4. 插入排序(Insertion Sort)

4.1 原理

插入排序像整理扑克牌一样,将每个元素插入到前面已排序序列的正确位置。对于近乎有序的数据,插入排序效率非常高。

4.2 代码实现

c 复制代码
void insertionSort(int arr[], int n) {
    for (int i = 1; i < n; i++) {
        int key = arr[i];  // 当前要插入的元素
        int j = i - 1;
        
        // 将比 key 大的元素向后移动
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key;  // 插入到正确位置
    }
}

5. 快速排序(Quick Sort)

5.1 原理

快速排序采用分治策略:选择一个基准元素(pivot),将数组分成两部分,左边都小于基准,右边都大于基准,然后递归地对两部分排序。它是实际应用中最常用的排序算法之一。

5.2 代码实现

c 复制代码
// 分区函数:返回基准元素的最终位置
int partition(int arr[], int low, int high) {
    int pivot = arr[high];  // 选择最后一个元素作为基准
    int i = low - 1;        // i 指向小于基准的区域的末尾
    
    for (int j = low; j < high; j++) {
        if (arr[j] < pivot) {
            i++;
            // 交换 arr[i] 和 arr[j]
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }
    // 将基准放到正确位置
    int temp = arr[i + 1];
    arr[i + 1] = arr[high];
    arr[high] = temp;
    
    return i + 1;
}

void quickSort(int arr[], int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        quickSort(arr, low, pi - 1);   // 递归排序左半部分
        quickSort(arr, pi + 1, high);  // 递归排序右半部分
    }
}

6. 归并排序(Merge Sort)

6.1 原理

归并排序同样采用分治策略:将数组不断对半拆分,直到每个子数组只有一个元素,然后两两合并成有序数组。它的时间复杂度稳定为 O(n log n),适合大数据量排序。

6.2 代码实现

c 复制代码
// 合并两个有序子数组
void merge(int arr[], int left, int mid, int right) {
    int n1 = mid - left + 1;
    int n2 = right - mid;
    
    // 创建临时数组
    int L[n1], R[n2];
    
    for (int i = 0; i < n1; i++) L[i] = arr[left + i];
    for (int j = 0; j < n2; j++) R[j] = arr[mid + 1 + j];
    
    int i = 0, j = 0, k = left;
    
    // 合并两个有序数组
    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) {
            arr[k] = L[i];
            i++;
        } else {
            arr[k] = R[j];
            j++;
        }
        k++;
    }
    
    // 复制剩余元素
    while (i < n1) {
        arr[k] = L[i];
        i++;
        k++;
    }
    while (j < n2) {
        arr[k] = R[j];
        j++;
        k++;
    }
}

void mergeSort(int arr[], int left, int right) {
    if (left < right) {
        int mid = left + (right - left) / 2;
        
        mergeSort(arr, left, mid);      // 排序左半部分
        mergeSort(arr, mid + 1, right); // 排序右半部分
        merge(arr, left, mid, right);   // 合并
    }
}

7. 排序算法对比

算法 平均时间复杂度 最坏时间复杂度 空间复杂度 稳定性
冒泡排序 O(n²) O(n²) O(1) 稳定
选择排序 O(n²) O(n²) O(1) 不稳定
插入排序 O(n²) O(n²) O(1) 稳定
快速排序 O(n log n) O(n²) O(log n) 不稳定
归并排序 O(n log n) O(n log n) O(n) 稳定

8. 实战:完整排序程序

下面是一个综合示例,演示如何用函数指针灵活切换不同的排序算法:

c 复制代码
#include <stdio.h>
#include <stdlib.h>

// 各种排序函数声明(实现见上文)
void bubbleSort(int arr[], int n);
void selectionSort(int arr[], int n);
void insertionSort(int arr[], int n);
void quickSort(int arr[], int low, int high);
void mergeSort(int arr[], int left, int right);

// 打印数组
void printArray(int arr[], int n) {
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int arr[] = {64, 34, 25, 12, 22, 11, 90};
    int n = sizeof(arr) / sizeof(arr[0]);
    
    printf("原始数组:\n");
    printArray(arr, n);
    
    // 使用快速排序
    quickSort(arr, 0, n - 1);
    printf("快速排序结果:\n");
    printArray(arr, n);
    
    return 0;
}

9. 常见面试问题

9.1 什么时候用哪种排序?

  • 数据量小(<50):插入排序或冒泡排序
  • 数据量中等:快速排序(平均性能最好)
  • 数据量很大且要求稳定:归并排序
  • 数据近乎有序:插入排序(接近 O(n))

9.2 如何判断排序算法的稳定性?

稳定性指相同值的元素在排序后保持原有相对顺序。冒泡、插入、归并是稳定的;选择、快速是不稳定的。

10. 总结

本文系统介绍了C语言中五种经典排序算法:冒泡、选择、插入、快速和归并。每种算法都有其适用场景:

  • 冒泡排序:简单直观,适合教学和小规模数据
  • 选择排序:交换次数少,适合交换代价高的场景
  • 插入排序:对近乎有序的数据效率极高
  • 快速排序:综合性能最优,实际应用最广
  • 归并排序:稳定且时间复杂度有保证,适合大数据量

建议初学者先吃透冒泡和插入排序,再逐步掌握快速排序和归并排序。多动手写代码、多调试,才能真正理解每种算法的精髓。

相关推荐
l1t1 小时前
DeepSeek总结的PIVCO-Huffman编码性能优化
数据结构·c++·算法
-dzk-1 小时前
【贪心算法】LC 763.划分字母区间
算法·贪心算法
奇牙coding1 小时前
Codex C接 配置教程:的 字段从迁移时必须写完整后缀,填旧值或省略 会静默回退默认模型
java·c语言·数据库·ai
拾饵9421 小时前
第六周第二节那个RAG工业项目遇到的问题
算法
强壮的CAT1 小时前
VINS-Fusion 移植 ROS2 Jazzy 踩坑(一):环境与编译
人工智能·算法·机器人·自动驾驶
Omics Pro1 小时前
计算虚拟扰动:网络毒理+虚拟敲除
数据库·人工智能·算法·机器学习·自然语言处理
yyy(十一月限定版)1 小时前
CF2137D Replace with Occurrences
算法
谢亮_vipxieliang1 小时前
GC 入门:G1 与 ZGC 怎么选
java·jvm·算法
兔小盈1 小时前
有效三角形个数与和为s的两个数字
算法·双指针