一、什么是归并排序
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
二、归并排序步骤图解
算法思想:
归并排序算法有两个基本的操作,,一个是分解,另一个是合并。分解是把原数组划分成两个子数组的过程,合并可以将两个有序数组合并成一个更大的有序数组。
将待排序的线性表不断地切分成若干个子表,直到每个子表只包含一个元素,这时,可以认为只包含一个元素的子表是有序表。将子表两两合并,每合并一次,就会产生一个新的且更长的有序表,重复这一步骤,直到最后只剩下一个子表,这个子表就是排好序的线性表。
示例图解步骤:
对如图的一个数组进行归并排序的步骤
三、归并排序代码实现
使用归并排序对一个数组a排序,需要另外创建一个数组tmp,用来存放有序子序列再放回去,具体过程如下
归并过程图解:
1、递归实现
//归并排序 void _MergeSort(int* a, int* tmp, int begin, int end) { //递归结束条件 if (begin >= end) { return; } int mid = (begin + end) / 2; _MergeSort(a, tmp, begin, mid); _MergeSort(a, tmp, mid+1, end); // 归并到tmp数据组,再拷贝回去 int begin1 = begin; int end1 = mid; int begin2 = mid + 1; int end2 = end; int index = begin; //begin小于end说明还有两部分都还有数据 while (begin1 <= end1 && begin2 <= end2) { if (a[begin1] < a[begin2]) { tmp[index++] = a[begin1++]; } else { tmp[index++] = a[begin2++]; } } //由于右边没有数据跳出的上一个循环,将左边剩下的数放入tmp数组对应位置 while (begin1 <= end1) { tmp[index++] = a[begin1++]; } //由于左边没有数据跳出的上一个循环,将右边剩下的数放入tmp数组对应位置 while (begin2 <= end2) { tmp[index++] = 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"); return; } _MergeSort(a, tmp, 0, n - 1); free(tmp); }
2、非递归实现
非递归实现时当gap的值不同时有许多数组的数据个数不适合当前gap,访问就会越界,比如9个值时当gap==1就会访问到下标为9的下标越界,所以要在代码中加入解决措施。当第一组右边界越界,第二组左边界也一定越界了,所以可分为第二组左边界越界和第二组右边界越界两种情况处理。
//非递归归并 void MergeSortNonR(int* a, int n) { int* tmp = (int*)malloc(sizeof(int) * n); if (tmp == NULL) { perror("malloc fail"); return; } int gap = 1; while (gap < n) { for (int i = 0; i < n; i += 2 * gap) { // 归并到tmp数据组,再拷贝回去 int begin1 = i; int end1 = i + gap - 1; int begin2 = i + gap; int end2 = i + 2 * gap - 1; // 如果第二组不存在,这一组不用归并了 if (begin2 >= n) { break; } // 如果第二组的右边界越界,修正一下 if (end2 >= n) { end2 = n - 1; } int index = i; //begin小于end说明还有两部分都还有数据 while (begin1 <= end1 && begin2 <= end2) { if (a[begin1] < a[begin2]) { tmp[index++] = a[begin1++]; } else { tmp[index++] = a[begin2++]; } } //由于右边没有数据跳出的上一个循环,将左边剩下的数放入tmp数组对应位置 while (begin1 <= end1) { tmp[index++] = a[begin1++]; } //由于左边没有数据跳出的上一个循环,将右边剩下的数放入tmp数组对应位置 while (begin2 <= end2) { tmp[index++] = a[begin2++]; } // 拷贝回原数组 memcpy(a + i, tmp + i, (end2 - i + 1) * sizeof(int)); } gap *= 2; } free(tmp); }
四、总结
归并排序的算法需要另外创建很大的空间,用来存放排序好的子表,然后再放回原线性表中,所以归并排序的空间复杂度比较大。
- 归并排序时间复杂度为:O(N*logN)
- 归并排序空间复杂度为:O(N)
- 归并排序是稳定的排序
- 归并排序属于外排序,在磁盘中进行