本博客以排升序为例介绍归并排序!
目录
我们先看一个问题:有两组均为升序的数组要合并到一组数组当中,要求合并后的数组也是升序的,怎么办?
办法:从头开始遍历两组数组,找“小”尾插到合并数组当中。举例如图:
这其实是归并的思想!
归并排序的基本思想:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有 序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
动图展示归并排序:
1.归并排序的递归写法
利用递归不断将需排序数组分解成左右子数组,直到左右子数组元素个数为1停止。再利用归并思想合并子数组到额外空间上,将合并好的子数组从额外空间拷贝回需排序数组相应位置。
void _MergeSort(int* a, int* tmp, int begin, int end)
{
if (begin >= end)
{
return;
}
int mid = (begin + end) / 2;
_MergeSort(a, tmp, begin, mid);
_MergeSort(a, tmp, mid + 1, end);
//分解成[begin mid]和[mid+1 end]两部分归并
int i = begin,n1 = begin, n2 = mid+1;
while (n1<=mid&&n2<=end)
{
if (a[n1] > a[n2])
{
tmp[i++] = a[n2++];
}
else
{
tmp[i++] = a[n1++];
}
}
while (n1<=mid)
{
tmp[i++] = a[n1++];
}
while (n2<=end)
{
tmp[i++] = a[n2++];
}
memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
//归并排序排升序
void MergeSort(int* a, int begin, int end)
{
int* tmp = (int*)malloc(sizeof(int) * (end - begin + 1));
if (tmp == NULL)
{
perror("malloc fail");
return;
}
_MergeSort(a, tmp, begin, end);
}
调用试试:
int main()
{
int a[] = { 1,2,8,9,7,5,33 };
MergeSort(a, 0, sizeof(a) / sizeof(a[0])-1);
for (int i = 0; i < sizeof(a) / sizeof(int); i++)
{
printf("%d ", a[i]);
}
return 0;
}
没问题吧。。。
2.归并排序的非递归写法
非递归其实也很简单,拿这组需排序数组举例来说:
每组1个元素,每两组利用归并思想归并:10和6归并;7和1归并;3和9归并;4和2归并。
每组2个元素,每两组利用归并思想归并:6、10和1、7归并; 3、9和2、4归并。
每组4个元素,每两组利用归并思想归并:1、6、7、10和2、3、4、9归并。
归并完就ok了!
当然,每两组归并的时候是归并到额外空间上的,所以每两组归并完成后记得将这两组数据拷贝回需排序数组相应位置!
void MergeSortNonr(int* a,int begin,int end)
{
int sum = end - begin + 1;
int* tmp = (int*)malloc(sizeof(int) * sum);
if (tmp == NULL)
{
perror("malloc fail");
return;
}
int gap = 1;
while (gap < sum)
{
for (int j = begin; j < sum; j += 2 * gap)
{
int n = j;
//[begin1 end1][begin2 end2]两部分归并
int begin1 = j, end1 = j + gap - 1;
int begin2 = j + gap, end2 = j + 2 * gap - 1;
if (end1 >= sum || begin2 >= sum)//[begin2 end2]这部分完全越界了,不用归并
{
break;
}
if (end2 >= sum)//[begin2 end2]这部分部分越界,修正end2即可继续归并
{
end2 = sum - 1;
}
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[n++] = a[begin1++];
}
else
{
tmp[n++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[n++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[n++] = a[begin2++];
}
memcpy(a + j, tmp + j, sizeof(int) * (end2 - j + 1));
}
gap *= 2;
}
}
调用一下吧:
int main()
{
int a[] = { 1,2,8,9,7,5,33 };
MergeSortNonr(a, 0, sizeof(a) / sizeof(a[0])-1);
for (int i = 0; i < sizeof(a) / sizeof(int); i++)
{
printf("%d ", a[i]);
}
return 0;
}
没问题滴。。。
3.归并排序特性
1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
2.不管哪种写法时间复杂度均为:O(N*logN) 。
3. 不管哪种写法都要用到大小相同于需排序数组大小的额外空间,所以空间复杂度:O(N)。
4.归并排序可以稳定也可以不稳定,关键在于代码的实现。如果[begin mid]和[mid+1 end]两部分归并的时候遇到相同的数据让[begin mid]那个数据尾插到额外空间的话就是稳定的;反之,如果让[mid+1 end]那个数据尾插到额外空间的和就是不稳定的。
谢谢您的阅读!