文章目录
- 基本思想
- 递归版本
- 思路
- 代码实现
- 非递归版
- 思路
- 代码实现
- 特性
- 结果演示
基本思想
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
递归版本
思路
1.分解:将要排序的序列每次分解为2个序列,直到不能再分解
2.合并:将分解的序列排序并两两归并
过程:我们需要创建一个临时数组tmp,将每次归并后的结果存入tmp,最后用memcpy将排好后的tmp的数据拷贝到原数组中。
代码实现
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);//将原序列分解为2段序列int i = begin;int begin1 = i, end1 = mid;int begin2 = mid + 1, end2 = end;//确定两段序列的首位下标while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[i++] = a[begin1++];}else{tmp[i++] = a[begin2++];}}//将2段序列中的数按顺序存入tmpwhile (begin1 <= end1){tmp[i++] = a[begin1++];}while (begin2 <= end2){tmp[i++] = a[begin2++];}//将没存完的那个序列剩下的值(有序)存入tmpmemcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));//将tmp拷贝给原数组,加上begin定位排序好的值,因为begin是我们操作2个序列的前一个序列的首下标。不加begin的话就是拷贝整个序列的第一个值,可能不是我们正在归并的序列,还没有被排序。
}
void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}_MergeSort(a, 0, n - 1, tmp);free(tmp);
}
非递归版
思路
递归版本时,我们通过将要排序的序列分解、合并,在合并的过程中完成排序。在非递归的版本中,合并的思路和代码实现是一样,主要是如何将序列分解,递归版本可以利用子问题分治实现,非递归就要用循环模拟递归,我们这里引入一个gap
,代表序列的数据个数,首先让gap等于1,这样就将序列分解为好了,然后开始两两归并,之后让gap不断2倍化,直到gap等于原数组的数据个数。这里分解后的序列首位下标之差就是gap-1(因为是双闭区间,序列的数据个数就是gap)。这里还要注意分解的序列的首尾下标不能超过原序列的数据个数。
代码实现
void MergeSortnonR(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}//临时数组,存归并好的数据int gap = 1;//分解好的序列的数据个数为gapwhile (gap < n){for (int i = 0; i < n; i += 2 * gap){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;//要归并的2个序列的首尾下标int j = begin1;if (end1 >= n || begin2 >= n){break;}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 +i, tmp +i, sizeof(int) * (end2 - i + 1));//将归并好的数据拷贝给原数组}gap *= 2;//放大排序序列的区间,begin1到end1和begin2到end2这两个区间的数据个数是2倍的gap,所以gap *= 2后新序列还是有序的,只需继续将新序列两两归并即可。}
}
特性
- 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
- 时间复杂度:O(N*logN)
- 空间复杂度:O(N)
- 稳定性:稳定
结果演示
int main()
{int a[] = { 2,5,9,1,6,11,3,4 };int n1 = sizeof(a) / sizeof(a[0]);MergeSort(a, n1);printf("MergeSort:");for (int i = 0; i < n1; i++){printf("%d ", a[i]);}printf("\n");int b[] = { 2,7,3,4,1,2,6,5,11,8 };int n2 = sizeof(b) / sizeof(b[0]);
MergeSortnonR(b, n2);
printf("MergeSortnonR:");for (int i = 0; i < n2; i++){printf("%d ", b[i]);}return 0;
}