1. 冒泡排序 (Bubble Sort)
思想:
重复遍历数列,比较相邻元素,若逆序则交换,每一趟将未排序部分的最大值“浮”到末尾。若某一趟未发生交换,则提前结束。
复杂度与稳定性:
时间:最好 O(n),平均 O(n²),最坏 O(n²)
空间:O(1)
稳定性:稳定
void bubbleSort(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 tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
swapped = 1;
}
}
if (!swapped) break; // 已经有序
}
}
代码思路
外层循环控制趟数,最多 n-1 趟。
内层循环比较相邻元素,每一趟的终点随 i 减少(后面已排好)。
使用 swapped 标志,若内层没有交换说明已有序,提前结束。
交换时用临时变量确保稳定(相等不交换,相对顺序不变)。
2. 选择排序 (Selection Sort)
思想
每一轮从未排序部分选出最小元素,与未排序部分的第一个元素交换,将最小元素归位。
复杂度与稳定性
-
时间:最好/平均/最坏均为 O(n²)
-
空间:O(1)
-
稳定性:不稳定
适用场景
实现简单,不要求稳定性且数据规模较小时可用。
void selectionSort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
int minIdx = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIdx])
minIdx = j;
}
// 交换
int tmp = arr[i];
arr[i] = arr[minIdx];
arr[minIdx] = tmp;
}
}
代码思路
i 作为已排序部分的边界。
内层循环在 [i+1, n-1] 中寻找真正的最小值索引。
交换 arr[i] 与 arr[minIdx],将当前最小元素放到正确位置。
交换操作可能跨越中间元素,导致相同元素的前后顺序改变,故不稳定。
3. 插入排序 (Insertion Sort)
思想
将数组分为有序区和无序区,每次取无序区第一个元素,在有序区中从后向前扫描,找到合适位置插入(将较大元素右移)。
复杂度与稳定性
时间:最好 O(n),平均 O(n²),最坏 O(n²)
空间:O(1)
稳定性:稳定
适用场景
数据量小且基本有序时效率高,常作为高级排序的底层优化。
void insertionSort(int arr[], int n) {
for (int i = 1; i < n; i++) {
int key = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
代码思路
i=1 开始,认为第一个元素有序。
取出 arr[i] 暂存为 key,然后向前扫描有序区。
只要 arr[j] > key,就将 arr[j] 右移一位。
最终将 key 插入到 j+1 位置。由于只在 > key 时才移动,相等元素不会跨越,保持稳定。
4. 希尔排序 (Shell Sort)
思想
插入排序的改进,先按较大增量分组进行插入排序,使数组逐步有序,最后增量缩小至1,完成最终插入排序。
复杂度与稳定性
时间:依赖增量序列,平均 O(n^1.3) ~ O(n²)
空间:O(1)
稳定性:不稳定
适用场景
中等规模数据,对插入排序的直接优化,实际效率优于简单插入排序。
void shellSort(int arr[], int n) {
for (int gap = n / 2; gap > 0; gap /= 2) {
for (int i = gap; i < n; i++) {
int tmp = arr[i];
int j = i;
while (j >= gap && arr[j - gap] > tmp) {
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = tmp;
}
}
}
代码思路
外层循环控制增量 gap,通常从 n/2 开始,每次折半。
内层对每个子序列进行插入排序,子序列元素间隔为 gap。
对于每个 i,在组内向前比较并移动元素,类似于插入排序,但步长为 gap。
由于分组插入可能打乱相同元素在不同组中的顺序,故不稳定。
5. 归并排序 (Merge Sort)
思想
采用分治法,递归地将数组二分至单个元素,再将有序子数组合并成更大的有序数组。
复杂度与稳定性
时间:最好/平均/最坏均为 O(n log n)
空间:O(n)(临时数组)
稳定性:稳定
适用场景
数据量大且要求稳定排序,也常用于外部排序。
void merge(int arr[], int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
int* L = (int*)malloc(n1 * sizeof(int));
int* R = (int*)malloc(n2 * sizeof(int));
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++];
else arr[k++] = R[j++];
}
while (i < n1) arr[k++] = L[i++];
while (j < n2) arr[k++] = R[j++];
free(L); free(R);
}
void mergeSort(int arr[], int left, int right) {
if (left >= right) return;
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
代码思路
mergeSort 递归拆分:mid 取中间点,先对左半和右半分别排序,再调用 merge 合并。
merge 合并时,将左右子数组复制到临时数组 L 和 R,然后用双指针比较归并。
归并时 if (L[i] <= R[j]) 保证相等元素优先取左半,从而保持稳定性。
最终需要 free 临时数组,避免内存泄漏。
6. 快速排序 (Quick Sort)
思想
选取基准元素,通过分区将数组分为小于基准和大于基准的两部分,再递归排序两部分。
复杂度与稳定性
时间:最好/平均 O(n log n),最坏 O(n²)
空间:O(log n)(递归栈)
稳定性:不稳定
适用场景
实际应用中最常用的内排序,需注意基准选取避免退化。
void swap(int* a, int* b) {
int t = *a;
*a = *b;
*b = t;
}
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++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]);
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);
}
}
代码思路
partition 选择最右元素为基准 pivot。
i 维护小于等于 pivot 的元素边界,j 扫描整个区间,遇到小于等于基准的元素就与 i+1 位置交换。
最后将基准放到 i+1 位置,返回该索引。
递归排序左右子区间。由于交换会打乱相同元素的相对位置,故不稳定。
7. 堆排序 (Heap Sort)
思想
利用大顶堆进行选择排序:先建堆,然后反复交换堆顶(最大值)与末尾元素,再调整堆。
复杂度与稳定性
时间:最好/平均/最坏均为 O(n log n)
空间:O(1)
稳定性:不稳定
适用场景
原地排序且保证 O(n log n) 时间,适合对空间要求严格的场合。
void heapify(int arr[], int n, int i) {
int largest = i;
int left = 2 * i + 1;
int right = 2 * i + 2;
if (left < n && arr[left] > arr[largest])
largest = left;
if (right < n && arr[right] > arr[largest])
largest = right;
if (largest != i) {
swap(&arr[i], &arr[largest]);
heapify(arr, n, largest);
}
}
void heapSort(int arr[], int n) {
// 建堆
for (int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i);
// 一个个提取最大元素
for (int i = n - 1; i > 0; i--) {
swap(&arr[0], &arr[i]);
heapify(arr, i, 0);
}
}
代码思路
heapify 是下沉调整,确保以 i 为根的子树满足大顶堆性质。比较父节点与左右子节点,若子节点更大则交换并递归调整。
建堆从最后一个非叶节点 n/2-1 开始向前调用 heapify,使得整个数组变成大顶堆。
排序循环中,每次将堆顶(最大值)与当前末尾元素交换,然后缩小堆大小并对新堆顶执行下沉调整。
由于堆调整和交换可能打乱相同元素的顺序,故不稳定。
8. 计数排序 (Counting Sort)
思想
非比较排序,统计每个元素的出现次数,再按次序覆盖原数组,适用于整数且范围有限的场景。
复杂度与稳定性
-
时间:O(n + k),k 为值域范围
-
空间:O(k)
-
稳定性:可稳定(取决于写回方式,简单写回不稳定)
适用场景
数据范围集中、整数排序,当 k 不大时速度极快。
#include <stdlib.h>
#include <string.h>
void countingSort(int arr[], int n) {
if (n <= 0) return;
int min = arr[0], max = arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] < min) min = arr[i];
if (arr[i] > max) max = arr[i];
}
int range = max - min + 1;
int* count = (int*)malloc(range * sizeof(int));
memset(count, 0, range * sizeof(int));
// 计数
for (int i = 0; i < n; i++)
count[arr[i] - min]++;
// 写回原数组(简单写回,不稳定)
int idx = 0;
for (int i = 0; i < range; i++) {
while (count[i]--) {
arr[idx++] = i + min;
}
}
free(count);
}
代码思路
先找到最大值和最小值,确定值域范围 range。
分配计数数组并初始化为0,统计每个值出现的次数。
按值从小到大遍历计数数组,将元素按出现次数写回原数组。
写回时用 idx 递增覆盖,此方式不保留原顺序,故不稳定。若需稳定,可采用从后向前遍历原数组,借助前缀和定位填入位置。

5万+

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



