赞
踩
在本篇文章中,将继续介绍一种排序算法:归并排序。
归并排序运用了归并的思想,即将两个有序数列归并为一个有序数列。在前面的合并两个有序链表时,运用了这种思想:戳我看归并实现合并两个有序链表(方法二)
归并排序需要一块与数组大小相同的空间,用于临时存储归并后的数据;
排序时,先将整个数组平分为两份,再分为4份……以此类推,直到不能再平分后(区间只剩一个元素);
向上归并,即将两个区间归并到临时空间中的对应位置;
再将临时数组中对应区间中已经排序好的数据拷回原数组;
一直向上归并,直到排序完成。
(需要注意的是,这里的平分不是同时进行的,在递归中,是一条线一条线进行的。左边递归到头后,返回给上一级,继续递归倒数第二层的右边,右边到底返回到上一级,执行最底层左右区间的归并。归并结束后返回到倒数第三层,然后递归该层的右边……)
与快排的递归模式不同,快排是对最大的区间操作完成后,向下递归,对左右区间进行操作,直到排序结束;而归排是先递归到最小区间,再向上归并。所以在实现快排时,每层的操作在前,函数递归在后;而快排是递归在前,每层的操作在后;
函数有4个参数:原数组、区间的左右下标、临时空间。
初始传给函数的参数为整个数组,向下递归,当begin>=end时,return结束递归;
创建midi,记录区间的中间位置,再将左右区间分别递归;
之后实现对区间的操作:
首先用 begin1 、end1 、begin2 、end2记录要归并区间的左右区间的始末下标,并用k记录要归并的区间在临时空间temp中的对应起始位置;
然后将左右区间归并到temp的对应位置;
最后将temp对应空间中的数据拷贝回来:
对于归并的过程,即将两个区间中的数据依次比较,将较小的元素尾插到临时空间中:
void _MergeSort(int* a, int begin, int end, int* temp) { if (begin >= end) { return; } int midi = (begin + end) / 2; _MergeSort(a, begin, midi, temp); _MergeSort(a, midi+1, end, temp); int begin1 = begin, end1 = midi; int begin2 = midi+1, end2 = end; int k = begin1; while (begin1 <= end1 && begin2 <= end2) { if (a[begin1] < a[begin2]) { temp[k++] = a[begin1++]; } else { temp[k++] = a[begin2++]; } } while (begin1 <= end1) { temp[k++] = a[begin1++]; } while (begin2 <= end2) { temp[k++] = a[begin2++]; } memcpy(a + begin, temp + begin, sizeof(int)*(end - begin + 1)); } void MergeSort(int* a, int n) //递归实现 { int left = 0; int right = n - 1; int* temp = (int*)malloc(sizeof(int) * n); if (temp == NULL) { perror("malloc"); return; } _MergeSort(a, left, right, temp); free(temp); }
非递归实现归排时,与快排不同,不用记录上一层的区间,将某个区间归并后,转回原数组即可。所以我们只需要将数组分组,归并每两组的值,每组元素的数量从1开始,每次循环后,每组元素个数乘2,直到大于数组长度循环结束;
每层循环内,需要将这一层中,两个为一组的区间全部归并完成,并将这些数据转回原数组中:
实现非递归时,首先动态开辟一块临时空间temp;
然后,创建gap为每层中每个需要归并区间的元素个数,并初始化为1;
外层while循环控制gap的值,大于等于数组的元素个数时,循环结束;
内层for循环控制每层中要归并的分组(两个元素个数为gap的小区间为一组归并):
左区间的起始位置begin1为i,结束位置end1为begin1+gap-1;右区间的起始位置begin2为end1+1,结束位置end2为begin2+gap-1;k为对应在temp中的起始位置下标;
然后需要判断的是,这组区间是否越界。由于for循环只是控制了i<n,即begin1<n,所以end1、begin2、end2都有可能越界;
当end1或begin2越界时,说明这组(左右)区间已经少了一个区间,不用归并了。当end2越界时,说明一组左右区间是存在的,需要归并,将end2改为数组最后一个元素的下标即可;
然后对左右区间进行归并,归并到temp中的对应位置;
每组(左右)区间归并后,就将数组拷回原数组:
【需要注意的是,由于之前的end2是可能越界的,所以不能直接拷贝2*gap个数据回去,而应该为end2-i+1个数据】
void MergeSortNonR2(int* a, int n) //非递归实现(分别转移) { int gap = 1; int* temp = (int*)malloc(sizeof(int) * n); if (temp == NULL) { perror("malloc"); return; } while (gap < n) { for (int i = 0; i < n; i += (gap * 2))//每层 { int begin1 = i, end1 = begin1 + gap - 1; int begin2 = end1 + 1, end2 = begin2 + gap - 1; int k = begin1; //判断是否越界(如果越界,直接break跳过归并) if (end1 >= n || begin2 >= n) { break; } else if (end2 >= n) { end2 = n - 1; } while (begin1 <= end1 && begin2 <= end2) { if (a[begin1] < a[begin2]) { temp[k++] = a[begin1++]; } else { temp[k++] = a[begin2++]; } } while (begin1 <= end1) { temp[k++] = a[begin1++]; } while (begin2 <= end2) { temp[k++] = a[begin2++]; } memcpy(a + i, temp + i, (end2 - i + 1) * sizeof(int));//每次归并都转移 } gap *= 2;//走向下一层 } }
到此,关于归并排序的内容就已经介绍完了,同时排序算法的讲解也暂告一段落了
如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出
如果本文对你有帮助,希望一键三连哦
希望与大家共同进步哦
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。