归并排序是分治思想的运用。
将待排序元素分成大小大致相同的2个子集合,分别对2个子集合进行排序,最终将排好序的子集合合并成为所要求的排好序的集合。
归并排序(Merge Sort) 是分治思想的经典应用。其核心理念是:
核心口诀:分、治、合!
// 归并排序
void MergeSort(Type a[], int left, int right)
{
if (left<right) //至少有2个元素
{
int i=(left+right)/2; // 取中点
mergeSort(a, left, i); // 处理左半边
mergeSort(a, i+1, right); // 处理右半边
merge(a, b, left, i, right); // 合并到数组b
copy(a, b, left, right); // 复制回数组a
}
}
// 确定分界点
int mid = (l + r) >> 1; // 求中点坐标
小技巧:使用右移运算符
>>
代替除法,计算更高效
// 递归处理子问题
mergeSort(a, l, mid); // 处理半左区间
mergeSort(a, mid + 1, r); // 处理半右区间
递归精髓:将大问题不断拆分,直到问题足够小(通常是1-2个元素)
// 合并子问题
int i = l, j = mid + 1;
int k = 0;
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++];
合并技巧:总是选择两个子数组中最小的元素
经过步骤2对左右区间进行递归处理后,左右两个子区间都是有序的,这里通过两个指针
i
和j
分别遍历左右子区间,比较当前元素大小,把较小的元素依次放入临时数组temp
。最后,把左右子区间剩余的元素也都放入temp。
故扫尾
时直接将剩余的元素加到中间数组后
// 将合并后的数组赋值给原数组
for (int i = l, j = 0; i <= r; ++i, ++j)
a[i] = temp[j];
}
注意:这里复制的赋值是从递归的起始位置
l
开始的,因为每次排序只是针对l
到r
这个范围的区间进行的
#include
using namespace std;
const int N = 100005; // 最大n的范围
int n;
int a[N], temp[N]; // 待排序数组和中间数组
void mergeSort(int a[], int l, int r)
{
// 递归结束条件:到达原子问题
if (l >= r)
return;
// 确定分界点
int mid = (l + r) >> 1;
// 递归处理子问题
mergeSort(a, l, mid);
mergeSort(a, mid + 1, r);
// 合并子问题
int i = l, j = mid + 1;
int k = 0;
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++];
// 将合并后的数组赋值给原数组: 注意这里的赋值是从递归的起始位置l开始的,因为每次排序只是针对l到r这个范围的区间进行的
for (int i = l, j = 0; i <= r; ++i, ++j)
a[i] = temp[j];
}
int main()
{
cin >> n;
for (int i = 0; i < n; ++i)
cin >> a[i];
mergeSort(a, 0, n - 1); // 归并排序:从0开始,到n-1结束
for (int i = 0; i < n; ++i)
{
cout << a[i] << ' ';
}
return 0;
}
最好/平均/最坏: O ( n l o g n ) O(n log n) O(nlogn)
稳定且高效的排序算法
O ( n ) O(n) O(n):需要额外的临时数组空间
这样,我们就完成了对归并排序的学习啦!希望大家都能掌握这个强大的算法。
如果在学习过程中有任何问题,欢迎在评论区留言交流哦!