二路归并排序的基本思想

设数组a中存放了n个数据元素,初始时把它们看成是n个长度为1的有序子数组,然后从第一个有序子数组开始,把相邻的有序子数组两两合并,得到[n/2]个长度为2的新的有序子数组(当n为奇数时,最后一个新的有序子数组的长度为1)。对这些新的有序子数组再进行两两归并。如此重复,直到得到一个长度为n的有序数组为止。

一次二路归并排序算法的目标是把若干个长度为k的相邻有序子数组从前、向后进行两两归并,得到个数减半的长度为2k的相邻有序子数组。算法设计中要考虑的一个问题是:若元素个数为2k的整数倍,则两两归并正好完成n个数据元素的一次二路归并;若元素个数不为2k的整数倍,则当归并到最后一组时,剩余的元素个数会不足2k个,这时的处理方法是:
① 若剩余的元素个数大于k而小于2k,则把前k个元素作为一个子数组,把剩余的元素作为最后一个子数组。
② 若剩余的元素个数小于k时,也即剩余的元素个数只够一组时,则不用再进行两两归并排序。

一次二路归并

void Merge(DataType a[], int n, DataType swap[], int k){
	//一次二路归并排序后的有序子序列存于数组swap中,k为有序子数组的长度
	int m = 0, u1, u2, l2, i, j;
	
	int l1 = 0;//第一个有序子数组的下界为0;
	while(l1+k <=n-1){
		l2 = l1+k;//计算第二个有序数组的下界 
		u1 = l2-1;//计算第一个有序数组的上界
		u2 = (l2+k-1 <=n-1) ? l2+k-1:n-1;//计算第二个有序数组上界
		
		for(i=l1,j=l2;i<u1&&j<=u2;m++){//两个有序子数组合并
		 
			if(a[i].key<=a[j].key){
				swap[m] = a[i];
				i++;
			}else{
				swap[m] = a[j];
				j++;
			}
		} 
		
		while(i<=u1){//子数组2已经归并完,将子数组1中剩余的元素存放到数组swap中 
			swap[m] = a[i];
			m++;
			i++; 
		}
		
		while(j<=u2){//子数组1已经归并完,将子数组2中剩余的元素存放到数组swap中 
			swap[m] = a[j];
			m++;
			j++; 
		}
		
		l1 = u2+1;
	} 
	
	for(i=l1;i<n;i++,m++){//将原始数组中只够一组的数据元素顺序存放到数组swap中 
		swap[m] = a[i];
	}
}

二路归并排序

void MergeSort(DataType a[], int n){
	int i, k=1;//归并长度从1开始
	DataType *swap;
	
	swap = (DataType *)malloc(sizeof(DataType)*n);//申请动态数组空间
	
	while(k<n){
		Merge(a,n,swap,k);//调用归并函数
		
		for(i=0;i<n;i++){
			a[i] = swap[i];//将元素从临时数组swap放回数组a中 
		} 
		
		k = 2*k;//归并长度加倍 
	} 
	
	free(swap);//释放动态数组空间 
	 
} 

测试代码

#include <stdio.h>
#include <stdlib.h>

typedef int KeyType; 

typedef struct{
	KeyType key;
}DataType;

void Merge(DataType a[], int n, DataType swap[], int k){
	//一次二路归并排序后的有序子序列存于数组swap中,k为有序子数组的长度
	int m = 0, u1, u2, l2, i, j;
	
	int l1 = 0;//第一个有序子数组的下界为0;
	while(l1+k <=n-1){
		l2 = l1+k;//计算第二个有序数组的下界 
		u1 = l2-1;//计算第一个有序数组的上界
		u2 = (l2+k <=n-1) ? l2+k-1:n-1;//计算第二个有序数组上界
		
		for(i=l1,j=l2;i<u1&&j<=u2;m++){//两个有序子数组合并
		 
			if(a[i].key<=a[j].key){
				swap[m] = a[i];
				i++;
			}else{
				swap[m] = a[j];
				j++;
			}
		} 
		
		while(i<=u1){//子数组2已经归并完,将子数组1中剩余的元素存放到数组swap中 
			swap[m] = a[i];
			m++;
			i++; 
		}
		
		while(j<=u2){//子数组1已经归并完,将子数组2中剩余的元素存放到数组swap中 
			swap[m] = a[j];
			m++;
			j++; 
		}
		
		l1 = u2+1;
	} 
	
	for(i=l1;i<n;i++,m++){//将原始数组中只够一组的数据元素顺序存放到数组swap中 
		swap[m] = a[i];
	}
}

void MergeSort(DataType a[], int n){
	int i, k=1;//归并长度从1开始
	DataType *swap;
	
	swap = (DataType *)malloc(sizeof(DataType)*n);//申请动态数组空间
	
	while(k<n){
		Merge(a,n,swap,k);//调用归并函数
		
		for(i=0;i<n;i++){
			a[i] = swap[i];//将元素从临时数组swap放回数组a中 
		} 
		
		k = 2*k;//归并长度加倍 
	} 
	
	free(swap);//释放动态数组空间 
	 
} 

void main(void){
	int i=0;
	DataType array[9]={72,73,71,23,94,16,5,68,64};
	DataType array2[9]; 
	printf("原数组顺序:");
	for(i=0;i<9;i++){
		printf("%d ",array[i]);
	}
	printf("\n\n");
	MergeSort(array,9);
	
	printf("二路归并排序之后结果:");
	for(i=0;i<9;i++){
		printf("%d ",array[i]);
	} 
	getch(0);
} 

在这里插入图片描述
上图所示是二路归并排序算法各次归并排序过程的一个示例。

复杂度分析

对n个元素进行一次二路归并排序时,归并的次数约为lbn,任何一次的二路归并排序元素的比较次数都约为n-1,所以,二路归并排序算法的时间复杂度为O(nlbn)。

二路归并排序时使用了n个临时内存单元存放数据元素,所以,二路归并排序算法的空间复杂度为O(n)。

由于二路归并排序算法是相邻有序子表两两归并,对于关键字相同的数据元素,能够保证原来在前边的元素排序后仍在前边,因此,二路归并排序算法是一种稳定的排序算法。

前边讨论过的几个时间复杂度为O(nlbn)的排序算法都是不稳定的排序算法,而二路归并排序算法不仅时间复杂度是O(nlbn),而且还是一种稳定的排序算法。这是二路归并排序算法的最大特点。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐