一.简述归并思想
归并本质上使用了分治的思想,有点像二叉树的后续遍历,同时归并排序是一种很典型的外部排序。非常适合解决磁盘中的排序问题。
给一个待排序的数组,不断将其划分直至只含有一个元素,最后将两个元素按数序比较结合起来。然后两两结合,然后四个四个一结合。
时间复杂度:O(n*logn) 每层消耗n 一共有log n层.
空间复杂度:O (N) 因为要单独开辟一个和原数组大小相等的数组。
稳定性 :稳定 (是否稳定取决与代码,只要在遇到相同的数的时候不交换位置即可)
图片来自 菜鸟教程
二 代码实现 递归版
void Mergesort(int* arr, int left, int right, int* tmp)//递归实现
{
if (left>=right){ //处理递归的返回条件
return;
}
int mid = (left + right+1) / 2;
Mergesort(arr,left,mid-1,tmp); //递归本质是二叉树的后序
Mergesort(arr,mid,right,tmp);
int begin1 = left, end1 = mid-1;
int begin2 = mid, end2 = right;
int i = begin1;
while (begin1 <= end1 && begin2 <= end2) //从这里开始处理排序,排序后放到临时数组里,最后拷进原数组
{
if (arr[begin1] <= arr[begin2]) //因为begin1开始的整体是比begin2开始的整体靠左的,在遇到相等时,为保证稳定性让左边的先落下
{
tmp[i++] = arr[begin1++];
}
else
{
tmp[i++] = arr[begin2++];
}
}
while(begin1<=end1) // 当划分的两端,一侧走完另一侧没走完时 直接将没走完的剩余数字拷进临时数组
{
tmp[i++] = arr[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = arr[begin2++];
}
memcpy(arr + left, tmp + left, (right - left + 1) * sizeof(int)); //最后将临时数组拷贝到原始数组,并继续向下递归
}
三 代码实现(非递归)
递归相比非递归比较难以实现,且非递归无法用栈或者队列进行模拟,因为栈或者队列实现的递归方式划分后的区域只能取到一次,也就是说只能划分。但归并排序还需要一个归并回去的过程。这个时候假如用栈或者队列的话,栈内已经空了。
void Mergesort2(int* arr, int len, int* tmp)//非递归实现
{
int gap = 1;
while (gap < len)
{
for (int i = 0; i < len; i+=2*gap) //处理边界,原情况下不是2的倍数的都会溢出,因为边界是手动算出来的,存在
{ //不存在的情况
int begin1 = i, end1 = i + gap - 1; //[i,i+gap-1][i+gap,i+2*gap-1]
int begin2 = i + gap, end2 = i + 2 * gap - 1;//注意不能跳出,就算范围不存在也要归并,因为拷贝的数组包含了这个位置,假如不包括的话,越界的位置就会变成随机值
if (end1 >= len) //当 end1 越界的时候 后序范围全部越界,可以修正成不存在的数组
{
end1 = len - 1;
begin2 = len;
end2 = len - 1;
}
if (end2 >= len) //当 end2 越界的时候 直接修正成数组最后的位置即可
{
end2 = len - 1;
}
if (begin2>=len)//同上
{
begin2 = len;
end2 = len - 1;
}
int i = begin1;
while (begin1 <= end1 && begin2 <= end2)
{
if (arr[begin1] <= arr[begin2])
{
tmp[i++] = arr[begin1++];
}
else
{
tmp[i++] = arr[begin2++];
}
}
while (begin1 <= end1)
{
tmp[i++] = arr[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = arr[begin2++];
}
}
memcpy(arr, tmp , len * sizeof(int));//拷贝的时候是原数组的整个范围,也可以放进归并过程心中
gap*=2;
}
}
我们可以手动实现归并过程,因为归并的范围是可以手动算出来的。【i,gap-1】是一个范围,【gap,i+gap*2-1】是另一个范围。但这里存在一个可能越界的问题.理论上 除了begin1,也就是i,其他边界都有可能越界,这时候就要对越界的进行修正。
从每次归并一个开始,下次每次归两个,依次乘以2。这里用gap表示每次归并的范围。归并的过程和递归的归并过程完全一致,这里我选择了归并完相同的gap后再进行拷贝,也可以归并一次拷贝一次。
但假如说归并一次一次拷贝的话有两个需要注意的点。一是拷贝的范围和大小,有可能不是从最开始拷贝的所以要加一个计数位置。同时拷贝的大小也不是原数组整个大小,而是从begin1到end2的范围。另一个注意点是边界修正问题,这次在end1或者begin2越界的时候就可以不归并了,在end2越界的时候直接修正end2就好了。因为拷贝范围不是整个数组,所以不存在不归并导致的随机值问题