欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

温故知新:合并排序(merge sort)及优化方法

程序员文章站 2022-06-08 12:18:06
...
在学习数据结构的表时,我们经常会做到这样的一道题:将两个数组合并为一个数组,并且从低位到高位是非减的。这题不难,但这样的操作是合并排序的基本过程。合并排序的基本思想是分治,将整个的数组排序建立在其子数组的排序之上,其过程为,将数组a[1~n]划分为以单个元素为一个集合,这样就得到了n个集合,再两两集合进行合并的同时排序(就是上面提到的那道题的操作),合并完就得到了n/2(取上整)个集合,再重复合并操作直到集合个数剩下一个即a[1~n],这时可结束合并排序。
template<typename T>
void mergeAB(T a[],int l,int m,int r)
{
    T *b=new T[r-l+1];
    for(int i=l;i<=r;i++)
        b[i-l]=a[i];
    int start1=0,start2=m-l+1,start3=l;
    while(start1<=m-l && start2<=r-l)
    {
        if(b[start1]<b[start2]) a[start3++]=b[start1++];
        else  a[start3++]=b[start2++];
    }
    while(start1<=m-l) a[start3++]=b[start1++];
    while(start2<=r-l) a[start3++]=b[start2++];
    delete[] b;
}
template<typename T>
void mergeSort(T a[],int l,int r)
{
    if(l>=r) return ;
    mergeSort(a,l,(l+r)/2);
    mergeSort(a,(l+r)/2+1,r);
    mergeAB(a,l,(l+r)/2,r);
}

对合并排序的优化主要是对其递归上进行简化。和快速排序一样,当合并排序进行递归的数组长度足够小时,可以改用插入排序,因为小规模上插入排序来得更有优势。那么,我们令当排序数组长度小于M=10时,采用插入排序,

template<typename T>
void insertion(T a[],int l,int r)
{
    for(int i=l+1;i<=r;i++)
    {
        T v=a[i];
        int j=i;
        while(j>l && v<a[j-1])
        {
            a[j]=a[j-1];
            j--;
        }
        a[j]=v;
    }
}
template<typename T>
void mergeSort_M(T a[],int l,int r)
{
    if(r-l<M) {insertion(a,l,r);return ;}
    mergeSort_M(a,l,(l+r)/2);
    mergeSort_M(a,(l+r)/2+1,r);
    mergeAB(a,l,(l+r)/2,r);
}

除了减少递归次数,还有就是采用模拟递归技术,我们知道合并排序就是将元素两两合并,那么可以从数组左端开始,令步长h=1,每隔两个步长就执行一次合并,再令步长h=h+h,重复上述直到步长超过数组长度,这种方法叫自底而上合并排序(mergeBU)。


template<typename T>
void mergeBU(T a[],int l,int r)
{
    for(int m=1;m<r-l;m+=m)
        for(int i=l;i<=r-m;i+=m+m)
        {
            if(a[i+m-1]<=a[i+m]) continue;
            mergeAB(a,i,i+m-1,min(i+m+m-1,r));
        }
}
观察mergeBU发现,以步长为1为起始是因为只有一个元素的数组是有序的,那么就可以放心地进行合并。但实际上,数组的一些元素本来便是有序的,如{4,2,3,9,1,5,6}中可以由分为有序的三个部分{4},{2,3,9},{1,5,6},这样我们只需对三个有序的进行合并排序即可,这种方法更加自然,所以称为自然合并排序。基本思想就是对数组进行一次扫描,划分为有序的部分,再进行合并排序。自然合并排序对有序的数组只需进行时间为o(n)的扫描,相对地mergeBU需要o(nlogn)。
template<typename T>
void naturalMergeSort(T a[],int l,int r)
{
    while(1)
    {
        int start=l;
        int m=l;
        int end=l;
        int flag=0;
        for(int i=l+1;i<=r;i++)
        {
            if(a[i]<a[end] && flag==1)
            {
                mergeAB(a,start,m,end);
                start=i;
                m=i;
                end=i;
                flag=0;
            }
            else if(a[i]<a[end] && flag==0)
            {
                m=end;
                end=i;
                flag=1;
            }
            else if(a[i]>=a[end])
            {
                end=i;
            }
        }
        mergeAB(a,start,m,end);
        if(start==l && end==r) break;
    }
}

下面测试长度为十万的随机数组,元素取值范围为[1,99999]的排序时间比较结果,

template<typename T>
void print(string name,void(*sortName)(T* ,int ,int ),T a[],int length){
	clock_t start=clock();
	sortName(a,0,length-1);
	clock_t end=clock();
	cout<<name<<" : "<<double(end-start)/CLOCKS_PER_SEC<<" s"<<endl;
}
int main(){
	srand(time(0));
	int length=100000;
	int l=1,r=99999;
	int *a=new int [length];
	for(int i=0;i<length;i++)
		a[i]=rand()%(r-l+1)+l;
	print("merge sort",mergeSort,a,length);
	print("M merge sort",mergeSort_M,a,length);
	print("buttom to up merge sort",mergeBU,a,length);
	print("natural merge sort",naturalMergeSort,a,length);
	return 0;
}
温故知新:合并排序(merge sort)及优化方法
相关标签: 数据结构