分治的基本概念
分治,字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
分治法,将问题分(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()函数,代码中已经给出了这两个函数的相关注释,所以这里不再详细解释。
运行截图如下:
这里为了使归并排序过程更容易理解,将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()函数就会输出此次归并后的子序列,使得对归并理解更加清晰。
改了代码后的程序运行截图如下:
结合着第一张图也就是归并排序图解一起看,是不是更加的清晰!!!
总结:
数组进行归并排序任务可以如下完成:
- 把前一半排序(左子数组)
- 把后一半排序(右子数组)
- 把两半归并到一个新的有序数组,然后再拷贝回原数组,排序完成。
最后提醒一下待排序数组{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)
感谢阅读,若有错误还请指出,如果有什么不懂的也可以留言!