前言
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:
一、递归方法
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);
//利用分治的思想进行分解,直到每一组只有一个元素的时候
int begin1 = begin;
int end1 = mid;
int begin2 = mid + 1;
int end2 = end;
//规定要排序两组的开始与结束
//我们合并的主要思想就是在两组有序数组里
//依次选出最小的放入临时数组tmp里,
//然后当两组元素放入完毕后,tmp里面就是之前两组数据的有序合并
int i = begin;
//i来记录这次合并的最开始的元素
while (begin1 <= end1 && begin2 <= end2) {
if (a[begin1] < a[begin2]) {
tmp[i++] = a[begin1++];
}
else {
tmp[i++] = a[begin2++];
}
}
//循环比较结束可能第一组的元素都比第二组大
//这样当第二组全放入tmp里面以后,还需要放第一组的元素
//第一组元素有剩余:
while (begin1 <= end1) {
tmp[i++] = a[begin1++];
}
//第二组元素有剩余:
while (begin2 <= end2) {
tmp[i++] = a[begin2++];
}
//将此次排好序的数组tmp拷贝到原数组里面
//从begin位置开始
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");
return;
}
//begin为0,end为n-1,这里指的是下标
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
}
二、非递归方法
非递归排序的主要思想其实也是分解然后合并,但这里我们可以不用分解了,把每一组的初始数据的个数设置为1即gap=1,然后逐渐累增,两个gap为1的数据合并成有序的gap为2的数组,然后gap=2的有序数组之间再通过比较合并成gap=4的数组,以此类推直到gap=待排序数组总个数为止
1.一下拷贝整个数组(不推荐)
void MergeSortNonR1(int* a, int n) {
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL) {
perror("malloc");
return;
}
//申请一个临时存放的数组
int gap = 1;
//每组数据从1开始进行有序合并
while (gap < n) {
for (int i = 0; i < n; i += 2 * gap) {
//i+=2*gap
//每次跳过两个gap长度的下标,新的begin1从上一个end2的后面一个位置开始
int begin1 = i;
int end1 = i + gap - 1;
int begin2 = i + gap;
int end2 = i + 2 * gap - 1;
int j = i;
//修改路线
//因为我们可能要归并的数据元素个数为奇数个这个时候可能会出现有一组
//相对另一组多或者少一些元素
//这个时候我们就要修改边界位置
if (end1 >= n) {
//end1越界了
end1 = n - 1;
//end1指向最后一个下标
end2 = n-1;
begin2 = n ;
//begin2与end2就不该存在的,所以让begin2》end2
//这样就不会进入到下面的循环比较过程
}
else if (begin2 >= n) {
//end1没越界,begin2越界了
begin2 = n ;
end2 = n - 1;
//begin2与end2就不该存在的,所以让begin2》end2
//这样就不会进入到下面的循环比较过程
}
else if (end2 >= n) {
//begin2没越界,end2越界了
end2 = n - 1;
//end2指向最后一个下标位置
}
while (begin1 <= end1 && begin2 <= end2) {
//两个有序数组合并过程
if (a[begin1] < a[begin2]) {
tmp[j++] = a[begin1++];
}
else {
tmp[j++] = a[begin2++];
}
}
//begin1剩余。继续合并
while (begin1 <= end1) {
tmp[j++] = a[begin1++];
}
//begin2剩余继续合并
while (begin2 <= end2) {
tmp[j++] = a[begin2++];
}
}
//当gap=m,(m<n)完成以后
//一把拷贝整个数组到原数组里面
memcpy(a, tmp, sizeof(int) * n);
//然后增加每一组的个数,继续进行合并
gap *= 2;
}
}
2.分布拷贝数组(推荐)
代码如下(示例):
void MergeSortNonR2(int* a, int n) {
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL) {
perror("malloc");
return;
}//申请一个临时存放的数组
int gap = 1;
//每组数据从1开始进行有序合并
while (gap < n) {
for (int i = 0; i < n; i += 2 * gap) {
//i+=2*gap
//每次跳过两个gap长度的下标,新的begin1从上一个end2的后面一个位置开始
int begin1 = i;
int end1 = i + gap - 1;
int begin2 = i + gap;
int end2 = i + 2 * gap - 1;
//修改路线
//因为我们可能要归并的数据元素个数为奇数个这个时候可能会出现有一组
//相对另一组多或者少一些元素
//这个时候我们就要修改边界位置
if (end1 >= n || begin2 >= n) {
break;
//因为为部分拷贝,我两组直接排序完了就拷贝一次
//所以当出现这种情况的时候直接break
//如果你直接从tmp里面拷贝,tmp对应位置存的随机值
//就会覆盖a的值
}
if (end2 >= n) {
end2 = n - 1;
//直接改边界
}
int j = i;
while (begin1 <= end1 && begin2 <= end2) {
if (a[begin1] < a[begin2]) {
tmp[j++] = a[begin1++];
}
else {
tmp[j++] = a[begin2++];
}
}
//begin1剩余。继续合并
while (begin1 <= end1) {
tmp[j++] = a[begin1++];
}
//begin2剩余继续合并
while (begin2 <= end2) {
tmp[j++] = a[begin2++];
}
//部分拷贝
//从begin1位置开始拷贝,拷贝end2与begin1之间排完序的数据
memcpy(a + i, tmp + i, (end2 - i + 1) * sizeof(int));
}
gap *= 2;
}
free(tmp);
}
总结
归并排序的特性总结:
- 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
- 时间复杂度:O(N*logN)
- 空间复杂度:O(N)
- 稳定性:稳定