什么是归并排序
归并排序是一种分治算法。它将数组不断地分成两半,对每一半进行排序,然后再将排序好的两半合并起来。通过不断重复这个过程,最终得到完全排序的数组。
归并排序的注意点:
- 空间复杂度:归并排序需要额外的与原始数组大小相同的临时空间来进行合并操作,这可能在一些内存受限的场景下需要特别注意。
- 递归深度:在处理大规模数据时,可能会导致较深的递归调用,需要关注栈空间的使用情况,避免栈溢出。
- 边界条件处理:在分割和合并过程中,要确保正确处理各种边界情况,如空数组、只有一个元素的数组等。
- 性能优化:虽然归并排序性能较为稳定,但在实际应用中可以考虑根据具体情况进行一些优化,比如更高效的合并操作等。
归并排序
题目描述
运行代码
#include <iostream>
using namespace std;
const int N = 1e5 + 5;
int a[N], t[N];
void merge_sort(int q[], int l, int r)
{
if (l >= r) return;
int mid = l + r >> 1;
merge_sort(q, l, mid);
merge_sort(q, mid + 1, r);
int k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r)
if (q[i] <= q[j])
t[k++] = q[i++];
else t[k++] = q[j++];
while (i <= mid)
t[k++] = q[i++];
while (j <= r)
t[k++] = q[j++];
for (i = l, j = 0; i <= r; i++, j++)
q[i] = t[j];
}
int main()
{
int n;
cin>>n;
for (int i = 0; i < n; i++)
cin>>a[i];
merge_sort(a, 0, n - 1);
for (int i = 0; i < n; i++)
cout<<a[i]<<" ";
return 0;
}
代码思路
-
定义常量:
N = 1e5 + 5
用于确定数组的最大长度,以适应最多10万级别的数据。 -
归并排序函数
merge_sort
:- 输入参数为待排序数组
q
,以及排序区间的左右边界索引l
和r
。 - 当
l >= r
时,说明区间内元素个数小于或等于1,无需排序,直接返回。 - 计算中间点
mid
,递归地对左半区间[l, mid]
和右半区间[mid+1, r]
进行排序。 - 使用临时数组
t[]
进行合并操作:设立两个指针i
和j
分别指向左右子序列的起始位置,比较两个序列中的元素,将较小的元素放入t[]
中,直至一个序列遍历完。接着,将剩余未遍历完的序列的剩余元素全部拷贝到t[]
中。 - 最后,将
t[]
中的元素拷贝回原数组q[]
中。
- 输入参数为待排序数组
-
主函数:读取数组长度
n
和每个元素的值。调用merge_sort
函数对数组进行排序。输出排序后的数组。
优化建议
-
减少临时数组的频繁创建:在当前代码中,每次递归调用
merge_sort
时都会创建和销毁临时数组t[]
,这在大数组排序时会消耗大量时间和空间。可以在函数外部定义一个全局的临时数组,避免每次递归都创建新数组。 -
非递归实现:对于极深的递归调用可能导致栈溢出的问题,可以考虑使用迭代而非递归的方式来实现归并排序,通过栈来管理待合并的区间,减少递归调用的深度。
-
尾递归优化:虽然C++标准没有强制规定编译器必须进行尾递归优化,但在理论上,如果编译器支持,可以通过修改递归调用的方式(在函数末尾调用自身,并且不需要在调用之后做其他操作)来实现。不过,归并排序的自然逻辑不太容易直接应用尾递归。
-
并行化:对于非常大的数据集,可以考虑将数组分割成多个部分,使用多线程或多进程分别排序,最后合并结果。这要求对归并过程进行适当的同步控制,以确保正确性。
其他代码(分治)
#include<iostream>
using namespace std;
const int N=1e5+5;
int a[N],temp[N];
void sortt(int a[],int l,int r){
if (l >= r) { // 添加边界检查
return;
}
int mid = (l + r) >> 1;
sortt(a, l, mid);
sortt(a, mid + 1, r);
int k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r) {
if (a[i] <= a[j]) {
temp[k++] = a[i++];
} else {
temp[k++] = a[j++];
}
}
while (i <= mid) {
temp[k++] = a[i++];
}
while (j <= r) {
temp[k++] = a[j++];
}
for (i = l, j = 0; i <= r; i++, j++) {
a[i] = temp[j];
}
return;
}
int main() {
int n;
if (cin >> n) { // 检查输入是否成功
for (int i = 0; i < n; i++) {
cin >> a[i];
}
sortt(a, 0, n - 1);
for (int i = 0; i < n; i++) {
cout << a[i] <<'';
}
}
return 0;
}
代码思路
整体流程:
- 定义了一个常量
N
表示数组可能的最大长度。 - 在
sortt
函数中实现归并排序的核心逻辑。 - 在
main
函数中获取输入的数组长度n
,并读取数组元素,然后调用sortt
函数进行排序,最后输出排序后的结果。
归并排序的具体思路:
sortt
函数通过不断地将数组区间一分为二,对分割后的两部分分别递归调用自身进行排序。- 在合并阶段,通过两个指针
i
和j
分别遍历左右已排序的两部分。比较两个指针所指向元素的大小,将较小的元素放入临时数组temp
中,并相应地移动指针。 - 当左右两部分中的某一部分遍历完后,将另一部分剩余元素直接添加到临时数组后面。
- 最后将临时数组中的排序好的元素复制回原数组对应的位置。