归并排序
原理:
归并排序采用的是分治法,并且依托于归并操作,其思想是分而治之。
归并操作是将两个有序的数列合并到一个有序的序列,那么对于一个无序的长序列,可以把它分解为若干个有序的子序列,然后依次进行归并。如果我们说每一个数字都是单独有序的序列,那么只要把原始长序列依次分解,直到每个子序列都只有一个元素的时候,再依次把所有的序列进行归并,直到序列数为1为止。
思路:
- 分解:将列表越分越小,直至分成一个元素。
- 终止条件:一个元素是有序的。
- 合并:将两个有序列表归并,列表越来越大。
图解归并排序
算法解析:
- 每一层的时间复杂度是O(n),层数是logn。因此总的时间复杂度是O(nlogn)。
- 归并排序需要一个与原数组相同长度的数组做辅助来排序由于merge函数创建了一个aux的临时空间,到最大的时候长度是n,空间复杂度是O(n)。
- 稳定性:归并排序是稳定的排序算法
- 该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。速度仅次于快速排序,为稳定排序算法,一般用于对总体无序,但是各子项相对有序的数列,归并排序的比较次数小于快速排序的比较次数,移动次数一般多于快速排序的移动次数
C代码如下:
#include <stdio.h>
#include <stdlib.h>
//归并排序——治
void merge(int a[], int l, int r, int mid)
{
int aux[8] = { 0 };
int i, j, k;
for (k = l; k <= r; k++){//在治的过程中,对数组做拷贝
aux[k - l] = a[k];
}
i = l;
j = mid + 1;
for (k = l; k <= r; k++)
{
if (i>mid)//左数组的值已经全部排列完毕
{
a[k] = aux[j - l];
j++;
}
else if (j>r)//右数组的值已经全部排列完毕
{
a[k] = aux[i - l];
i++;
}
//左右两个数组最左端进行对比
else if (aux[i - l]>aux[j - l])
{
a[k] = aux[j - l];
j++;
}
//保证当左右两部分的值相等的时候,先复制左边的值,这样可以保证值相等的时候两个元素的相对位置不变。
else
{
a[k] = aux[i - l];
i++;
}
}
}
//归并排序————分
void MergeSort(int a[], int l, int r)
{
if (l >= r)
return;
int mid = l + (r - l) / 2;
MergeSort(a, l, mid);
MergeSort(a, mid + 1, r);
merge(a, l, r, mid);
}
int main(){
int i;
int arr[] = { 8, 4, 5, 7, 1, 3, 6, 2 };
int len = sizeof(arr) / sizeof(arr[0]);
MergeSort(arr, 0, len-1);
for (i = 0; i < len; i++)
{
printf("%d ", arr[i]);
}
system("pause");
return 0;
}
代码生成图
知识点习题:
- 分治法能够解决的问题具有的特征是( )
A. 该问题的规模缩小到一定的程度就可以容易的解决。
B. 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。
C. 利用该问题分解出的子问题的解可以合并为该问题的解。
D. 该问题所分解出的自问题是相互独立的,即子问题之间不包含子子问题。
正确答案:ABCD
如有不同见解,欢迎留言讨论!