目录
一、问题(合并子功能的实现):
想要弄懂归并排序,首先要弄懂这样一个问题:
1.给定两个有序数组,如何将这两个数组合并到一个数组中去,同时还要保证这个新的数组是有序的呢?(图片来自别的博主)
2.根据这个过程,我们可以设计一个代码来进行合并的操作,具体思路如下:
1、使用双指针分别指向两个数组的开始
2、开辟一个新的辅助数组空间
3、将指针数值小的元素拷贝到辅助空间,该指针后移
4、必然会出现两种情况,即其中一个指针会先越界
5、判断指针的越界情况,将未越界的剩余元素全部移到辅助空间
我们可以发现,能这样做的首要前提是两个待处理数组都是有序的。我们每次都将最小的数字移入空间,那么最后剩下的一批肯定比已经移入的元素要大,可以自行画图移动来进行理解。
3.好,那我们现在来设计对应的代码来实现合并的操作:
void merge(vector<int>& arr, int L, int M, int R) {
int i = L, j = M + 1;
vector<int> tem; //定义临时数组存放排序好的数据
while (i <= M && j <= R) {
if (arr[i] <= arr[j]) tem.push_back(arr[i++]); //先拷贝再自增 简化代码
else tem.push_back(arr[j++]);
}
while (i <= M) tem.push_back(arr[i++]); //如果是j越界了,i还么放完,剩下的全塞进去
//这两个while只会中其中一个
while (j <= R) tem.push_back(arr[j++]);
int t = 0, p = L; //t指向tem,p指向原数组
while (p <= R) arr[p++] = tem[t++]; //覆盖元素组[L,R]区间上的元素
}
在这个函数中,我们的L,M,R规定了这两个和并数组的界限:
[L,M]是第一个数组的下标范围,[M+1,R]是第二个数组的下标范围
二、递归思路
递归设计:我们每次将数组同过传入递归传入的参数范围,将数组平分。
返回条件:当(L+R)传入的参数左右范围是同一个值说明此时已经不能再往下拆分,停止递归。
回溯:调用合并函数,将每次将下一层递归处理好的子数组进行合并(merge操作)。
三、全流程完整代码(递归函数+合并函数)
#include <vector> //用于动态数组
#include <iostream>
#include <ctime> //用于生成随机数种子
#include <cstdlib> //用于rand()
using namespace std;
int randint(int a, int b) {
return rand() % (b - a + 1) + a;
}
void initArr(vector<int>& arr, int N) {
for (int i = 0; i < N; i++) arr.push_back(randint(1, 100));
//生成数值1到100范围的长度为N数组
}
void merge(vector<int>& arr, int L, int M, int R) {
int i = L, j = M + 1;
vector<int> tem; //定义临时数组存放排序好的数据
while (i <= M && j <= R) {
if (arr[i] <= arr[j]) tem.push_back(arr[i++]); //先拷贝再自增 简化代码
else tem.push_back(arr[j++]);
}
while (i <= M) tem.push_back(arr[i++]); //如果是j越界了,i还么放完,剩下的全塞进去
//这两个while只会中其中一个
while (j <= R) tem.push_back(arr[j++]);
int t = 0, p = L; //t指向tem,p指向原数组
while (p <= R) arr[p++] = tem[t++]; //覆盖元素组[L,R]区间上的元素
}
void mergeSort(vector<int>& arr, int L, int R) {
if (L == R) return; //L==R说明递归到最底部,这时开始回溯
int mid = L + ((R - L) >> 1); //等同与L + (R - L)/2 但是位运算更帅且更快~
//这里注意+优先级大于>>
//之前我写一直出bug,上网一搜位运算优先级,有个大哥也是在写归并排序出现了这个同样的问题,说来真巧~
// https://blog.csdn.net/hhhenjoy/article/details/115479935参见这篇blog
mergeSort(arr, L, mid); //递归左边
mergeSort(arr, mid + 1, R); //递归右边
//先递归到最底部再回溯然后merge归并起来
merge(arr, L, mid, R);
//归并下一层的有序数组
}
int main() {
srand(time(0)); //生成随机数种子来随机生成数组
vector<int> arr;
initArr(arr, 50); //调用初始化数组函数
vector<int>::iterator it = arr.begin(); //建立一个迭代器
cout << "未排序的数组:" << endl;
for (; it < arr.end(); it++) cout << *it << ' ';cout << endl; //打印没有排序的数组
mergeSort(arr, 0, arr.size() - 1); //归并排序范围为整个数组
cout << "排好序的数组:" << endl;
it = arr.begin(); //初始化迭代器让他回到第一个位置
for (; it < arr.end(); it++) cout << *it << ' ';cout << endl; //打印排好序的数组
}
四、代码效果
五、总结与学习建议
1、归并排序,实际上是递归算法的一种实现,巧妙地利用了递归栈存函数,从而延时调用的性质。
2、在学习归并之前(快排也是一个原理),最好是先弄懂递归调用的底层原理,然后能够独立设计出merge(合并部分)算法,这时再来看归并会通透很多。