归并排序是一种利用分治思想来排序的算法。其核心思想是
不断合并有序数据,直到所有数据有序为止。
这里介绍二路归并,即初始有序长度为1,然后两两归并,如果重复,直到所有元素有序。
二路归并的递归算法比较简洁,但是实用性很差。因为它调用了递归栈,需要额外的空间log2n次,再加上需要与数据等长度的额外数组,所以空间复杂度是o(n),归并算法的时间复杂度是o(n),一共要归并log2n次,所以时间复杂度是o(nlog2n).
和快速排序和堆排序相比,它的唯一优势是它是一个稳定的排序算法,即相等元素的顺序不会在排序之后改变。所以,我们一般不在内部排序之中使用二路归并,这里我们通过介绍它来体会分治思想,因为归并在外部排序中很有用。
递归版本
#include <stdio.h>
#include <stdlib.h>
#define MAX 2<<30-1 //32位有符号最大整数
void merge(int a[], int left, int mid, int right)
{
int i;
int m = mid-left+1;//前半部分的长度
int n = right - (mid+1)+1;//后半部分的长度
int* arr1 = (int*)malloc(sizeof(int)*(m+1));//多分配一个位置给哨兵卫
int* arr2 = (int*)malloc(sizeof(int)*(n+1));
for (i=0; i<m+1; i++) arr1[i] = a[left+i];
for (i=0; i<n+1; i++) arr2[i] = a[mid+i+1];
arr1[m] = arr2[n] = MAX; //设置哨兵卫,防止边界条件的判断
int p, q;
p = q = 0;
for (i=left; i<=right; i++)
{
if (arr1[p] <= arr2[q]){
a[i] = arr1[p];
p++;
}
else
{
a[i] = arr2[q];
q++;
}
}
free(arr1);
free(arr2);
arr1 = arr2 = NULL;
}
void merge_sort(int a[], int left, int right)
{
if (left < right)
{
int mid = (left+right)/2;
merge_sort(a, left, mid);
merge_sort(a, mid+1, right);
merge(a, left, mid, right);//合并
}
}
int main(void)
{
int a[] = {1, 2, 9, 0, 3, 4, 12, 1, -23, 12, -89, -123};
int i;
int size = sizeof(a)/sizeof(int);
merge_sort(a, 0, size-1);
for (i=0; i<size; i++)
printf("%d ", a[i]);
getchar();
return 0;
}
非递归版本
只是划分过程不一样,合并函数是一样的
void merge_sort(int a[], int n)
{
if (n > 1)
{
int size=1,low,mid,high;
while(size<=n-1)
{
low=0;
while(low+size<=n-1)
{
mid=low+size-1;
high=mid+size;
if(high>n-1)//第二个序列个数不足size
high=n-1;
merge(a,low,mid,high);//调用归并子函数
low=high+1;//下一次归并时第一关序列的下界
}
size*=2;//范围扩大一倍
}
}
}