【算法设计与分析】归并(合并)排序(分治算法经典问题)

本文主要代码及思路来源于:【算法设计与分析(第五版)】【王晓东】

1、基本思想

待排序元素分成大小大致相同的2个子集合,

分别对2个子集合进行排序,

最终将排好序的子集合合并成为所要求的排序

//递归描述
void MergeSort(Type a[ ], int left, int right)
{
      if (left

 

2、改进——消除递归

对于算法MergeSort,还可以从多方面对它进行改进。例如,从分治策略的机制入手,容易消除算法中的递归

事实上,算法MergeSort的递归过程只是将待排序集合一分为二,直至待排序集合只剩下一个元素为止,然后不断合并两个排好序的数组段

此机制,可以首先将数组a中相邻元素两两配对。用合并算法将它们排序,构成n/2组长度为2的排好序的子数组段,然后再将它们排序成长度为4的排好序的子数组段,如此继续下去,直至整个数组排好序。

//消去递归后的合并排序算法
void MergeSort(int *a,int n)
{
	int b[n];
	int s=1;//子数组长度
	while(s

 【算法设计与分析】归并(合并)排序(分治算法经典问题)_第1张图片

 

3、长长的代码们即将来袭

1、书上代码较完整示例(见书P23(第五版))

#include 
#include 
using namespace std;

template 
void Merge(Type c[], Type d[], int l, int m, int r)
// 合并c[l:m]和c[m+1:r]到d[l:r]
{
	int i=l;   // 左子数组的当前处理索引 
	int j=m+1; // 右子数组的当前处理索引
	int k=l;   // 合并结果索引
	
	while (i<=m && j<=r) 
	{
		if (c[i]<=c[j]) d[k++]=c[i++];
		else d[k++]=c[j++];
	}
	
	if (i>m) for(int q=j; q<=r; q++) d[k++]=c[q];
	else for(int q=i; q<=m; q++) d[k++]=c[q];
}

template 
void MergePass(Type x[], Type y[], int s, int n)
// 将x中长度为s的相邻子数组合并,结果存入y中
// n: 数组元素数量 
{
	int i=0; // 子数组起始位置
	
	// 合并度为s的相邻子数组 
	while (i+s+s<=n) 
	{
		Merge(x, y, i, i+s-1, i+s+s-1);
		i+=s+s;
	}
	
	// 剩下元素数量少于2s 
	if (i+s
void MergeSort(Type a[], int n)
{
	Type *b=new Type [n];
	int s=1; // 子数组长度
	while (s
void OutputArray(Type elements[], int num_elements)
{
	cout<

2、结合书上代码复写版

#include 
using namespace std;

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=l;//左右子数组的当前处理索引,合并后的索引
	while(i<=m && j<=r) {
		if(c[i]<=c[j])  d[k++]=c[i++];
		else d[k++]=c[j++];
	}
	if(i>m)//左子数组全部并入数组d,同时右子数组还有值未并入数组d
		for(int q=j; q<=r; q++)
			d[k++]=c[q];
	else
		for(int q=i; q<=m; q++)
			d[k++]=c[q];
}

void MergePass(int *x,int *y,int s,int n)
// 将x中长度为s的相邻子数组合并,结果存入y中
// n: 数组元素数量
{
	int i=0;  //之数组起始位置
	while(i+2*s<=n) {
		Merge(x,y,i,i+s-1,i+2*s-1);
		i+=2*s;
	}
	if(i+s

3、书上代码一些细节优化后的参考版本

//二路归并排序算法
#include 
#include 

void disp(int a[], int n)			//输出a中所有元素
{
    int i;
    for(i = 0; i < n; i++)
        printf("%d ", a[i]);
    printf("\n");
}

void Merge(int a[], int low, int mid, int high)
//将a[low..mid]和a[mid+1..high]两个相邻的有序子序列归并为一个有序子序列a[low..high]
{
    int *tmpa;
    int i = low, j = mid + 1, k = 0;		//k是tmpa的下标,i、j分别为两个子表的下标

    tmpa = (int *)malloc((high - low + 1) * sizeof(int)); //动态分配空间

    while(i <= mid && j <= high)	//在第1子表和第2子表均未扫描完时循环
        if(a[i] <= a[j]) {	//将第1子表中的元素放入tmpa中
            tmpa[k] = a[i];
            i++;
            k++;
        }
        else {				//将第2子表中的元素放入tmpa中
            tmpa[k] = a[j];
            j++;
            k++;
        }
    while(i <= mid) {		//将第1子表余下部分复制到tmpa
        tmpa[k] = a[i];
        i++;
        k++;
    }
    while(j <= high) {		//将第2子表余下部分复制到tmpa
        tmpa[k] = a[j];
        j++;
        k++;
    }
    for(k = 0, i = low; i <= high; k++, i++) 		//将tmpa复制回a中
        a[i] = tmpa[k];
    free(tmpa);						//释放tmpa所占内存空间
}

void MergePass(int a[], int length, int n)	//一趟二路归并排序;合并排好序的相邻数组段
{
    int i;
    for(i = 0; i + 2 * length - 1 < n; i = i + 2 * length)	//归并length长的两相邻子表
        Merge(a, i, i + length - 1, i + 2 * length - 1);
    if(i + length - 1 < n)					//余下两个子表,后者长度小于length
        Merge(a, i, i + length - 1, n - 1);		//归并这两个子表
}

void MergeSort(int a[], int n)			//二路归并算法
{
    int length;
    for(length = 1; length < n; length = 2 * length)
        MergePass(a, length, n);
}

int main()
{
    int n = 11;
    int a[] = {2, 5, 11, 7, 10, 6, 9, 4, 3, 8, 1};
    printf("排序前: ");
    disp(a, n);
    MergeSort(a, n);
    printf("排序后: ");
    disp(a, n);
}

4、改进——自然合并

自然合并排序是合并排序算法MergeSort的一个变形

上述合并排序算法中,第一步合并相邻长度为1的子数组段,这是因为长度为1的子数组段是已排好序的。事实上,对于初始给定的数组a,通常存在多个长度大于1的已自然排好序的子数组段

例如,若数组a元素{48371562},则自然排好序的子数组段有{48}{37}{156}{2}。用1次对数组a的线性扫描就足以找出所有这些排好序的子数组段。然后将相邻的排好序的子数组段两两合并,构成更大的排好序的子数组段。对上面的例子,经一次合并后可得到2个合并后的子数组段{3478}{1256}。继续合并相邻排好序的子数组段,直至整个数组已排好序。这2个数组段再合并后就得到{12345678}

//自然合并排序
#include 
#include 

using namespace std;

/* run this program using the console pauser or add your own getch, system("pause") or input loop */

template 
void Merge(Type c[], Type d[], int l, int m, int r)
// 合并c[l:m]和c[m+1:r]到d[l:r]
{
	int i=l;   // 左子数组的当前处理索引 
	int j=m+1; // 右子数组的当前处理索引
	int k=l;   // 合并结果索引
	
	while (i<=m && j<=r) 
	{
		if (c[i]<=c[j]) d[k++]=c[i++];
		else d[k++]=c[j++];
	}
	
	if (i>m) for(int q=j; q<=r; q++) d[k++]=c[q];
	else for(int q=i; q<=m; q++) d[k++]=c[q];
}

template 
void NaturalMergePass(
Type x[], Type y[], int seg_i0[], int s, int nseg)
// 将x中长度为s的相邻子段组合并,结果存入y中
// seg_i0: 每个子段的起始索引 
// nseg: 子段数量 
{
	int i=0; // 子段组起始位置
	
	// 合并长度为s的相邻子段组 
	while (i+s+s<=nseg) 
	{
		Merge(x, y, seg_i0[i], seg_i0[i+s]-1, seg_i0[i+s+s]-1);
		i+=s+s;
	}
	
	// 剩下子段数量少于2s 
	if (i+s
void NaturalMergeSort(Type a[], int n)
{
	// 寻找子段起始位置和子段数量 
	int *seg_i0=new int [n+1];
	seg_i0[0]=0;
	int nseg=1;
	for (int i=1; i
void OutputArray(Type elements[], int num_elements)
{
	cout<

 

5、简单分析

最坏时间复杂度:O(nlogn)

平均时间复杂度:O(nlogn)

辅助空间:O(n)

你可能感兴趣的:(算法设计与分析【王晓东】,C++,递归和分治)