排序篇(四)----归并排序
1.归并(递归)
基本思想:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
归并排序核心步骤:
//归并
void _MergeSort(int* a, int* tmp, int begin, int end)
{if (end <= begin){return;}int mid = (begin + end) / 2;_MergeSort(a, tmp, begin, mid);_MergeSort(a, tmp, mid + 1, end);// a->[begin, mid][mid+1, end]->tmp//归并int begin1 = begin, end1 = mid;int begin2 = mid + 1, end2 = end;int index = begin;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2]){tmp[index++] = a[begin1++];}else{tmp[index++] = a[begin2++];}}while (begin1 <= end1){tmp[index++] = a[begin1++];}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 = malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");exit(-1);}_MergeSort(a, tmp, 0, n - 1);free(tmp);
}
归并需要开一个同样大的tmp数组来存放数据,相当于是拿空间来换时间了.
代码详解:
- 首先,将整个序列分为两部分,分别递归调用_MergeSort函数对左右两部分进行排序。
- 在_MergeSort函数中,首先判断递归终止条件,如果end小于等于begin,则表示当前子序列只有一个元素或者为空,无需排序,直接返回。
- 然后,计算中间位置mid,并分别递归调用_MergeSort函数对左右两部分进行排序。
- 接下来,进行归并操作。首先,设置四个指针begin1、end1、begin2、end2分别指向左右两部分的起始和结束位置,以及一个指针index指向当前归并结果的位置。
- 然后,使用两个循环比较左右两部分的元素大小,并将较小的元素放入tmp数组中,同时移动相应的指针。
- 最后,将剩余的元素复制到tmp数组中。
- 最后,将tmp数组中的元素复制回原数组a中,完成归并排序。
2.归并(非递归)
非递归版是在递归上进行了修改,将其递归改为了循环.
//归并(非递归版)
void _MergeSortNotR(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");exit(-1);}int gap = 1;while (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;if (begin2 >= n){break;}if (end2 >= n){end2 = n - 1;}int index = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2]){tmp[index++] = a[begin1++];}else{tmp[index++] = a[begin2++];}}while (begin1 <= end1){tmp[index++] = a[begin1++];}while (begin2 <= end2){tmp[index++] = a[begin2++];}memcpy(a+i, tmp+i, (end2-i+1) * sizeof(int));}gap *= 2;}free(tmp);
}
代码详解:
分配一个临时数组tmp,用于存储归并结果。
然后,设置一个变量gap为1,表示归并的间隔大小。
接下来,循环执行以下操作,直到gap大于等于n:
- 遍历整个数组,每次取两个相邻的子序列进行归并。
- 计算左右两个子序列的起始和结束位置。
- 判断右子序列的结束位置是否超过了数组的长度,如果超过,则将结束位置设置为数组的最后一个元素的下标。
- 使用两个指针begin1和begin2分别指向左右两个子序列的起始位置,使用指针end1和end2分别指向左右两个子序列的结束位置。
- 使用一个指针index指向当前归并结果的位置。
- 使用一个循环,比较左右两个子序列的元素大小,并将较小的元素放入临时数组tmp中,同时移动相应的指针。
- 如果左子序列还有剩余元素,则将剩余元素复制到tmp数组中。
- 如果右子序列还有剩余元素,则将剩余元素复制到tmp数组中。
- 将tmp数组中的元素复制回原数组a中。
- 将gap乘以2,进行下一轮归并。
最后,释放临时数组tmp的内存空间。
归并排序的特性总结:
- 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
- 时间复杂度:O(N*logN)
- 空间复杂度:O(N)
中的元素复制回原数组a中。 - 将gap乘以2,进行下一轮归并。
最后,释放临时数组tmp的内存空间。
归并排序的特性总结:
- 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
- 时间复杂度:O(N*logN)
- 空间复杂度:O(N)
- 稳定性:稳定