C语言实现归并排序(递归法)
分解:将数组分割成两个数组,再分别将两个数组又细分成2个数组,直到,最后每个数组都是一个元素,这时将该单元素数组看为有序数组。此时我们再把这些小小小数组(数字)给合并,根据我们上面的艰苦奋斗,发现合并出来肯定有序,直到合并得就剩一个,那不就完成了吗!合并:将分割的有序数组进行排序,排成有序数组后继续为上一个分割它的数组合并,直到数组被合并成原来的数组,此时已经排好序了。上方我们合并的时候用的是有序
1.归并排序基本信息
1.基本原理
归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。
将已有序的子序列合并,得到完全有序的序列;
即先使每个子序列有序,再使子序列段间有序。
分解:将数组分割成两个数组,再分别将两个数组又细分成2个数组,直到,最后每个数组都是一个元素,这时将该单元素数组看为有序数组
合并:将分割的有序数组进行排序,排成有序数组后继续为上一个分割它的数组合并,直到数组被合并成原来的数组,此时已经排好序了
2.优缺点
和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(n log n)的时间复杂度。代价是需要额外的内存空间。归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法一个非常典型的应用
3.动图理解

4.复杂度
1.时间复杂度
归并排序把集合进行层层拆半分组。如果集合长度为n,那么拆半的层数就是logn,每一层进行归并操作的运算量是n。所以,归并排序的时间复杂度等于每一层的运算量×层级数,即O(nlogn)。
2.空间复杂度
归并排序是需要用到额外空间的,但是每次归并所创建的额外空间都会随着方法结束而释放,因此单次归并操作开辟的最大空间是n。所以,归并排序的空间复杂度是O(n)。
2.归并排序的思路

先用这张图来大概展示一下思路,下面将会细讲
1.最基础操作(合并)
即:将已有序的子序列合并,得到完全有序的序列;
如果有两个已经排序好的数组;
我们要把这两个数组合并;1.我们先把俩数组合并成一个数组,这个很简单;
但是合并之后的数组可能并不是一个特别有序的数组,那么此时我们就要给他排序;
2.我们把两个数组排序一下返回一个完全有序的序列
我们用 i 来表示数组A中的第一个元素;
用 j 来表示数组B中的第一个元素;
用 k 来表示新数组也就是待排序的数组的第一个元素;
判断 i 和 j的大小;
如果i<j,那么就让k=i;i++,k++;
如果i>j,那么就让k=j;j++,k++;
假设i<j,那么此时新数组的第一个数字已经排进去了,i++,说明此时i指向了A的第2个元素,而此时j并没有动,仍指向B第一个元素。这时候再次比较ij大小,以此类推,直到某个数组全部元素都清空,我们此时直接把另一个数组的元素并入其中即可。
好了,原理理清,我们开始整理代码思路
我们定义一个merge函数接受1个数组arr,以及待合并子序列的左边界left、中间位置mid和右边界right作为参数。函数的目标是将r中[left, mid]和[mid+1, right]两个有序子序列合并到数组temp中。
具体的合并过程如下:
- 定义一个数组temp暂时储存有序数组。
- 定义三个指针i、j和k,分别指向左子序列的起始位置left,右子序列的起始位置mid+1,以及合并后的数组temp的起始位置left。
- 当i指针和j指针都在对应的子序列内时,比较arr[i]和arr[j]的大小,并将较小的元素放入temp[k]中。移动指针i或j,以及k,继续循环比较并填充temp数组。
- 若左子序列有剩余元素,将剩余元素依次放入temp数组中。若右子序列有剩余元素,将剩余元素依次放入temp数组中。
- 将k返回最开始的位置,遍历temp数组存入arr数组中
- 返回0表示合并过程完成。
来人,上代码!
void merge(int arr[], int left, int mid, int right) {
int temp[100];
int i = left, j = mid + 1;
int k = left;
while (i <= mid && j <= right) {
if (arr[i] < arr[j]) {
temp[k++] = arr[i++];
}
else {
temp[k++] = arr[j++];
}
}
while (i <= mid)
temp[k++] = arr[i++];
while (j <= right)
temp[k++] = arr[j++];
k = left;
while (k <= right) {
arr[k] = temp[k];
k++;
}
}
2.分治法扩大可操作范围
下来开始第二个操作:分!!!
上方我们合并的时候用的是有序数组来排,但是通常我们要的是给乱序数组排序。
这就要用到一个叫做分治法的一个思想;
怎么回事呢;
就是把目标数组分成两半,去执行上面的排序功能,
当我们发现分割后的两个小数组也不满足我排序功能的条件啊!
哎,那我就把小数组都再次分成两半,分成小小数组,再去归并排序;
啊?还不满足,那就再给我分!给我分分分分!!
最后分成一堆就剩一个元素的数组,一定满足有序的条件了吧!
此时我们再把这些小小小数组(数字)给合并,根据我们上面的艰苦奋斗,发现合并出来肯定有序,直到合并得就剩一个,那不就完成了吗!
那么下来便是代码思路:
具体的分割过程如下:
- 首先判断left是否小于right,如果不满足这个条件,则说明当前子序列只有一个元素或为空,无需再进行拆分和合并操作,直接返回。
- 计算出中间位置mid=(left+right)/2。
- 通过递归调用mergesort函数,将左半部分的子序列继续拆分并进行排序(从left到mid)。
- 通过递归调用mergesort函数,将右半部分的子序列继续拆分并进行排序(从mid+1到right)。
- 调用merge函数将左半部分和右半部分的子序列进行合并操作。
在mergesort函数中,通过不断地二分拆分子序列并进行合并操作,最终完成整个序列的排序。
下来便是代码:
void mergesort(int arr[], int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
merge_sort(arr, left, mid);
merge_sort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
3.代码实现
有了上面两个操作的,归并排序就已经完成了
我们只需要引用一次mergesort函数即可通过函数本身来完成分与治两个方向
下来便是归排函数的完整代码
void merge(int arr[], int left, int mid, int right) {
int temp[100];
int i = left, j = mid + 1;
int k = left;
while (i <= mid && j <= right) {
if (arr[i] < arr[j]) {
temp[k++] = arr[i++];
}
else {
temp[k++] = arr[j++];
}
}
while (i <= mid)
temp[k++] = arr[i++];
while (j <= right)
temp[k++] = arr[j++];
k = left;
while (k <= right) {
arr[k] = temp[k];
k++;
}
}
void merge_sort(int arr[], int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
merge_sort(arr, left, mid);
merge_sort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
归并排序三大特点:
在“递”的过程中,对数组均等一分为二,再将子数组,一分为二;
在“归”的过程中,将这两个有序的子数组合并成一个有序的子数组;
递归:
递归的结束返回永远是最小子问题的解
递归调用就是普通函数调用,并无本质区别(每次调用有自己独立的栈空间)
用递归思路解决问题,一定要时刻提醒自己原问题是什么,那么子问题就是什么!
更多推荐



所有评论(0)