合并排序基本思想:
将待排序元素分成大小大致相同(可以不等长)的两个子集和,分别对两个子集合进行排序,最终将排好序的子集合合并成所要求的排好序的集合。
递归版:
void MergeSort(int a[],int left,int right)
{
if(left
最坏情况下:T(n)=2T(n/2)+O(n);
则其时间复杂度O(nlogn)
非递归:
void MergeSort(int a[],int n)
{
int *b=new int[n];
int s=1;
while(s
MergePass(b,a,s,n); //合并到数组A
s+=s;
}
}
void MergePass(int x[],int y[],int s,int n)
{
int i=0;
while(i<=n-2*s)
{
Merge(x,y,i,i+s-1,i+2*s-1); //这里只能完成标准区间内的合并排序,如果有多余的元素便顾及不了
i=i+2*s;
}
if(i+sm)
for(int q=j;q<=r;q++) //i>m说明i已经复制到d中了,所以要把剩下的j复制到b中,并且此时c中元素是有序的,所以可以直接复制
d[l++]=c[q];
else
for(int p=i;p<=m;p++)
d[l++]=c[p];
}
分析一下时间复杂度:
纵向:1->2->4->8....n 共logn次
横向:包括比较和循环复制,最多都不超过n
所以时间复杂度也为O(nlogn)
但在现实中给定数组中,肯定存在已排好序的子数组段,如果将其利用起来,就避免了无意义的比较。
例如:a[]={0,-1,9,3,6,4,2,5,7};
自然排好序的子数组段有{0}{-1,9}{3,6}{4}{2,5,7}.可以用一次对数组a的线性扫描,找出这些已排好序的子数组段,然后两两合并,构成更大已排好序的数组。
{-1,0,9}{3,4,6}{2,5,7}->{-1,0,3,4,6,9}{2,5,7}->{-1,0,2,3,4,5,6,7,9}
核心代码:
void GetBPoint(int a[],int b[],int n,int &m) //线性扫描a[],记录下已排好序的子数组段下标
{
int j=0;
b[j++]=0;
for(int i=0;i
void MergeSort(int a[],int n)
{
int *b=new int[n];
int s=1;
while(s
时间复杂度分析:
由于利用了自然排好序的子数组段,所以在自然合并排序中,合并的次数要少很多,即使是在某一具体实例中,而不是所谓的平均情况下。
尽管理论上分析时间复杂度也为O(nlogn),但是实际中,一定会存在自然排好序片段,这样的话时间就会大大降低了。
极端的例子,如果n元素数组已经排好序了,该算法并不需要合并,而合并排序算法还需要进行logn合并,
所以自然合并排序算法需要O(n)(时间仅仅花在扫描上了),合并排序算法需要O(nlogn)时间
自然合并排序完整代码:
#include
using namespace std;
#define N 9
int t_m; //自然合并排序中线性扫描的标记数
int t[N];
void Merge(int c[],int d[],int l,int m,int r)
{
//合并c[l:m]和c[m+1:r]到d[l:r]
int i=l,j=m+1,k=r;
while((i<=m)&&(j<=r))
{
if(c[i]<=c[j])
d[l++]=c[i++];
else
d[l++]=c[j++];
}
if(i>m)
for(int q=j;q<=r;q++) //i>m说明i已经复制到d中了,所以要把剩下的j复制到b中,并且此时c中元素是有序的,所以可以直接复制
d[l++]=c[q];
else
for(int p=i;p<=m;p++)
d[l++]=c[p];
}
void PrintArray(int a[],int n)
{
for(int i=0;i