目录
归并排序的递归实现
代码实现
归并排序的非递归实现
代码实现
前言
归并排序的思想很简单——分治法。简单地说,归并排序的是将序列拆分成几段子序列,将每一段子序列分别进行排序,排好之后再将有序的子序列归并(有点像合并两个有序数组)成为一个有序的序列。
例如要排序数列:10、6、7、1、3、9、4、2;
将序列拆分为 2 个子序列;
继续拆分;
继续拆分;
至此,每个子序列的长度都为 1 ,因为只有一个数,所以可认为是有序序列;
现将子序列两两归并,即合并两个有序序列;
继续归并;
继续归并;
以上就是归并排序的整个过程,很显然归并排序的实现应该离不开递归的思想。
正文
归并排序的递归实现
归并排序的递归实现较为简单,需要注意的有两点:
1. 归并的过程并非在原数组上直接改动,而是开辟一个临时数组,在临时数组上进行排序,排好之后将临时数组的内容全部拷贝到原数组;
2. 代码中使用的是二路归并(如上图所示,每次将序列拆分为两个子序列)。
代码实现
void _MergeSort(int* a, int begin, int end, int* tmp) { //递归的结束条件//当序列只有一个元素时或序列不存在时 if (begin >= end) return; //将序列进行拆分 //[begin,mid] [mid+1,end] int mid = (begin + end) / 2; //拆分的过程 _MergeSort(a, begin, mid, tmp); _MergeSort(a, mid+1, end, 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++]; } } //如果第二段序列先结束 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 fail"); exit(-1); } _MergeSort(a, 0, n - 1, tmp); //释放与置空 free(tmp); tmp = NULL; }
归并排序的非递归实现
非递归与递归作用思想基本相同。递归实现时,因为拆分序列时采用的是递归的方式,所以通过传递参数就可以控制子序列的长度。但是非递归不行,非递归通过变量 rangeN 来控制序列的长度(或间隔),每次让 rangeN *= 2 例如:
但是由于 rangeN 每次都 *2 ,而我们排序的序列长度不可能总是 2 的倍数,所以 可能会有数组越界访问的风险。例如:
现将两个子序列归并,并将数据拷贝回原数组时,就会发生越界:
当然这只是其中一种越界的可能情况——第二段序列发生越界,原因是右边界 end2 大于 n;
实际操作中,一共会有三种情况导致越界:
两段序列的区间分别为: [begin1,end1] [begin2,end2]
1. end1 > n;
2. begin > n;
3.end2 > n;
所以,当这三种情况发生时,需要修正区间,以上述用例为例, end2 大于 n 时,令 end2 = n-1即可;
代码实现
void MergeSortNonR(int* a, int n) { //开辟一个临时数组 int* tmp = (int*)malloc(sizeof(int) * n); if (tmp == NULL) { perror("malloc fail"); exit(-1); } int rangeN = 1; while (rangeN < n) { // i 控制访问子序列的位置 for (int i = 0; i < n; i += 2 * rangeN) { //拆分为两段子序列//[begin1,end1] [begin2,end2] int begin1 = i, end1 = i + rangeN - 1; int begin2 = i + rangeN, end2 = i + 2 * rangeN - 1; int j = i; //判断是否发生越界的三种情况,如果有,就修正区间 if (end1 >= n) { end1 = n - 1; //将第二段序列改为不存在的序列即可 begin2 = n; end2 = n - 1; } else if (begin2 >= n) { //将第二段序列改为不存在的序列即可 begin2 = n; end2 = n - 1; } else if (end2 >= n) { //修正区间 end2 = n-1; } 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(a, tmp, sizeof(int) * n); //控制间隔 rangeN *= 2; } //释放与置空 free(tmp); tmp = NULL; }