OK啊,这边也是放寒假了,继续学习!
排序一直是很基础但与此同时也很深奥的话题,通过以前的学习,我们接触到了时间复杂度为O(n²)的排序算法:冒泡排序,选择排序,插入排序,每一种排序都有各自的优点,比如冒泡排序,它的代码简单容易记,上手快;再比如插入排序,受数组本身有序性的影响较大,如果数组有序性较强,插入排序就是不二之选;再比如选择排序,它胜在稳定。那么大家有没有感觉到这些排序算法为什么虽然能完成排序的要求,但其实并没有那么优秀呢?是因为上述基本排序都浪费掉了大量的比较过程,也就是说比较后的信息没有办法保存下来,导致很多数据比较了很多次,最后才会比较出结果。那么有没有一些排序算法不会浪费比较过程呢?归并排序就是其中一种。
归并排序
我们为什么要学习归并排序?因为归并排序快,很多OJ都会有时间复杂度和额外空间复杂度的要求,这时候用快的排序算法才不会超时!
现在先来介绍一下归并排序的基本算法。先随便给出一个乱序的数组,对它进行升序排序。
简单来说,归并排序就是首先把一个数组分为左边和右边两个部分,让这两个部分各自有序,然后这两个部分再次将本身划分成左右两个部分,再让这两个部分各自有序,以此类推知道各自各自元素只有一个时,开始从后往前合并。就以上特征可以看出,这是递归的本质,同时也是栈的本质,也就是说上述过程需要递归实现。需要注意的是,上述过程完成递归分为两个部分,也就是左部分和右部分,只有当同一层递归的过程中结果都出现的时候,这层递归才能执行下去,如上图红色箭头的顺序,是从后往前的!
讲完了归并排序的大致思路,我们再来看具体一步是怎么执行的!这里我就拿最后一步左右部分的数组合并过程举例子。
上图中,p1用来指向左侧数组中的元素,p2用来指向右侧数组中的元素,红色箭头表示p1和p2指针的移动,红色箭头上的数字表示指针移动的次序。每一次讲讲p1和p2所指向的数值进行比较,将较小的数拷贝到help数组的i位置,然后i后移1位,以此类推知道两个数组都越界,help数组就拷贝完成,且该数组有序,最后将help数组里的值拷贝回arr数组里面去,输出arr就是排好序的数组啦1!
至此归并排序的基本思路已经讲完,上代码!
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
void displayarr(int* arr, int size)
{
printf("the array is:");
for (int i = 0; i < size; i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void merge(int* arr, int size, int first, int mid, int last)
{
int* help = (int*)malloc(size * sizeof(int));
int i = 0;
int p1 = 0;
int p2 = mid + 1;
while (p1 <= mid && p2 <= last)
{
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
//哪个指针所指向的数值较小,就拷贝哪一个给help,然后对应完成自增操作
//注意:help[i++]是先对help[i]进行操作,再对i进行自增
//如果是help[++i],则是先对i进行自增,再对help[i]进行操作
}
while (p1 <= mid)
{
help[i++] = arr[p1++];
}
while (p2 <= last)
{
help[i++] = arr[p2++];
}
for (int j = 0; j < i; j++)
{
arr[j] = help[j];//将辅助空间的值拷贝给原数组
}
free(help);
//每一个动态分配的内存都需要最后free掉,就是malloc和calloc最后都要free
}
//size传进去的作用是为了动态分配内存申请空间
void mergesort(int* arr, int size,int first,int last)
{
if (first >= last)
{
return;
//递归可以理解成栈,就是先入后出的原理,如果没有return,也就是不返回的话,就不会实现递归的操作,因此对于递归来说return十分重要
}
//求出中间值
int mid = first + (last - first) / 2;
//对以中间值为划分的区域各自mergesort,使得左右部分都有序
mergesort(arr, size, first, mid);
mergesort(arr, size, mid + 1, last);
merge(arr, size, first, mid, last);
}
int main()
{
int arr[] = { 6,5,7,8,2,3 };
int size = sizeof(arr) / sizeof(int);
printf("原始数组:");
displayarr(arr, size);
mergesort(arr, size, 0, size - 1);
printf("归并排序数组:");
displayarr(arr, size);
return 0;
}
值得注意的是,归并排序的算法时间复杂度是O(n*logn),额外空间复杂度是O(n),额外空间复杂度很好理解,就是利用了一个help数组,help数组的个数和本身乱序的数组的元素个数相等,而时间复杂度为何是O(n*logn),这个涉及到master公式,这里就不做过多解释了,只需要知道确实是比一般排序要快即可。
至此我对归并排序的理解就讲完了,如有不妥,欢迎指出,感谢您看到这里!