自然合并排序算法

合并排序基本思想:

          将待排序元素分成大小大致相同(可以不等长)的两个子集和,分别对两个子集合进行排序,最终将排好序的子集合合并成所要求的排好序的集合



递归版:

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



你可能感兴趣的:(算法分析,自然合并排序,分治法,合并排序)