归并排序从字面上来看,它的大致核心应与归并有关——归并拆分开来,变成归类和合并,归类则是将数组进行有序化,合并则是将两个有序的数组进行合并变成一个有序的数组。
它的特点在于并不是一开始就将整个数组进行归类和调整,而是以一定的间隔数分成多次小的排序,最后再逐渐将小的排序的范围变大,最后变大到整个数组时,已经完全有序。
递归法(Top-down)
迭代法(Bottom-up)
原理如下(假设序列共有n个元素)
界定比较的数据个数:一般按照2的倍数增长:两个互相比较、四个互相比较、八个互相比较…下图可以很好地说明这种方法
上图根据颜色的不同进行分组,可以看到先分成两个数据。再分成四个…
void _MergeSort(int* a, int begin, int end, int* tmp)
{
int mid = (begin + end) / 2;//中间值
//(提问:如果不是2的倍数会不会有错——不会,归并本身和元素个数无关,基于"/"的特性)
if (begin >= end)
{
return;
}
_MergeSort(a, begin, mid, tmp);//先从左边开始
_MergeSort(a, mid + 1, end, tmp);//再从右边开始
int begin1 = begin, end1 = end;//左边的起始位置和结束位置
int begin2 = mid + 1, end2 = end;//右边的起始位置和结束位置
int i = begin;//这里不能给0,因为递归时会多次调用
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(tmp + begin, a + begin, sizeof(int) * (end - begin + 1));//将排好序的拷贝回去
}
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
printf("malloc fail\n");
return;
}
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
}
非递归的归并排序
//非递归归并
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
//归并排序为什么不能用栈?——因为递归的时候需要保存现场,栈不方便
if (tmp == NULL)
{
printf("malloc fail\n");
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;//右边的起始位置和结束位置
//考虑越界情况
//if (end1 >= n)
//{
// end1 = n - 1;
// begin2 = n;//这里不能给n-1,因为下面会++,会越界
// end2 = n - 1;
//}
//else if (begin2 >= n)
//{
// begin2 = n;
// end2 = n - 1;
//}
if (end1 >= n || begin2 >= n)
{
break;
}
else if (end2 >= n)
{
end2 = n - 1;
}
int j = i;//这里不能给0,因为递归时会多次调用
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(tmp + i, a + i, sizeof(int) * (end2 - i + 1));
}
gap *= 2;
}
free(tmp);
}
O(nlogn)
鉴于归并排序会改变前后元素的相对位置,所以:不稳定
我们发现快速排序和归并排序都使用了一种分治的思想,这里对其进行简单介绍一下,以便更好地理解归并排序
分治模式在每层递归时都有三个步骤: 1.分解原问题为若干子问题,这些子问题是原问题的规模较小的实例,也就是说实际上子问题也可以看作是一个原问题。 2.解决这些子问题,递归地求解各子问题。然而,若子问题的规模足够小,则直接求解。 3.合并这些子问题的解成原问题的解。 归并排序算法完全遵循分治模式。直观上其操作如下:
所以从这个角度来看实际上分治思想也是基于递归的思想来解决问题的。所以间接地也能帮助我们理解递归的思想。
在计算机科学中,分治法(英语:Divide and conquer)是建基于多项分支递归的一种很重要的算法范型。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
分治法能解决的问题一般有如下特征:
总的来说,分治法也可以被称作一种算法,它是一种基于递归的、“分而治之”的算法思想。