分治的典型应用:归并排序(C++实现)

分治的基本概念

分治,字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
分治法,将问题分(divide)成一些小的问题也就是子问题然后递归求解,而治(conquer)则是将分所得到的各个解合并在一起,即分而治之。

归并排序

归并排序(Merge Sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

举个实例,比如有一个待排序数组{6,3,4,2,7,8,1,9,5,10},归并排序图解如下:
归并排序
如图所示,归并排序是一个递归加分治的思想完成的,对于数组问题,总的来说就是把左子数组排好序再把右子数组排好序,最后对左右有序数组进行归并(即一起排序的过程)。

代码如下:

/*
s:start m:middle e:end
*/
#include<iostream>
using namespace std;
int a[10] = {6,3,4,2,7,8,1,9,5,10}; 
int b[10];
void Merge(int a[],int s,int m,int e,int tmp[])
{
 	//将数组 a 的局部 a[s,m]和 a[m+1,e]合并到 tmp,并保证 tmp有序,然后再拷贝回 a[s,m] 
 	int pb=0;
 	int p1=s,p2=m+1;
 	while(p1 <= m && p2 <= e)
 	{
  		if(a[p1] <= a[p2])//如果是if(a[p1] < a[p2])那么归并排序就不稳定了
   			tmp[pb++]=a[p1++];
  		else
   			tmp[pb++]=a[p2++];
 	}
 	while(p1 <= m)
 	{
  		tmp[pb++]=a[p1++];
 	}
 	while(p2 <= e)
 	{
  		tmp[pb++]=a[p2++];
 	}
 	for(int i=0; i<e-s+1; ++i)
  		a[s+i]=tmp[i]; 
}
void MergeSort(int a[],int s,int e,int tmp[])
{
 	//通过二分(折半)划分子序列并且通过递归不断的去划分   
 	if(s<e)//当子序列中只有一个元素时结束递归  
 	{
  		int m=s+(e-s)/2;
  		MergeSort(a,s,m,tmp);//左子序列  
  		MergeSort(a,m+1,e,tmp);//右子序列    
  		Merge(a,s,m,e,tmp);//左右子序列合并并保证有序  
 	}
}
int main()
{
 	int size=sizeof(a)/sizeof(int);
 	MergeSort(a,0,size-1,b);
 	for(int i=0; i<size; i++)
  		cout << a[i] << " ";
 	cout << endl;
 	return 0;
}

上述代码有两个十分重要的函数,其中第一个是Merge()函数,还有一个叫MergeSort()函数,代码中已经给出了这两个函数的相关注释,所以这里不再详细解释。
运行截图如下:

运行截图1
这里为了使归并排序过程更容易理解,将Merge()函数中的代码:

for(int i=0; i<e-s+1; ++i)
    a[s+i]=tmp[i]; 

改为

for(int i=0; i<e-s+1; ++i)
{
 	a[s+i]=tmp[i];
  	cout << a[s+i] << " ";  
}
cout << endl;

这样当每次调用Merge()函数就会输出此次归并后的子序列,使得对归并理解更加清晰。

改了代码后的程序运行截图如下:

运行截图2
结合着第一张图也就是归并排序图解一起看,是不是更加的清晰!!!
总结:
数组进行归并排序任务可以如下完成:

  1. 把前一半排序(左子数组)
  2. 把后一半排序(右子数组)
  3. 把两半归并到一个新的有序数组,然后再拷贝回原数组,排序完成。

最后提醒一下待排序数组{6,3,4,2,7,8,1,9,5,10},数组下标是从0到9,所以一开始代码中s=0,e=9。
这里再推荐一篇对于归并排序写得很好的文章,超链接如下:
漫画:什么是归并排序?

关于归并排序的时间复杂度

对n个元素进行排序的时间:
T(n) = 2 * T(n/2) + a * n (a是常数,具体多少不重要)
= 2 * (2 * T(n/4)+a * n/2) + a * n
= 4 * T(n/4) + 2 * a * n
= 4 * (2 * T(n/8) + a * n/4) + 2 * a * n
= 8 * T(n/8) + 3 * a * n

= 2^k * T(n/2^k) + k * a * n
一直做到 n/2^k = 1 (此时 k = log2n),(2为底数)
T(n) = 2^k * T(1) + k * a * n
= 2^k * T(1) + k * a * n
= 2^k + k * a * n
= n + a * (log2n) * n (2为底数)
所以归并排序时间复杂度为O(nlogn)

感谢阅读,若有错误还请指出,如果有什么不懂的也可以留言!

blackflag

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值