1、归并排序
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
归并排序主要分为三个步骤:
第一, 分解: 把待排序的 n 个元素的序列分解成两个子序列, 每个子序列包括 n/2 个元素.
第二, 治理: 对每个子序列分别调用归并排序MergeSort, 进行递归操作
第三, 合并: 合并两个排好序的子序列,生成排序结果.
首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。
2、两个子序列的合并
两个子序列:[left,mid],[mid+1,right],要对这个两个数组进行合并并有序。
思想:同时遍历两个子数组,保存数值小的那个元素,并且取下一个;
当其中一个数组已经遍历完毕时,可以把另一个数组的剩余元素全部保存到临时数组;
void MergeArray(int a[], int left, int mid, int right, int temp[])
{
int i,j,m,n,k;
i=left; j=mid+1; k=0;
m=mid; n=right;
while(i<=m && j<=n)
{
if(a[i]<=a[j]) //保存较小者
temp[k++]=a[i++];
else
temp[k++]=a[j++];
}
while(i<=m) //当一个数组遍历完毕,需要把另一个数组的剩余元素全部保存到临时数组
temp[k++]=a[i++];
while(j<=n)
temp[k++]=a[j++];
//Copy temp array to a
for(i=0;i<k;i++)
a[left+i]=temp[i];
}
3、对整个数组归并排序
整个归并过程递归对子数组进行归并,排序
void Merge(int a[], int left, int right, int temp[])
{
if(left<right) //递归条件判断
{
int mid=(left+right)/2;
Merge(a, left, mid, temp); //递归对左子数组排序
Merge(a, mid+1, right, temp); //递归对右子数组排序
MergeArray(a,left,mid,right,temp); //合并左子数组和右子数组
}
}
4、时空复杂度分析
时间复杂度分析:
归并排序时间复杂度公式:
当n=1时可以设T(1)=c
1,当n>1时可以设T(n)=2T(n/2)+c2n
于是上述公式可以转化为:
这样计算出的结果应该是T(n)的上界。下面我们把T(n/2)展开成2T(n/4)+cn/2,然后再把T(n/4)进一步展开,直到最后全部变成T(1)=c1,图示如下:
所有的项加起来就是总的执行时间。这是一个树状结构,每一层的和都是cn,共有logn+1层,因此总的执行时间是cnlogn+cn,相比nlogn来说,cn项可以忽略,因此T(n)的上界是Θ(nlogn)。
如果先前取c1和c2中较小的一个设为c,计算出的结果应该是T(n)的下界,然而推导过程一样,结果也是Θ(nlogn)。既然T(n)的上下界都是Θ(nlogn),显然T(n)就是Θ(nlogn)。
空间复杂度分析:
由于在合并两个子数组的时候,用到了一个临时数组temp,该数组的大小和原始数组a的大小一致,因此
空间复杂度:O(n)