8.5.归并排序和基数排序
1. 归并排序
核心思想:
-
分解,将待排序序列递归地分成两个子序列,直到每个子序列仅含有一个元素(此时天然有序)。
-
将两个有序子序列合并成为一个更大的有序序列,最终得到完整的有序序列。
实现代码(C语言):
- 递归实现归并排序的代码
void Merge(int arr[], int low, int mid, int high) {
int i = low, j = mid + 1, k = 0;
int *temp = (int *)malloc((high - low + 1) * sizeof(int)); // 辅助数组
while (i <= mid && j <= high) {
if (arr[i] <= arr[j]) temp[k++] = arr[i++]; // 保证稳定性
else temp[k++] = arr[j++];
}
// 处理剩余元素
while (i <= mid) temp[k++] = arr[i++];
while (j <= high) temp[k++] = arr[j++];
// 将temp数组拷贝回原数组
for (i = low, k = 0; i <= high; i++, k++) {
arr[i] = temp[k];
}
free(temp);
}
void MSort(int arr[], int low, int high) {
if (low < high) {
int mid = (low + high) / 2;
MSort(arr, low, mid); // 递归左半部分
MSort(arr, mid + 1, high); // 递归右半部分
Merge(arr, low, mid, high); // 合并
}
}
void MergeSort(int arr[], int n) {
MSort(arr, 0, n - 1);
}
- 非递归实现的归并排序
// 合并两个有序子数组
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 n) {
for (int step = 1; step < n; step *= 2) { // 子数组长度从1开始翻倍
for (int left = 0; left < n - 1; left += 2 * step) {
int mid = left + step - 1;
int right = (left + 2 * step - 1) < (n - 1) ? (left + 2 * step - 1) : (n - 1);
Merge(arr, left, mid, right); // 合并相邻子数组
}
}
}
算法特性:
-
时间复杂度为\(O(n log_{2}n)\),空间复杂度为\(O(n)\)。
-
归并排序具有稳定性,可以用于需要保持关键字原始顺序的场景。
-
可以用于大数据排序(外部排序,具体见\(\rightarrow\)8.7.外部排序),链表排序(无需随机访问,合并操作非常适合链表结构)。
-
不适用于有内存限制的问题(需要\(O(n)\))的辅助空间,也不适用于实时系统(递归调用和空间开销较大)。
-
虽然归并排序和快速排序的时间复杂度相同,但是归并排序的常数因子较大,实际运行可能慢于快速排序。
-
归并排序适用于顺序存储和链式存储的线性表。
2. 基数排序(链式)
核心思想:
-
按位排序,从最低位(LSD)或者最高位(MSD)开始,根据当前位的值将数据分配到对应的链式桶中。
-
链式桶,每个桶是一个链表,存储相同位值的元素,避免数据移动开销。
-
顺序收集,按桶的顺序(如\(0\rightarrow9\))将链表元素合并,形成新一轮待排序序列。
代码实现(C语言):
typedef struct Node {
int data;
struct Node *next;
} Node;
// 获取数字的第d位(从0开始,个位为0)
int GetDigit(int num, int d) {
for (int i = 0; i < d; i++) num /= 10;
return num % 10;
}
// 链式基数排序
void RadixSort(int arr[], int n) {
// 1. 初始化桶(10个链表)
Node *buckets[10] = {NULL};
Node *tails[10] = {NULL}; // 记录每个链表的尾部
// 2. 计算最大位数d
int max_val = arr[0];
for (int i = 1; i < n; i++)
if (arr[i] > max_val) max_val = arr[i];
int d = 0;
while (max_val > 0) { max_val /= 10; d++; }
// 3. 按位分配与收集
for (int exp = 0; exp < d; exp++) {
// 分配阶段:将数据插入对应桶的链表尾部
for (int i = 0; i < n; i++) {
int digit = get_digit(arr[i], exp);
Node *new_node = (Node *)malloc(sizeof(Node));
new_node->data = arr[i];
new_node->next = NULL;
if (buckets[digit] == NULL) {
buckets[digit] = new_node;
tails[digit] = new_node;
} else {
tails[digit]->next = new_node;
tails[digit] = new_node;
}
}
// 收集阶段:按桶顺序合并链表
int idx = 0;
for (int i = 0; i < 10; i++) {
Node *p = buckets[i];
while (p != NULL) {
arr[idx++] = p->data;
Node *temp = p;
p = p->next;
free(temp); // 释放节点
}
buckets[i] = NULL; // 清空桶
}
}
}
算法特性:
-
时间复杂度为\(O(d * (n + k))\),空间复杂度为O(n + k)。
-
时间复杂度较低,稳定排序,非比较型算法,不是原地排序。
-
适用于整数排序,字符串排序,多关键字排序等。
-
基数排序适用于顺序存储和链式存储的线性表。
常见问题:
-
链式基数排序为何适合外部排序?
- 链表结构支持分块存储,适合磁盘顺序读写(减少随机访问)。
-
如何处理负数?
- 所有数字减去最小值,排序后还原。
-
如何优化空间?
- 使用静态链表,避免频繁动态内存分配
- 使用静态链表,避免频繁动态内存分配

浙公网安备 33010602011771号