概念
基本思想:归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有
序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:
归并排序的递归实现
原理
实现
比较begin1和begin2的值的大小,小的插到tmp数组后面,代码实现如下:
//归并
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++];
}
}
//只有begin1或begin2其中一个结束了才会进入后面的循环.
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}
了解归并排序的单趟过程后,再对其进行递归,即可实现归并排序,代码如下:
//处理一段区间
void _MergeSort(int* a, int* tmp, int begin, int end)
{
if (begin = end)
return;
int mid = (begin + end) / 2;
//将区间分为[begin, mid] [mid + 1, end]两个区间,若两区间都有序即可
_MergeSort(a, tmp, begin, mid);
_MergeSort(a, tmp, mid + 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++];
}
}
//只有begin1或begin2其中一个结束了才会进入后面的循环.
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}
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);
tmp = NULL;
}
递归的理解
假设现在我们排0到9这10个数据,则我们先走左边(begin到mid)0到4,要想让0到4有序,则先走左边(0到2),要想让0到2有序,则继续递归分为0到1,再继续分为0到0,1到1,即左边分完,再继续向上再继续递归右边(3到4),再往上走,类似于二叉树中的后 序遍历. 递归展开图:
时间复杂度
空间复杂度也类似于堆排序,每一层有N个,有logN层,则时间复杂度为O(logN).
归并排序的非递归实现
对于归并排序的非递归实现,我们需要转换思路,将数据一一归并,再两两归并,再四四归并......依此类推:
非递归的本质其实是循环,我们设一个gap为每组归并数据的个数,如上图,第一排gap为1,第二排为2,第三排为4,依此类推, 首先将数据两两配对,再四四配对,代码实现如下:
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail!");
}
//gap为每组归并数据的数据个数
int gap = 1;
while (gap < n)
{
for (int i = 0; i < n; i += 2 * gap)//i代表了每次归并的起始位置
{
//[begin1, end1] [b egin2, end2]
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[i++] = a[begin1++];
}
else
{
tmp[i++] = a[begin2++];
}
}
//只有begin1或begin2其中一个结束了才会进入后面的循环.
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
memcpy(a + i, tmp + i, (end2 - i + 1) * sizeof(int));//每次归并后都拷贝回去
}
gap *= 2;
}
//归并一次归两组
free(tmp);
tmp = NULL;
}
但如图所示,我们只能排列2的指数次方数的数据,因为不为2的偶数次方数的数据会导致数据溢出,在控制台显示如下:
一共10个数据,如果显示大于9的数据都是溢出的数据,而数据溢出分为三种情况
第一种与第二种合并为第一种
1.右边数组全部溢出
这种代表右边已经是大数,我们不用管,直接尾插入新数组即可.
if (begin2 >= n)
{
break;
}
2.右边数组未全部溢出
这种情况我们要对数组进行调整处理(修正):将end2减一即可
if (end2 >= n)
{
end2 = n - 1;
}
这样,我们的总代码即为:
总代码
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail!");
}
//gap为每组归并数据的数据个数
int gap = 1;
while (gap < n)
{
for (int i = 0; i < n; i += 2 * gap)//i代表了每次归并的起始位置
{
//[begin1, end1] [b egin2, end2]
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
//第二组不存在(都越界),则不需要归并
int j = i;
if (begin2 >= n)
{
break;
}
//第二组的begin2没有越界,则需要修正,并继续归并
if (end2 >= n)
{
end2 = n - 1;
}
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[i++] = a[begin1++];
}
else
{
tmp[i++] = a[begin2++];
}
}
//只有begin1或begin2其中一个结束了才会进入后面的循环.
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
memcpy(a + i, tmp + i, (end2 - i + 1) * sizeof(int));//每次归并后都拷贝回去
}
gap *= 2;
}
//归并一次归两组
free(tmp);
tmp = NULL;
}