动画详解十大经典排序算法 - C 语言版
概述
一
冒泡排序
算法原理
动图演示
代码实现
void bubble_sort(int arr[], int n) {
int i, j;
for (i = 0; i < n - 1; i++) {
for (j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr, j, j+1);
}
}
}
}
算法分析
void bubble_sort_quicker(int arr[], int n) {
int i, j, flag;
for (i = 0; i < n - 1; i++) {
flag = 0;
for (j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr, j, j+1);
flag = 1;
}
}
if (!flag) return;
}
}
二
快速排序
基本思想
动图演示
代码实现
/* 选取序列的第一个元素作为基准 */
int select_pivot(int arr[], int low) {
return arr[low];
}
void quick_sort(int arr[], int low, int high) {
int i, j, pivot;
if (low >= high) return;
pivot = select_pivot(arr, low);
i = low;
j = high;
while (i != j) {
while (arr[j] >= pivot && i < j) j--;
while (arr[i] <= pivot && i < j) i++;
if (i < j) swap(arr, i, j);
}
arr[low] = arr[i];
arr[i] = pivot;
quick_sort(arr, low, i - 1);
quick_sort(arr, i + 1, high);
}
算法分析
/* 随机选择基准的位置,区间在 low 和 high 之间 */
int select_pivot_random(int arr[], int low, int high) {
srand((unsigned)time(NULL));
int pivot = rand()%(high - low) + low;
swap(arr, pivot, low);
return arr[low];
}
/* 取起始位置、中间位置、末尾位置指向的元素三者的中间值作为基准 */
int select_pivot_median_of_three(int arr[], int low, int high) {
// 计算数组中间的元素的下标
int mid = low + ((high - low) >> 1);
// 排序,使 arr[mid] <= arr[low] <= arr[high]
if (arr[mid] > arr[high]) swap(arr, mid, high);
if (arr[low] > arr[high]) swap(arr, low, high);
if (arr[mid] > arr[low]) swap(arr, low, mid);
// 使用 low 位置的元素作为基准
return arr[low];
}
三
插入排序
基本思想
动图演示
代码实现
void insertion_sort(int arr[], int n) {
int i, j, temp;
for (i = 1; i < n; i++) {
temp = arr[i];
for (j = i; j > 0 && arr[j - 1] > temp; j--)
arr[j] = arr[j - 1];
arr[j] = temp;
}
}
算法分析
四
希尔排序
基本思想
动图演示
代码实现
void shell_sort_split_half(int arr[], int n) {
int i, j, dk, temp;
for (dk = n >> 1; dk > 0; dk = dk >> 1) {
for (i = dk; i < n; i++) {
temp = arr[i];
for (j = i - dk; j >= 0 && arr[j] > temp; j -= dk)
arr[j + dk] = arr[j];
arr[j + dk] = temp;
}
}
}
void shell_insert(int arr[], int n, int dk) {
int i, j, temp;
for (i = dk; i < n; i += dk) {
temp = arr[i];
j = i - dk;
while (j >= 0 && temp < arr[j]) {
arr[j + dk] = arr[j];
j -= dk;
}
arr[j + dk] = temp;
}
}
void shell_sort(int arr[], int n, int dlta[], int t) {
int k;
for (k = 0; k < t; ++k) {
// 一趟增量为 dlta[k] 的插入排序
shell_insert(arr, n, dlta[k]);
}
}
算法分析
五
选择排序
算法步骤
动图演示
代码实现
void selection_sort(int arr[], int n) {
int i, j;
for (i = 0; i < n - 1; i++) {
int min = i;
for (j = i + 1; j < n; j++) {
if (arr[j] < arr[min])
min = j;
}
swap(arr, min, i);
}
}
算法分析
六
堆排序
基本思想
动图演示
代码实现
void heapify(int tree[], int n, int i) {
// n 表示序列长度,i 表示父节点下标
if (i >= n) return;
// 左侧子节点下标
int left = 2 * i + 1;
// 右侧子节点下标
int right = 2 * i + 2;
int max = i;
if (left < n && tree[left] > tree[max]) max = left;
if (right < n && tree[right] > tree[max]) max = right;
if (max != i) {
swap(tree, max, i);
heapify(tree, n, max);
}
}
void build_heap(int tree[], int n) {
// 树最后一个节点的下标
int last_node = n - 1;
// 最后一个节点对应的父节点下标
int parent = (last_node - 1) / 2;
int i;
for (i = parent; i >= 0; i--) {
heapify(tree, n, i);
}
}
void heap_sort(int tree[], int n) {
build_heap(tree, n);
int i;
for (i = n - 1; i >= 0; i--) {
// 将堆顶元素与最后一个元素交换
swap(tree, i, 0);
// 调整成大顶堆
heapify(tree, i, 0);
}
}
算法分析
此外,堆排序仅需一个记录大小供交换用的辅助存储空间。
七
归并排序
基本思想
动图演示
代码实现
/* 将 arr[L..M] 和 arr[M+1..R] 归并 */
void merge(int arr[], int L, int M, int R) {
int LEFT_SIZE = M - L + 1;
int RIGHT_SIZE = R - M;
int left[LEFT_SIZE];
int right[RIGHT_SIZE];
int i, j, k;
// 以 M 为分割线,把原数组分成左右子数组
for (i = L; i <= M; i++) left[i - L] = arr[i];
for (i = M + 1; i <= R; i++) right[i - M - 1] = arr[i];
// 再合并成一个有序数组(从两个序列中选出最小值依次插入)
i = 0; j = 0; k = L;
while (i < LEFT_SIZE && j < RIGHT_SIZE) arr[k++] = left[i] < right[j] ? left[i++] : right[j++];
while (i < LEFT_SIZE) arr[k++] = left[i++];
while (j < RIGHT_SIZE) arr[k++] = right[j++];
}
void merge_sort(int arr[], int L, int R) {
if (L == R) return;
// 将 arr[L..R] 平分为 arr[L..M] 和 arr[M+1..R]
int M = (L + R) / 2;
// 分别递归地将子序列排序为有序数列
merge_sort(arr, L, M);
merge_sort(arr, M + 1, R);
// 将两个排序后的子序列再归并到 arr
merge(arr, L, M, R);
}
算法分析
八
桶排序
算法步骤
动图演示
代码实现
void bucket_sort(int arr[], int n, int r) {
if (arr == NULL || r < 1) return;
// 根据最大/最小元素和桶数量,计算出每个桶对应的元素范围
int max = arr[0], min = arr[0];
int i, j;
for (i = 1; i < n; i++) {
if (max < arr[i]) max = arr[i];
if (min > arr[i]) min = arr[i];
}
int range = (max - min + 1) / r + 1;
// 建立桶对应的二维数组,一个桶里最多可能出现 n 个元素
int buckets[r][n];
memset(buckets, 0, sizeof(buckets));
int counts[r];
memset(counts, 0, sizeof(counts));
for (i = 0; i < n; i++) {
int k = (arr[i] - min) / range;
buckets[k][counts[k]++] = arr[i];
}
int index = 0;
for (i = 0; i < r; i++) {
// 分别对每个非空桶内数据进行排序,比如计数排序
if (counts[i] == 0) continue;
counting_sort(buckets[i], counts[i]);
// 拼接非空的桶内数据,得到最终的结果
for (j = 0; j < counts[i]; j++) {
arr[index++] = buckets[i][j];
}
}
}
算法分析
时间复杂度
-
n 次循环,每个数据装入桶 -
r 次循环,每个桶中的数据进行排序 (每个桶中平均有 n/r 个数据)
空间复杂度
九
计数排序
算法步骤
动图演示
代码实现
void counting_sort(int arr[], int n) {
if (arr == NULL) return;
// 定义辅助空间并初始化
int max = arr[0], min = arr[0];
int i;
for (i = 1; i < n; i++) {
if (max < arr[i]) max = arr[i];
if (min > arr[i]) min = arr[i];
}
int r = max - min + 1;
int C[r];
memset(C, 0, sizeof(C));
// 定义目标数组
int R[n];
// 统计每个元素出现的次数
for (i = 0; i < n; i++) C[arr[i] - min]++;
// 对辅助空间内数据进行计算
for (i = 1; i < r; i++) C[i] += C[i - 1];
// 反向填充目标数组
for (i = n - 1; i >= 0; i--) R[--C[arr[i] - min]] = arr[i];
// 目标数组里的结果重新赋值给 arr
for (i = 0; i < n; i++) arr[i] = R[i];
}
算法分析
时间复杂度
空间复杂度
十
基数排序
算法步骤
动图演示
代码实现
// 基数,范围0~9
#define RADIX 10
void radix_sort(int arr[], int n) {
// 获取最大值和最小值
int max = arr[0], min = arr[0];
int i, j, l;
for (i = 1; i < n; i++) {
if (max < arr[i]) max = arr[i];
if (min > arr[i]) min = arr[i];
}
// 假如序列中有负数,所有数加上一个常数,使序列中所有值变成正数
if (min < 0) {
for (i = 0; i < n; i++) arr[i] -= min;
max -= min;
}
// 获取最大值位数
int d = 0;
while (max > 0) {
max /= RADIX;
d ++;
}
int queue[RADIX][n];
memset(queue, 0, sizeof(queue));
int count[RADIX] = {0};
for (i = 0; i < d; i++) {
// 分配数据
for (j = 0; j < n; j++) {
int key = arr[j] % (int)pow(RADIX, i + 1) / (int)pow(RADIX, i);
queue[key][count[key]++] = arr[j];
}
// 收集数据
int c = 0;
for (j = 0; j < RADIX; j++) {
for (l = 0; l < count[j]; l++) {
arr[c++] = queue[j][l];
queue[j][l] = 0;
}
count[j] = 0;
}
}
// 假如序列中有负数,收集排序结果时再减去前面加上的常数
if (min < 0) {
for (i = 0; i < n; i++) arr[i] += min;
}
}