归并排序(二路归并)
算法思路
归并排序(MergeSort)是基于归并操作的一种排序算法,归并排序对序列元素进行折半分组,之后从最小分组开始比较排序,排序之后合成一个一个比较大的分组,再次于同等规划大的分组进行合并,直到最终所有的元素合并好,排序完成。
❗❗❗归并合并的时候两个序列必须都是组内有序才可以进行合并(为什么先进行递归处理的原因)。
算法操作
1. 递归分组
相比于快排,快排操作顺序是确定好一个元素所放置的位置之后,在对左右区间进行递归处理,所以要先操作,后递归。
而归并操作,需要先将所有的分组都划分成最小单位,之后依次从底层向上合并,所以归并的递归操作放在最开始,先进行递归分组,之后在进行合并。
2. 归并操作
双指针依次指向合并后的两个有序序列的开头,分别进行比较,根据比较的情况进行元素移动,(❗)循环结束的时候,还要判断两个有序序列是否都已经完成添加,若没有完成则全部进行添加。
3. 将归并好的数据复制到原数组之中。
空间复杂度
需要一个额外的数组来存放排序完后的数据,时间复杂度为O(n)。
时间复杂度
归并排序的时候区间分组的基准是相同的,也就是说无论什么情况下区间分组树的高度都是O(log2n)。
所以最好情况与最坏情况相差在归并操作的复杂度上。
①. 最好情况
对于两个有序序列,第一个有序序列的尾元素刚好是小于等于第二个序列的开头元素。也就是两个序列可以直接合并。
这样情况下进行的操作复杂度为n。
综上总时间复杂度为O(nlog2n)。
②. 最坏情况
两个有序序列中的元素大小相对于对方序列的元素是大小依次出现的,这样需要比较次数最多。
最坏情况下操作的复杂度也是n(只代表数量级,真正的操作次数远比最好情况下大)
综上总时间复杂度为O(nlog2n)。
③. 平均情况
时间复杂度为O(log2n)。
代码实现
//归并排序的模板
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n;
int a[N]; //原数组
int tmp[N]; //排序好数据所存放的数组
void merge_sort(int a[],int l,int r)
{
if(l >= r)
{
return ;
}
int mid = l + r >> 1;
merge_sort(a,l,mid); //递归分组
merge_sort(a,mid + 1,r);
int k = 0;
int i = l;
int j = mid + 1;
while(i <= mid && j <= r) //按照升序进行排序
{
if(a[i] < a[j])
{
tmp[k++] = a[i++];
}
else
{
tmp[k++] = a[j++];
}
}
//判断两个序列哪一个没有完全合并,将剩余的合并进去
while(i <= mid)
{
tmp[k++] = a[i++];
}
while(j <= r)
{
tmp[k++] = a[j++];
}
for(i = l, j = 0 ; i <= r ; i++,j++)
{
a[i] = tmp[j];
}
return ;
}
int main ()
{
cin >> n;
for(int i = 0 ; i < n ; i++)
{
scanf("%d",&a[i]);
}
merge_sort(a,0,n - 1);
for(int i = 0 ; i < n ; i++)
{
printf("%d ",a[i]);
}
return 0;
}
平均情况不应该是O(nlog2n)吗?