目录
概念与思路
概念
归并排序是建立在 归并操作 上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
若将两个有序表合并成一个有序表,称为二路归并。
思路
思路由下面动图演示:
比较begin1与begin2处的元素,将小的放到tmp中,然后该下标位置继续向后走,循环往复直到一个数组全部放入tmp中,最后将另一个数组拷贝进tmp中
- 实际实现需要先将数组逐渐分解再合并,可以使用递归实现
代码实现
递归实现
思路如上图
void _MergeSort(int* a, int left, int right, int* tmp)
{
if (left >= right) // left
return;
int mid = (left + right) / 2;
// 递归排序 [left, mid] 和 [mid+1, right] 两数组
_MergeSort(a, left, mid, tmp);
_MergeSort(a, mid + 1, right, tmp);
// a[left, mid] a[mid+1, right] 归并到tmp数组
// 数组被分为[left, mid] [mid+1, right]
int begin1 = left, end1 = mid;
int begin2 = mid + 1, end2 = right;
int i = left; // tmp 的起始位置
while (begin1 <= end1 && begin2 <= end2)
{
//左右两边哪个元素更小,就放到tmp中并读到下一位
if (a[begin1] < a[begin2])
{
tmp[i++] = a[begin1++];
}
else
{
tmp[i++] = a[begin2++];
}
}
//出循环则证明某组元素全部插入到tmp
//再将另一组元素拷贝进tmp中
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
// 最后将tmp数组拷贝回a
for (int j = left; j <= right; ++j)
{
a[j] = tmp[j];
}
}
void MergeSort(int* a, int n)
{
//tmp动态开辟空间
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
printf("malloc fail\n");
exit(-1);
}
_MergeSort(a, 0, n - 1, tmp);
// 释放tmp
free(tmp);
tmp = NULL;
}
非递归实现
法一
归并排序非递归可以用栈和队列的方式实现,不妨使用循环尝试
思路如下:
当出现越界后,由于最后需要将tmp中的所有元素拷回a中,则需要进行元素修正;
比如当end1>=n
,此时end=n-1
,后续end1便可以拷贝到tmp中,最后拷贝tmp到a中也不会差错。
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc");
exit(-1);
}
//定义间隔
int gap = 1;
while (gap < n)
{
for (int i = 0; i < n; i += 2 * gap)
{
//作为下标将元素存进tmp
int index = i;
// [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;
//处理越界问题
//end1越界,[begin2, end2]不存在
if (end1 >= n)
{
end1 = n - 1;
}
//[begin1, end1]存在 [begin2, end2]不存在
if (begin2 >= n)
{
begin2 = n;
end2 = n - 1;//不会进入下面的循环
}
if (end2 >= n)
{
end2 = n - 1;
}
//同于递归的思路
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[index++] = a[begin1++];
}
else
{
tmp[index++] = a[begin2++];
}
}
//将另一组拷贝给tmp
while (begin1 <= end1)
{
tmp[index++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[index++] = a[begin2++];
}
}
//把归并后的数据拷贝回原数组
for (int j = 0; j < n; j++)
{
a[j] = tmp[j];
}
gap *= 2;//gap扩大一倍
}
free(tmp);
tmp = NULL;
}
法二
当end1/begin2越界时,实际并没有必要继续归并(后面的所有元素都越界),break即可;
当end2越界时,直接修正即可(后面无元素)。
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
printf("malloc fail\n");
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;
//end1、begin2、end2都有可能越界
// end1越界 或者 begin2 越界都不需要归并(后面元素均越界)
if (end1 >= n || begin2 >= n)
{
break;
}
// end2越界,需要归并,修正end2
if (end2 >= n)
{
end2 = n - 1;
}
int index = i;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[index++] = a[begin1++];
}
else
{
tmp[index++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[index++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[index++] = a[begin2++];
}
// 把归并小区间拷贝回原数组
for (int j = i; j <= end2; ++j)
{
a[j] = tmp[j];
}
}
gap *= 2;
}
free(tmp);
tmp = NULL;
}
特性总结
- 归并的 缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
- 时间复杂度: O(NlogN)*
- 空间复杂度: O(N)
- 稳定性: 稳定