一、思路
我把归并排序大致分成两个部分:分 和 排序合并。
分
归并排序重要的思想就是“分治”,所谓“分治”就是把一个大问题化解成多个小问题解决。
比如要对一个数组进行归并排序,那么就可以把他从中间切断,变成两个数组。这两个数组分别排序,对这两个数组分别排序的时候,又可以继续划分。重复这个操作,直到不能再划分。
不能再划分时,就进行排序。不能再划分时,数组一定被分成了最小单元,就是只有一个元素。
单个元素就可以看成是一个有序数组,那么接下来的操作就是对有序数组进行合并。
排序合并
也就是将两个有序的数组进行组合排序。
首先,归并排序不是一个原地排序算法,他需要额外的空间存储排序好的数组。
开始我以为在数组中不能原地,链表中总可以吧?结果,在做有序链表的合并时,我企图原地完成,但出现一个问题就是:改变next指针后,当前加入有序链表的值并不是剩下未加入的最大的。也就是说,后面的节点进行比较后,如果他的值小于当前节点的值,就需要插入到该节点的前面,而对于单链表来说,想要插入值,必须知道前一个节点的位置。
来个链接感受一下
剑指 Offer 25. 合并两个排序的链表 - 力扣(LeetCode)
言归正传:
只要将两个数组的值依次比较,较小的哪个放入临时数组,最后再将临时数组赋值给原始数组对应的位置。
边界值条件
两个数组不一定长度一样,再比较过程中,如果一个数组结束了就跳出循环,将另一个数组剩下的部分赋值给临时数组。
由于我的代码整体上最终返回在原数组上操作后的结果,所以下标的判断很重要,一定要注意自己现在处理的是哪一部份数组,这样才不会出错。
二、代码
#include <bits/stdc++.h>
using namespace std;
// 合并函数:比较左右两边数组元素的大小进行排序重组
void merge(int a[], int left, int right, int addM)
{
int i = left;
int j = addM;
// 设置临时数组的长度
int num = right - left + 1;
// 设置临时数据,排序当前区域
int temp[num] = {0};
int k = 0;
// 注意边界条件,这里的addM是原来的middle+1 所以属于右边的数组,左边的数组不能取到
// 这里的right是原数组的最后一个下边,右边数组需要取到,所以+1。也可以替代成:j <= right
// 当左边数组结束或者右边数组结束就退出循环
while (i < addM && j < right + 1)
{
// 左边数组的项大于右边数组的项,就将小的那一项(右数组的项)放入临时数组,并且右边数组下标自增到下一个
if (a[i] > a[j])
{
temp[k++] = a[j];
j++;
}
else
{
// 否则将左数组的当前项放入临时数组,同时左数组下标自增到下一个元素
temp[k++] = a[i];
i++;
}
}
// 如果左边数组结束,就遍历右边数组,把剩下的都放入临时数组
if (i >= addM)
{
for (; k < num; k++)
{
temp[k] = a[j++];
}
}
else
{
// 否则遍历左边数组,把剩下的都放入临时数组
for (; k < num; k++)
{
temp[k] = a[i++];
}
}
// 把临时数组赋值给原数组,注意原数组的下标,要对应现在正在处理的区间,所以开始下标是left
for (int i = 0; i < num; i++)
{
a[left] = temp[i];
left++;
}
}
// 归并排序递归算法,把数组分成多个左右两部分进行处理
// 分到最小单位时才开始排序
void mergeSort(int a[], int left, int right)
{
// 如果数组只有一个元素,就不用排序
if (left == right)
{
return;
}
else
{
// 先排序左边的数组
int middle = (left + right) / 2;
mergeSort(a, left, middle);
// 在排序右边的
mergeSort(a, middle + 1, right);
// 左右都排序后合并
merge(a, left, right, middle + 1);
}
}
// 主函数
int main()
{
int a[10] = {9, 5, 0, 200, 199, 5, 4, 7, 6, 3};
mergeSort(a, 0, 9);
for (int i = 0; i < 10; i++)
{
cout << a[i] << " ";
}
system("pause");
return 0;
}
有问题请留言交流。