「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」
排序
常见的排序算法
image-20211119082822804
常见排序算法的实现
归并排序
基本思想
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:
实际上归并我们不是第一次接触,之前我们也是接触过的,比如合并两个有序数组这个就是归并思想
但是我们上面的题目是左区间有序,右区间也有序。我们正常题目肯定不会直接给你有序。这时候再深一点,你不是没有序吗,那我们再分,分到你无法再分,也就是只有一个了,你能说一个没有序吗,肯定不行,所以我们继续分治。
递归写法
看上面的GIF也知道第一反应是递归
通过调试看一下现象
image-20211130224907672
归并顺序
image-20211130230648998
归并2
image-20211201014452321
归并排序递归子函数
// 归并排序递归子函数
void _MergeSort(int* a, int left, int right, int* tmp){
//左大于右说明是空数组,空数组就跳
//左等于右就是我们要的单体有序
if (left >= right)
return;
//防溢出写法
int mid = left + (right - left) / 2;
_MergeSort(a, left, mid, tmp);
_MergeSort(a, mid+1,right, tmp);
//
int begin1 = left;
int end1 = mid;
int begin2 = mid + 1;
int end2 = right;
int i = left;
//跑空一组就直接跳
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++];
}
//把tmp数组拷贝回到原来的数组中
i = left;
while (i<=right)
{
a[i] = tmp[i];
i++;
}
}
归并排序递归实现
// 归并排序递归实现
void MergeSort(int* a, int n) {
assert(a);
//首先创建一个临时数组
int* tmp = (int*)malloc(sizeof(int) * n);
//空就直接错
assert(tmp);
//子函数
_MergeSort(a, 0, n - 1, tmp);
//不用了就free掉
free(tmp);
//然后置空
tmp = NULL;
}
非递归写法
2^n^个元素的数组
image-20211201012824875
image-20211201014637727
我们看到上面好像没啥问题,那是用为数组元素个数真的太有好了,一直没有落单的元素,好的不真实
image-20211201015555785
image-20211201020545678
随便几个元素的数组
修正下标
越界情况讨论
image-20211201092011579
但是出现另一种恶心情况 重复拷贝
image-20211201094210650
所以接下来我们需要解决index问题
image-20211201095510524
我们修正到n-1,同样也可以把数组修不存在,让他不进下面的循环也就可以不会进行归并
image-20211201100753211
image-20211201101244258
归并排序非递归实现 修正下标
// 归并排序非递归实现
void MergeSortNonR(int* a, int n) {
assert(a);
//首先创建一个临时数组
int* tmp = (int*)malloc(sizeof(int) * n);
//空就直接错
assert(tmp);
int gap = 1;
int i = 0;
while (gap<n){
for (i = 0; i < n; i += 2 * gap){
//单组需要排序的区间
//[i,i+gap-1] [i+gap,i+2*gap-1]
int begin1 = i, end1 = i + gap - 1;
int begin2 = i+gap, end2 = i + 2*gap - 1;
//适用任何元素个数的核心部分
//end1出界,[begin2,end2]不存在
if (end1 >= n) {
end1 = n - 1;
}
//[begin2,end2]不存在
if (begin2 >= n) {
begin2 = n ;
end2 = n - 1;
}
//end2出界
if (end2 >= n) {
end2 = n - 1;
}
//printf("[%d,%d],[%d,%d]",begin1,end1,begin2,end2);
////重复拷贝基本是我们修正到同一个位置的原因
////我们条件断点一下
//if (begin1 <mark> end1 && end1 </mark> begin2 && begin2 <mark> end2 && end2 </mark> n-1)
//{
// //随便一个代码来承接断点,一句费代码
// int a = 0;
//}
//tmp需要一个索引
int index = i;
while (begin1 <= end1 && begin2 <= end2){
if (a[begin1] > a[begin2]) {
tmp[index++] = a[begin2++];
}
else{
tmp[index++] = a[begin1++];
}
}
//肯定还有一个没跑完
while (begin1 <= end1){
tmp[index++] = a[begin1++];
}
while (begin2 <= end2) {
tmp[index++] = a[begin2++];
}
//printf(" %d", index);
}
//printf("\n");
//一行归并完了再考回去
for (i = 0; i < n; i++) {
a[i] = tmp[i];
}
gap *= 2;
}
//不用了就free掉
free(tmp);
//然后置空
tmp = NULL;
}
归一部分拷一部分
我们也可以像递归那样归一半分拷贝一部分,就不需要修正了,因为修正要考虑很多边界情况,有点繁琐
image-20211201104303020
归并排序非递归实现 归一部分拷一部分
// 归并排序非递归实现
void MergeSortNonR(int* a, int n) {
assert(a);
//首先创建一个临时数组
int* tmp = (int*)malloc(sizeof(int) * n);
//空就直接错
assert(tmp);
int gap = 1;
int i = 0;
while (gap<n){
for (i = 0; i < n; i += 2 * gap){
//单组需要排序的区间
//[i,i+gap-1] [i+gap,i+2*gap-1]
int begin1 = i, end1 = i + gap - 1;
int begin2 = i+gap, end2 = i + 2*gap - 1;
////适用任何元素个数的核心部分
////end1出界,[begin2,end2]不存在
//if (end1 >= n) {
// end1 = n - 1;
//}
////[begin2,end2]不存在
//if (begin2 >= n) {
// begin2 = n ;
// end2 = n - 1;
//}
////end2出界
//if (end2 >= n) {
// end2 = n - 1;
//}
//适用任何元素个数的核心部分
//end1出界,[begin2,end2]不存在 都不需要归并
if (end1 >= n || begin2 >= n) {
//直接跳,因为是在原数组操作的不需要担心最后一个没考进去
break;
}
//end2出界 需要归并 就修正
if (end2 >= n) {
end2 = n - 1;
}
//printf("[%d,%d],[%d,%d]",begin1,end1,begin2,end2);
////重复拷贝基本是我们修正到同一个位置的原因
////我们条件断点一下
//if (begin1 <mark> end1 && end1 </mark> begin2 && begin2 <mark> end2 && end2 </mark> n-1)
//{
// //随便一个代码来承接断点,一句费代码
// int a = 0;
//}
//tmp需要一个索引
int index = i;
while (begin1 <= end1 && begin2 <= end2){
if (a[begin1] > a[begin2]) {
tmp[index++] = a[begin2++];
}
else{
tmp[index++] = a[begin1++];
}
}
//肯定还有一个没跑完
while (begin1 <= end1){
tmp[index++] = a[begin1++];
}
while (begin2 <= end2) {
tmp[index++] = a[begin2++];
}
//归一部分拷贝一部分
int j = 0;
for (j = i; j <= end2; j++) {
a[j] = tmp[j];
}
//printf(" %d", index);
}
//printf("\n");
////一行归并完了再考回去
//for (i = 0; i < n; i++) {
// a[i] = tmp[i];
//}
gap *= 2;
}
//不用了就free掉
free(tmp);
//然后置空
tmp = NULL;
}