归并:将两个或两个以上的有序表组合成一个新有序表。
基本思路:
归并排序采用了分治策略(divide-and-conquer),就是将原问题分解为一些规模较小的相似子问题,然后递归解决这些子问题,最后合并其结果作为原问题的解。(也就是将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。)
先递归的把数组划分为两个子数组,一直递归到数组中只有一个元素,然后再调用函数把两个子数组排好序,因为该函数在递归划分数组时会被压入栈,所以这个函数真正的作用是对两个有序的子数组进行排序;
可以将A,B组各自再分成两组。依次类推,当分出来的小组只有一个数据时(即为该层递归的返回条件,此时first=last),可以认为这个小组组内已经达到了有序,然后再合并相邻的两个小组就可以了。这样通过先递归的分解数列,再合并数列就完成归并排序。
首先考虑下如何将将两个有序数列合并。这个非常简单,只要从比较两个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。
//将有序数组a[]和b[]合并到c[]中
void MemeryArray(int a[], int n, int b[], int m, int c[])
{
int i, j, k;
i = j = k = 0;
while (i < n && j < m)
{
if (a[i] < b[j])
c[k++] = a[i++];
else
c[k++] = b[j++];
}
while (i < n)
c[k++] = a[i++];
while (j < m)
c[k++] = b[j++];
}
时间复杂度为O(n)
再看归并的操作:先递归分解数列,再对数列合并排序,
每层递归的返回条件就是last=first,即此分组中只有一个元素:
//将有两个有序数列a[first...mid]和a[mid...last]合并。
void mergearray(int a[], int first, int mid, int last, int temp[])
{
int i=first, j=mid+1;
int m=mid, n=last;
int k=0;
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++];
for(i=0;i<k;i++)
a[first+i]=temp[i];
}
void mergesort(int a[], int first, int last, int temp[])
{
if(first<last)
{
int mid=(first+last)/2;
mergesort(a,first,mid,temp); //左边有序
mergesort(a,mid+1,last,temp); //右边有序
mergearray(a,first,mid,last,temp); //将两个有序数列合并
}
}
复杂度
时间复杂度
归并排序的过程图与完全二叉树十分相似,算法中把记录扫描一遍耗费O(n),
又由完全二叉树的深度知:整个归并排序需要[log2 n]次,故总的时间复杂度为O(n * log n)
换句话说,设数列长n,将数列分为小数列需要log n次,每一步都在合并有序数列,时间复杂度为O(n),
故总的时间复杂度为O(n * log n) (同时间复杂度的还有希尔排序,堆排序,快速排序,归并排序效率较高)
空间复杂度
归并排序在归并过程中需要与原始记录序列同等数量的存储空间存放归并结果以及递归时深度为log2 n的栈空间,
故空间复杂度O(n + log n)
稳定性
mergearray()
该函数中有if(a[i]<a[j])之类的语句,说明其为两两比较,无跳跃情况,所以是稳定的算法
综上,归并排序是一种比较占用内存,但效率高且稳定的算法
测试代码:
#include<stdio.h>
#include<stdlib.h>
//归并排序
//将两个有序数列a[first...mid]和a[mid...last]合并。
void mergearray(int a[], int first, int mid, int last, int temp[])
{
int i=first, j=mid+1;
int m=mid, n=last;
int k=0;
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++];
for(i=0;i<k;i++)
a[first+i]=temp[i];
}
void mergesort(int a[], int first, int last, int temp[])
{
if(first<last)
{
int mid=(first+last)/2;
mergesort(a,first,mid,temp); //左边有序
mergesort(a,mid+1,last,temp); //右边有序
mergearray(a,first,mid,last,temp); //将两个有序数列合并
}
}
int main()
{
int a[] = {2,23,34,43,45,6,7,8,5,4,56,78,80,211,222,444,111};
int length = sizeof(a)/sizeof(a[0]); //sizeof(a)计算数组a所占内存,sizeof(a[0])计算单个元素所占内存
int p[17]={0};
int k;
mergesort(a,0,length-1,p);
for (int i = 0; i < length; i++)
{
printf("%d\t",a[i]);
k++;
if(k%8==0)
printf("\n");
}
printf("\n");
return 0;
}