归并排序
归并排序(MergeSort)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(分而治之-Divide and Conque)的一个典型应用,也是目前较为流行的经典算法之一。归并排序可分为二路归并、三路归并、多路归并等,本文基于最普遍的二路归并来讲述。
归并排序步骤
首先,我们应该知道,分治法思想可以应用于在生活上所遇到的问题,就是先将一个大问题切分为几个小问题,把小问题解决了,再合并起来,一个大问题就能轻松解决了。相对于这里来说,就是将一个未排好序的序列切分为两个子序列,单独对这两个子序列进行排序然后再合并到一起就能得到一个排好序的序列。
总的来说,归纳为两个步骤:
Step 1: 拆分
将序列拆分为两个子序列,假设mid
是array
中的中间值,那么arrray
以mid
为边界就可以分为两个子序列array[0...mid],array[mid+1...end]
;
Step2 :有序归并
在这个步骤里面,我们需要将两个有序的子序列合并,合并的时候保证是有序的。这里可能说得有点模糊,但是这里当你在拆分子序列成为两个单独的一个元素的时候,这两个子序列就是有序的了,所以要求在第一次合并的时候要是两个元素的合并,这样回溯回去得到的序列就是有序的,所以第一次合并之前必须是序列被拆分到不可再分。
伪代码理解
//p 是起始下标 r是末尾下标(最后一个元素)
MergeSort(A, p, r)
If p == r
return;
q = (p+r)/2;
mergeSort(A, p, q)
mergeSort(A, q+1, r)
merge(A, p, q, r)
这里对归并的理解建立与对递归的理解之上,上述的伪代码正式我刚刚描述的那两个步骤的体现,首先获取序列的中间的部分,再分别对两个对两个子序列进行分解排序,分解排序完毕后,再进行有序的合并,这里递归的终止条件就是起始下标与最后一个元素的下标相等的时候,这两者相等的时候就说明该序列就只有一个元素的时候。
重点:
何为拆分排序?就是不断拆分到只有一个元素的时候,这两个序列自然而然就可以看作有序的,再根据有序的合并,就能返回一个排好序的序列。
代码实现
合并
合并就是将两个有序的子序列进行合并,如何合并呢?相信看了上面图示也应该知道是如何去合并了。
假设 i,j
是这两个序列的起始下标
step 1:
当i,j
两个都没有一个到达数组末尾的时候,判断谁小谁先放(谁大谁先放),然后下标地递增。
while(i <= _mid && j <= _end){
if(_array[i]<_array[j]){
auxiliaryArray[k++] = _array[i++];
}else{
auxiliaryArray[k++] = _array[j++];
}
}
step 2:
上面的步骤执行完毕之后,如果两个子序列中有一个还有元素,就将剩余的元素全部赋值给辅助的数组
while(i <=_mid){
auxiliaryArray[k++] = _array[i++];
}
while(j <= _end){
auxiliaryArray[k++] = _array[j++];
}
step 3:
将辅助数组的元素都移到员数组中。
for(int i = _start;i <= _end;i++){
_array[i] = auxiliaryArray[i-_start];
}
函数完整部分
void _merge(int * _array,int _start,int _mid,int _end){
//这里这个声明定义(new) 可以看作
// int * auxiliaryArray = (int *)malloc(sizeof(int)*_end+);
int * auxiliaryArray =new int[_end+1];
int i = _start;
int j = _mid+1;
int k = 0;
while(i <= _mid && j <= _end){
if(_array[i]<_array[j]){
auxiliaryArray[k++] = _array[i++];
}else{
auxiliaryArray[k++] = _array[j++];
}
}
while(i <=_mid){
auxiliaryArray[k++] = _array[i++];
}
while(j <= _end){
auxiliaryArray[k++] = _array[j++];
}
for(int i = _start;i <= _end;i++){
_array[i] = auxiliaryArray[i-_start];
}
}
归并排序函数
拆分排序子序列然后合并
void _mergeSort(int * _array,int _start,int _end){
if(_start == _end){
return ;
}
int _mid = (_start+_end)/2;
_mergeSort(_array,_start,_mid);
_mergeSort(_array,_mid+1,_end);
_merge(_array,_start,_mid,_end);
}
执行分析
1 6 9 4 2 7 8 64 4 5 7
1 6 9 4 2 7
1 6 9
1 6
1
6
1 6
9
1 6 9
4 2 7
4 2
4
2
2 4
7
2 4 7
1 2 4 6 7 9
8 64 4 5 7
8 64 4
8 64
8
64
8 64
4
4 8 64
5 7
5
7
5 7
4 5 7 8 64
1 2 4 4 5 6 7 7 8 9 64
由以上的执行结果可以看到和我开始的说法一致,先是拆分后是合并。
复杂度分析
空间复杂度 | O(N) |
时间复杂度 | O(NlogN) |
另类实现
int* merge_array(int *arr1, int n1, int* arr2, int n2)
{
int * arr = new int[n1+n2];
int i = 0,j = 0,k = 0;
while(i < n1 || j < n2)
{
if((i < n1) && (j <n2) && (arr1[i]<arr2[j]))
{
arr[k++] = arr1[i];
i++;
}
else if((i < n1) && (j <n2) && (arr1[i]>= arr2[j]))
{
arr[k++] = arr2[j];
j++;
}
else if((i < n1) && (j >= n2))
{
for(; i < n1; i++ )
{
arr[k++] = arr1[i];
}
}
else if((i >= n1) && (j < n2))
{
for(; j < n2; j++ )
{
arr[k++] = arr2[j];
}
}
}
for(int i = 0; i < k; i++)
{
arr1[i] = arr[i];
}
delete arr;
return arr1;
}
int* merge_sort(int *arr, int n)
{
if(n >= 2)
{
int *arr1 = &arr[n/2];
arr = merge_sort(arr,n/2);
if(n%2 == 0){
arr1 = merge_sort(arr1,n/2);
arr = merge_array(arr,n/2,arr1,n/2);
}else{
arr1 = merge_sort(arr1,n/2+1);
arr = merge_array(arr,n/2,arr1,n/2+1);
}
}
return arr;
}