排序算法之归并排序
一、归并排序基本思想
基本思想:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and
Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
归并排序的思想就类似于无数次进行将两个有序的数组合并,而关键的问题是如何得到有序数组
将整个数组进行二分,分到最后,一组只会有一个元素,此时 数组就是有序的
整个分解和合并过程就是归并思想,这个过程近似于二叉树的后序遍历,先递归到最后得到有序数组,之后回到上层递归完成合并,一层一层直到回到最开始的数组左右合并,完成排序
二、递归实现归并排序
递归的实现过程可以观察上面第一张图
合并需要利用一个新数组,合并之后再拷贝回原数组
递归结束的条件是只有一个元素时已经有序,或者没有元素(此处是一个边界问题,写全面些不需要考虑,有兴趣可以研究下会不会出现没有元素的情况,就本人分析来应该是没有,如果不正确评论区留言)
// 归并排序递归实现
void _MergeSort(int* a, int* tmp, int begin, int end)
{
if (begin >= end)
{
return;
}
int midi = (begin + end) / 2;
_MergeSort(a, tmp, begin, midi);
_MergeSort(a, tmp, midi + 1, end);
int begin1 = begin, begin2 = midi + 1;
int end1 = midi, end2 = end;
int i = begin;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
{
tmp[i++] = a[begin1++];
}
else
{
tmp[i++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
}
_MergeSort(a, tmp, 0, n - 1);
free(tmp);
}
三、非递归实现归并排序
非递归的过程可以看上面第二张动图
当gap=1时,每个元素都是单独的一个组,每个组都是有序的
当gap=2时,每两个元素是一个组,这时我们需要比较归并,使得组内有序
当gap=4时候,… …
我们在不用递归的时候依旧可以利用归并的思想,只不过与合并的顺序与递归方法有所不同,但大体上的实现途径是相同的
通过gap的大小来分每组的大小,从最开始的gap = 1 ,保证实现的每组都有序之后,以每次 gap *=2开始增加,不断合并保证每组都是有序的,直到gap > n结束
在讲完思路之后,我们还需要考虑特殊情况——边界问题
根据上图中,我们在实现的时候可能会碰见上面三种情况,如果没处理就会越界
所以当出现以上三种情况的时候,我们需要对其修正
先检查end2是否越界,一旦end2 > n-1 , 我们修正为 end2 = n-1
此时若begin2也越界,end2始终是小于begin2的,该部分不会进行合并
这时再检查 end1是否越界若end1越界则进行修正为 end1 = n - 1
// 归并排序非递归实现
void MergeSortNonR(int* a, int n)
{
int gap = 1;
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
}
while (gap < n)
{
int i = 0;
for (i = 0; i < n; i += gap * 2)
{
int begin1 = i, begin2 = i + gap;
int end1 = i + gap - 1, end2 = i + gap * 2 - 1;
int k = i;
if (end2 > n - 1)
{
end2 = n - 1;
}
if (end1 > n - 1)
{
end1 = n - 1;
}
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
{
tmp[k++] = a[begin1++];
}
else
{
tmp[k++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[k++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[k++] = a[begin2++];
}
}
memcpy(a, tmp, sizeof(int) * n);
gap *= 2;
}
free(tmp);
}
四、归并排序的特性总结
- 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题
- 时间复杂度:O(N*logN)
- 空间复杂度:O(N)
- 稳定性:稳定