您的位置:首页 > 其它

【从零学习经典算法系列】分治策略实例——归并排序(Mergesort)

2014-07-13 17:20 459 查看
1、归并排序算法简介

归并排序是递归算法的一个很好的实例,该算法的基本操作是合并两个已排序的表,因为这两个表是已排序的,所以若将输出放到第三个表中时,该算法可以通过对输入数据一趟排序来完成。

算法流程:基本的合并算法是取两个输入数组A和B,一个输出数组C,以及三个计数器Aptr,Bptr,Cptr,它们初始置于对应数组的起始端。A[Aptr]和B[Bptr]中较小者被拷贝到C中的下一个位置,相关的计数器向下个元素推进一步。当两个输入表有一个用完之后,则将另一个表剩余部分拷贝到C中。

2、归并排序算法分析

对于N个数的归并排序的用时等于完成两个大小为N/2的递归排序所用的时间再加上合并的线性时间。

其递归表达式为:T(N) = 2T(N/2) + N,由主方法可得,其运行时间为O(NlogN)。

但是,归并排序主要问题在于合并两个排序的表需要线性附加内存(开辟临时内存),在整个算法中还要花费将数据拷贝到临时数组再拷贝回来这样的一些附加工作,其结果导致严重放慢了排序的速度。

3、归并算法c语言实现

我们在c程序实现的过程中,开辟了一个和输入的数组相同大小的空间来作临时存取用。由于Merge过程位于Sort的后面,故在任一时刻只需要一个临时数组,可以使用该临时数组的任意部分。



图1 归并排序动态演示图

<span style="font-size:14px;">#include <stdio.h>
#include <stdlib.h>
typedef int ElementType;

//Lpos = start of left half, Rpos = start of right half
void Merge(ElementType A[], ElementType TmpArray[],\
			int Lpos, int Rpos, int RightEnd)
{
	int LeftEnd, NumElements, TmpPos;

	LeftEnd = Rpos - 1;
	TmpPos = Lpos;
	NumElements = RightEnd - Lpos + 1;

	//main loop
	while(Lpos <= LeftEnd && Rpos <= RightEnd){
		if(A[Lpos] <= A[Rpos])
			TmpArray[TmpPos++] = A[Lpos++];
		else
			TmpArray[TmpPos++] = A[Rpos++];
	}
	while(Lpos <= LeftEnd)//copy the rest of left half
		TmpArray[TmpPos++] = A[Lpos++];
	while(Rpos <= RightEnd)//copy the rest of right half
		TmpArray[TmpPos++] = A[Rpos++];

	//copy TmpArray back
	for(int i=0;i<NumElements;i++,RightEnd--)
		A[RightEnd] = TmpArray[RightEnd];
}

void Sort(ElementType A[], ElementType TmpArray[],\
			int Left, int Right)
{
	int Center;

	if(Left < Right){
		Center = (Left+Right)/2;
		Sort(A, TmpArray, Left, Center);
		Sort(A, TmpArray, Center+1, Right);
		Merge(A, TmpArray, Left, Center+1, Right);
	}
}

void Mergesort(ElementType A[], int N)
{
	ElementType *TmpArray;
	TmpArray = (ElementType *)malloc(N * sizeof(ElementType));
	if(TmpArray != NULL){
		Sort(A, TmpArray, 0, N-1);
		free(TmpArray);
	}
	else
		fprintf(stderr,"No space for temporary array");
}

void PrintElement(ElementType A[],int N,char *prompt)
{
	printf("%s : ",prompt);
	for(int i=0;i < N;i++){
		printf("%d  ",A[i]);
	}
	printf("\n");
}

int main(int argc,char **argv)
{
	ElementType a[] = {9,8,7,6,5,4,3,2,1};
	int NumElement = sizeof(a)/sizeof(ElementType);
	PrintElement(a,NumElement,"The original array");
	Mergesort(a,NumElement);
	PrintElement(a,NumElement,"The sorted array");

	return 0;
}</span>


最终的执行结果如图示:



图2 归并排序程序执行结果

补充:

接下来介绍一下用非递归的方法来实现归并排序。

基本思路:非递归的方法是用迭代,将数组中相邻元素两两配对,构成n/2组长度为2的排好序的子数组段,然后再将它们排序成长度为4的子数组段,如此循环下去,直至整个数组都排好序。

这种非递归的排序方法是自底向上,循环展开的;而采用分治方法的归并排序则是自顶向下的。

c代码如下,仅列出排序函数:

void MergeSort_iteration(ElementType A[], int N)
{
	int LeftMin, LeftMax, RightMax, RightMin, next;
	ElementType *TmpArray;
	TmpArray = (ElementType *)malloc(N * sizeof(ElementType));
	if(TmpArray != NULL){
		for(int i=1;i<N;i*=2){
			for(LeftMin=0;LeftMin<N-i;LeftMin=RightMax){
				LeftMax = RightMin = LeftMin + i;
				RightMax = LeftMax + i;
				if(RightMax > N)
					RightMax = N;

				next = 0;
				while(LeftMin < LeftMax && RightMin < RightMax)
					TmpArray[next++] = A[LeftMin] > A[RightMin] ? A[RightMin++] : A[LeftMin++];

				//如果右半边已到头,直接把表左半边的元素复制到表右半边的最后
				while(LeftMin < LeftMax)
					A[--RightMin] = A[--LeftMax];
				//然后接着把剩余的元素从后到前复制回去
				while(next > 0)
					A[--RightMin] = TmpArray[--next];
			}
		}
	}
	else{
		fprintf(stderr,"No space for temporary array");
	}

	free(TmpArray);
}
程序运行结果如图:



这里要明白,递归是把复杂的问题求解分解成规模简单,形式相同的问题求解,通过函数的自身调用来实现,有可能有一系列的重复计算造成效率损失。

而迭代式利用变量原值推算出变量的下一个新值。

转载请标明出处,原文地址:/article/1490824.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: