目录
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
分解为多个小区间
可以看到这种结构很像一棵完全二叉树,分阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。
合并相邻有序子序列
分割区间
以每个区间的中间位置为交界处,将一个区间分割为两个区间。
int mid = (begin + end) / 2;
//[begin,mid] [begin+1,end]
//先分再合并
_MergeSort(a, begin, mid, tmp);
_MergeSort(a, mid+1, end, tmp);
?返回条件
当区间内只剩下一个元素时,我们可以认为该区间是有序的。
if (begin >= end)
{
return;
}
?合并
- 合并是在分割完之后进行的,类似于二叉树里面的后序遍历,在递归的回归过程进行合并区间。
- 合并时,每次取较小的数尾插到tmp数组里
- 合并结束后,将tmp数组拷贝会原数组。
//[begin,mid] [begin+1,end] 归并
int begin1 = begin, end1 = mid;
int begin2 = mid+1, 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 begin, int end,int* tmp)
{
if (begin >= end)
{
return;
}
int mid = (begin + end) / 2;
//[begin,mid] [begin+1,end]
//先分再合并
_MergeSort(a, begin, mid, tmp);
_MergeSort(a, mid+1, end, tmp);
//[begin,mid] [begin+1,end] 归并
int begin1 = begin, end1 = mid;
int begin2 = mid+1, 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!");
return;
}
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
}
基本方法:
非递归的实现方法是采用一种顺序合并,就是直接以一个数作为一个区间,然后进行两两合并,一趟结束后,再以两个数作为一个区间,将区间两两合并,以此类推。
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
//[begin1,end1][begin2,end2]
?注意:这种区间合并的方法可能会造成越界访问,所以我们需要加判断条件,又因为这里是在一趟合并结束后进行拷贝了,所以当begin2和end1大于等于n时,可以直接跳出这层循环,而当end2>=n时,则将end2=n-1,再进行合并。
//防止越界
if (end1 >= n || begin2 >= n)
break;
if (end2 >= n)
{
end2 = n - 1;//直接合并
}
?完整代码
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc!");
return;
}
int gap = 1;
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;
//[begin1,end1][begin2,end2]
//防止越界
if (end1 >= n || begin2 >= n)
break;
if (end2 >= n)
{
end2 = n - 1;//直接合并
}
printf("[%2d,%2d][%2d, %2d] ", begin1, end1, begin2, end2);
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 (j<n&&begin2 <= end2)
{
tmp[j++] = a[begin2++];
}
memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));//防止越界拷贝
}
printf("\n");
gap *= 2;
}
free(tmp);
}