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语言中五种经典排序算法:冒泡、选择、插入、快速和归并。每种算法都有其适用场景:
- 冒泡排序:简单直观,适合教学和小规模数据
- 选择排序:交换次数少,适合交换代价高的场景
- 插入排序:对近乎有序的数据效率极高
- 快速排序:综合性能最优,实际应用最广
- 归并排序:稳定且时间复杂度有保证,适合大数据量
建议初学者先吃透冒泡和插入排序,再逐步掌握快速排序和归并排序。多动手写代码、多调试,才能真正理解每种算法的精髓。