8645 归并排序(非递归算法)
时间限制:1000MS 代码长度限制:10KB
提交次数:2398 通过次数:1192
题型: 编程题 语言: G++;GCC
Description
用函数实现归并排序(非递归算法),并输出每趟排序的结果
输入格式
第一行:键盘输入待排序关键的个数n
第二行:输入n个待排序关键字,用空格分隔数据
输出格式
每行输出每趟排序的结果,数据之间用一个空格分隔
输入样例
10
5 4 8 0 9 3 2 6 7 1
输出样例
4 5 0 8 3 9 2 6 1 7
0 4 5 8 2 3 6 9 1 7
0 2 3 4 5 6 8 9 1 7
0 1 2 3 4 5 6 7 8 9
#include <iostream>
#include <vector>
using namespace std;
// 合并两个有序区间
void merge(vector<int>& a, vector<int>& temp, int left, int mid, int right) {
int i = left, j = mid, k = left;
while (i < mid && j < right) {
if (a[i] <= a[j]) temp[k++] = a[i++];
else temp[k++] = a[j++];
}
while (i < mid) temp[k++] = a[i++];
while (j < right) temp[k++] = a[j++];
for (int l = left; l < right; ++l) {
a[l] = temp[l];
}
}
// 非递归归并排序
void mergeSort(vector<int>& a) {
int n = a.size();
vector<int> temp(n);
for (int len = 1; len < n; len *= 2) {
for (int i = 0; i < n; i += 2 * len) {
int left = i;
int mid = min(i + len, n);
int right = min(i + 2 * len, n);
merge(a, temp, left, mid, right);
}
// 输出当前一趟排序后的结果
for (int i = 0; i < n; ++i) {
cout << a[i];
if (i != n - 1) cout << " ";
}
cout << endl;
}
}
int main() {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
mergeSort(a);
return 0;
}
✅ 归并排序原理详解
归并排序(Merge Sort)是一种分治法(Divide and Conquer)思想实现的稳定排序算法,主要分为两个阶段:
🧠 核心思想:分而治之
归并排序将一个数组分成若干个子数组,每个子数组是有序的,然后再将这些有序的子数组两两合并成一个有序的大数组。
🔁 工作过程(递归版):
-
分解(Divide):
-
把数组不断二分,直到每个子数组只包含一个元素。
-
-
合并(Conquer):
-
将两个有序的子数组合并成一个有序数组。
-
一直合并直到最终合并成完整有序数组。
-
🔁 非递归(迭代)归并排序的流程:
非递归版本的归并排序是**自底向上(Bottom-Up)**的实现,不使用递归,而是从小段逐步向大段合并。
举个例子:
数组:[5, 4, 8, 0, 9, 3, 2, 6, 7, 1]
第一趟:每组2个元素(段长=1)
-
[5,4] → [4,5], [8,0] → [0,8],...
-
合并后:
[4,5,0,8,3,9,2,6,1,7]
第二趟:每组4个元素(段长=2)
-
[4,5,0,8] → [0,4,5,8], [3,9,2,6] → [2,3,6,9] ...
-
合并后:
[0,4,5,8,2,3,6,9,1,7]
第三趟:每组8个元素(段长=4)
-
[0,4,5,8,2,3,6,9] → [0,2,3,4,5,6,8,9]
-
合并后:
[0,2,3,4,5,6,8,9,1,7]
第四趟:整个数组(段长=8)
-
[0,2,3,4,5,6,8,9,1,7] → [0,1,2,3,4,5,6,7,8,9]
for (int len = 1; len < n; len *= 2) {
for (int i = 0; i < n; i += 2 * len) {
int left = i;
int mid = min(i + len, n);
int right = min(i + 2 * len, n);
merge(a, temp, left, mid, right);
}什么意思
这段代码是归并排序的非递归实现中的核心部分,用于控制归并的每一“趟”,我们逐行分析它的作用。
💡 总体目标:
这段代码用来控制归并排序的分段归并过程,其中:
for (int len = 1; len < n; len *= 2)
表示:每一趟归并中,每个小段的长度是 len
,并且每次翻倍(1 → 2 → 4 → 8 ...)
🔍 逐行解析:
✅ 外层循环:
for (int len = 1; len < n; len *= 2)
-
len
表示当前归并的每段的长度 -
初始为 1,表示每个元素看作一个长度为1的有序段
-
每次乘2,下一趟就合并更大的段
✅ 内层循环:
for (int i = 0; i < n; i += 2 * len)
-
遍历整个数组,把每两个
len
长的段合并起来 -
i
是当前要合并的左段的起始位置 -
步长为
2 * len
,因为你一次合并两段
✅ 获取左右段的范围:
int left = i;
int mid = min(i + len, n);
int right = min(i + 2 * len, n);
-
left
:当前合并段的起点(左段起点) -
mid
:右段起点(也是左段终点) -
right
:右段终点
使用 min(..., n)
是为了防止数组越界。
例如:数组长度为 10
,len = 4
时,
-
i = 8
时:mid = min(8 + 4, 10) = 10
,right = min(8 + 8, 10) = 10
-
所以
merge(8, 10, 10)
,不会越界
✅ 调用归并函数:
merge(a, temp, left, mid, right);
将 a[left..mid-1]
和 a[mid..right-1]
这两个有序段合并到 temp
中,再写回 a
📌 举个小例子说明:
假设数组为 [5, 4, 3, 2, 1]
,长度 n = 5
第1趟:len = 1(相邻两个数合并)
归并对:[5,4]
,[3,2]
,[1]
(最后剩一个不用归并)
第2趟:len = 2(两个两位段合并)
归并对:[4,5,2,3]
,[1]
第3趟:len = 4
归并对:[2,3,4,5,1]
✅ 总结:
这段代码:
-
实现非递归归并排序
-
每一趟处理数组中长度为
len
的若干段落 -
两两合并这些段
-
最终得到一个整体有序的数组
如果你希望添加“每一趟打印当前数组”的输出,也可以放在外层 len
循环内部。需要演示我也可以加上。