文章目录
- 基本思想
- 递归版本
- 思路
- 代码实现
- 非递归版
- 思路
- 代码实现
- 特性
- 结果演示
基本思想
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
递归版本
思路
1.分解:将要排序的序列每次分解为2个序列,直到不能再分解
2.合并:将分解的序列排序并两两归并
过程:我们需要创建一个临时数组tmp,将每次归并后的结果存入tmp,最后用memcpy将排好后的tmp的数据拷贝到原数组中。
代码实现
void _MergeSort(int* a, int begin, int end, int* tmp)
{
if (begin >= end)
return;//序列只有一个数的时候,不用也不能再分解
int mid = (begin + end) / 2;
_MergeSort(a, begin, mid, tmp);
_MergeSort(a, mid+1, end, tmp);//将原序列分解为2段序列
int i = begin;
int begin1 = i, end1 = mid;
int begin2 = mid + 1, end2 = end;//确定两段序列的首位下标
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[i++] = a[begin1++];
}
else
{
tmp[i++] = a[begin2++];
}
}//将2段序列中的数按顺序存入tmp
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}//将没存完的那个序列剩下的值(有序)存入tmp
memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));//将tmp拷贝给原数组,加上begin定位排序好的值,因为begin是我们操作2个序列的前一个序列的首下标。不加begin的话就是拷贝整个序列的第一个值,可能不是我们正在归并的序列,还没有被排序。
}
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
return;
}
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
}
非递归版
思路
递归版本时,我们通过将要排序的序列分解、合并,在合并的过程中完成排序。在非递归的版本中,合并的思路和代码实现是一样,主要是如何将序列分解,递归版本可以利用子问题分治实现,非递归就要用循环模拟递归,我们这里引入一个gap
,代表序列的数据个数,首先让gap等于1,这样就将序列分解为好了,然后开始两两归并,之后让gap不断2倍化,直到gap等于原数组的数据个数。这里分解后的序列首位下标之差就是gap-1(因为是双闭区间,序列的数据个数就是gap)。这里还要注意分解的序列的首尾下标不能超过原序列的数据个数。
代码实现
void MergeSortnonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
return;
}//临时数组,存归并好的数据
int gap = 1;//分解好的序列的数据个数为gap
while (gap < n)
{
for (int i = 0; i < n; i += 2 * gap)
{
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;//要归并的2个序列的首尾下标
int j = begin1;
if (end1 >= n || begin2 >= n)
{
break;
}
if (end2 >= n)
{
end2 = n - 1;
}//保证首尾下标不越界
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 +i, tmp +i, sizeof(int) * (end2 - i + 1));//将归并好的数据拷贝给原数组
}
gap *= 2;//放大排序序列的区间,begin1到end1和begin2到end2这两个区间的数据个数是2倍的gap,所以gap *= 2后新序列还是有序的,只需继续将新序列两两归并即可。
}
}
特性
- 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
- 时间复杂度:O(N*logN)
- 空间复杂度:O(N)
- 稳定性:稳定
结果演示
int main()
{
int a[] = { 2,5,9,1,6,11,3,4 };
int n1 = sizeof(a) / sizeof(a[0]);
MergeSort(a, n1);
printf("MergeSort:");
for (int i = 0; i < n1; i++)
{
printf("%d ", a[i]);
}
printf("\n");
int b[] = { 2,7,3,4,1,2,6,5,11,8 };
int n2 = sizeof(b) / sizeof(b[0]);
MergeSortnonR(b, n2);
printf("MergeSortnonR:");
for (int i = 0; i < n2; i++)
{
printf("%d ", b[i]);
}
return 0;
}