赞
踩
归并排序是建立在归并操作上的一种有效算法。该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列间断有序。若将两个有序表合并成一个有序表,成为二路归并。
● 把长度为n的输入序列分成两个长度近似为n/2([begin,mid] [mid + 1,end] )的子序列
● 对这两个子序列再分别进行归并排序,将区间分解到不可在分为止
● 依次将两个排序好的子序列合并成一个最终的排序序列
方法: 开辟新数组,用双指针选出两个子序列中的较小元素尾插在新数组中
为什么要将区间拆分成[begin,mid] [mid + 1,end] ?
上面介绍过我们的算法步骤是把长度为 n 的输入序列分成两个长度近似为 n/2 的子序列,在此前提下,除了分成[begin,mid] [mid + 1,end]外,还有一种分法:[begin,mid-1] [mid,end],为什么不用这种分法?
mid = (begin + end)/2,所以mid是偶数
示例:用两种不同的分法分解区间[0,9]
当所要分解的区间是 [偶数,偶数],上述的两种分法都行得通,但是当区间是[偶数,偶数+1]的情况,[begin,mid-1] [mid,end]这种分法就行不通。
- void _MergeSort(int* arr, int* tmp, int begin, int end)
- {
- if (begin >= end)
- return;
-
- int mid = (begin + end) / 2;//偶数
- //[begin,mid] [mid+1,end] ——>[偶数,偶数] [偶数+1,偶数+1]
- _MergeSort(arr,tmp, begin, mid);
- _MergeSort(arr, tmp, mid + 1, end);
-
- //左右区间有序就可以归并了
- //归并
- int begin1 = begin, end1 = mid;
- int begin2 = mid + 1, end2 = end;
- int i = begin;
- while (begin1 <= end1 && begin2 <= end2)
- {
- if (arr[begin1] < arr[begin2])
- {
- tmp[i++] = arr[begin1++];
- }
- else
- {
- tmp[i++] = arr[begin2++];
- }
- }
- //剩下的尾插在tmp数组
- while (begin1 <= end1)
- {
- tmp[i++] = arr[begin1++];
- }
- while (begin2 <= end2)
- {
- tmp[i++] = arr[begin2++];
- }
- //将一定长度的tmp复制到arr中
- memcpy(arr + begin, tmp + begin, (end - begin + 1) * sizeof(int));
- }
- //归并
- void MergeSort(int* arr, int n)
- {
- int* tmp = (int*)malloc(sizeof(int) * n);
- if (tmp == NULL)
- {
- perror("malloc fail!");
- return;
- }
- _MergeSort(arr,tmp, 0, n - 1);
-
- free(tmp);
- tmp = NULL;
- }
不同排序对一百万个随机数进行排序的效率: (毫秒)
● gap表示每组归并的数据个数
● i 表示每组归并的起始位置
● 两层循环,一层循环用来归并数组中的数据,另一层扩大归并的数据个数
为什么要归并一次拷贝一次?
● 如果整体拷贝,在上述的“第二组的都越界不存在”的情况下,虽然跳出循环,但在整体拷贝的时候还会把越界的部分拷贝回去
● 但如果归并一次拷贝一次,在“第二组的都越界不存在”的情况下,直接跳出循环,不会将越界的部分拷贝到数组中
- //归并(非递归)
- void MergeSortNonR(int* arr, int n)
- {
- int* tmp = (int*)malloc(sizeof(int) * n);
- if (tmp == NULL)
- {
- perror("malloc fail!");
- return;
- }
- //gap表示每组要归并数据的个数
- int gap = 1;
- while (gap < n)
- { //i表示每组归并的起始位置
- for (int i = 0; i < n; i += 2 * gap)//个数于区间的转化关系
- {//[bagin1,end1] [bagin2,end2]
- int begin1 = i, end1 = i + gap - 1;
- int begin2 = i + gap, end2 = i + 2 * gap - 1;
-
- //第二组的都越界不存在,那么第二组不用归并了,跳出循环
- if (begin2 > n)
- break;
-
- //第二组的begin2没越界,end2越界了,需要修正区间,再归并
- if (end2 > n)
- end2 = n - 1;
-
- //归并
- int j = i;
- while (begin1 <= end1 && begin2 <= end2)
- {
- if (arr[begin1] < arr[begin2])
- {
- tmp[j++] = arr[begin1++];
- }
- else
- {
- tmp[j++] = arr[begin2++];
- }
- }
- //剩下的尾插在tmp数组
- while (begin1 <= end1)
- {
- tmp[j++] = arr[begin1++];
- }
- while (begin2 <= end2)
- {
- tmp[j++] = arr[begin2++];
- }
- //将一定长度的tmp复制到arr中
- //归并一次拷贝一次
- memcpy(arr + i, tmp + i, (end2 - i + 1) * sizeof(int));
- }
- gap *= 2;
- }
- free(tmp);
- tmp = NULL;
- }
归并排序是一种稳定的排序方法。和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是,代价是需要额外的内存空间。
(1) 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在此版中的外排序问题
(2) 时间复杂度:O(N*logN) 相当于一个满二叉树
(3) 空间复杂度:O(N) 开辟了一个额外的数组
(4) 稳定性:稳定
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。