目录
一、前言
前面我们讲了插入排序,选择排序以及交换排序,感兴趣的话可以看看下面的链接
那么今天我们来讲一讲归并排序
二、归并排序
![](https://i-blog.csdnimg.cn/blog_migrate/8fc00d726eed1e8039e9ff46d61caa43.png)
我们需要新建一个数组空间,用来保存分区间排序后的数据。使用递归的方法可以把数组分为多个小的区间,先使各个小区间有序,然后再使大区间有序。
归并排序的一般思想就是使用递归法实现。
代码实现如下:
void _MergeSort(int* a, int left, int right, int* tmp)
{
if(left >= right)
return;
int mid = (left + right) / 2;
//[left, mid] [mid+1, right]
//分治递归
_MergeSort(a, left, mid, tmp);
_MergeSort(a, mid+1, right, tmp);
int begin1 = left, end1 = mid;
int begin2 = mid+1, end2 = right;
int idx = begin1;
while(begin1 <= end1 && begin2 <= end2)
{
if(a[begin1] < a[begin2])
{
tmp[idx++] = a[begin1++];
}
else
{
tmp[idx++] = a[begin2++];
}
}
while(begin1 <= end1)
{
tmp[idx++] = a[begin1++]
}
while(begin2 <= end2)
{
tmp[idx++] = a[begin2++];
}
memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
}
void MergeSort(int* a, int n)
{
(int*)tmp = (int*)malloc(sizeof(int) * n)
if(tmp == NULL)
{
printf("malloc fail");
exit(-1);
}
_MergeSort(a, 0, n-1, tmp);
free(tmp);
}
三、归并排序的非递归思想
归并排序的递归思想是最常用的思想。但是,我们知道一旦有递归,必定会建立新的栈帧。而在实际应用中,如果待排序的数据非常多,那么我们就必须建立更多的栈帧,而内存空间中栈区的内存有限,如果栈帧建立过多就会发生栈溢出,造成风险。因此学习归并排序的非递归就十分有必要了。那么接下来我们就来讲讲归并排序的非递归形式。
无法使用递归,那么我们就来使用循环来帮助我们实现归并排序。那么要怎么来控制循环呢?这是一个难题。其实我们这里就需要借助一个变量gap来帮助我们归并区间的数。具体做法如下:
* gap=1,每次归并 [ i, i+gap-1] [i+gap, i+2*gap-1]两个区间的数。即 [0, 0] [1, 1]归并....
* 然后gap *= 2, 两两归并,即 [0, 1] [2, 3]区间的数归并
* 重复上面的步骤
可能文字描述不是很形象,那么请看下图:
<< 聪明的你一定看出来了,i= 多少那后面的区间我是用红色和黑色区分写的,这是为什么呢?因为红色写的我们需要归并的区间已经超出了数组的区间,但是执行程序时却需要超出,因为那是我们算出来的归并区间。实际上后面超出的区间根本就没有数据,那是不是思路错了呢?当然不是,遇到这种情况我们就需要修正一下区间范围,以防止数组越界。
看明白了上图并且想清楚了上面的问题,那么我们就用代码来实现:
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n)
if (tmp == NULL)
{
printf("malloc fail");
exit(-1);
}
int gap = 1;
while(gap < n)
{
for(int i=0; i < n; i += 2*gap)
{
//[i, i+gap-1] [i+gap, i+2*gap-1]
int begin1 = i, end1 = i+gap-1;
int begin2 = i+gap, end2 = i+2*gap-1;
//修正边界
if(end1 >= n)
{
end1 = n-1;
//把begin2和end2修正为不存在的区间
begin2 = n;
end2 = n-1;
}
else if(begin2 >= n)
{
begin2 = n;
end2 = n-1;
}
else if(end2 >= n)
{
end2 = n-1;
}
int j = begin1;
while(begin1 <= end1 && begin2 <= end2)
{
if(a[begin1] < a[begin2])
{
tmp[j++] = a[begin1++];
}
else
{
tmp[j++] = a[begin2++];
}
}
while(begin1 <= end1)
{
tmp[j++] = a[begin1];
}
while(begin2 <= end2)
{
tmp[j++] = a[begin2];
}
}
memcpy(a, tmp, sizeof(int) * n);
gap *= 2;
}
free(tmp);
}
下面我们来测试一下归并排序的非递归代码
int main()
{
int a[10] = { 7,317,96,1,108,6,8,33,55,44 };
int n = sizeof(a) / sizeof(a[0]);
MergeSortNonR(a, n);
for (int i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
运行结果如下:
好了,归并排序的非递归也成功实现了,实现起来确实有一点点小复杂。那么我们的归并排序的所有都讲完了。
四、归并排序的特性总结
1、归并的缺点在于需要O(N)的空间复杂度,归并排序更多的是解决在磁盘中的外排序问题。
2、时间复杂度:O(N*logN)
3、空间复杂度:O(N)
4、稳定性:稳定