归并排序采用一种分治的算法思想,而分治算法在结构上是递归的。归并排序算法有三个步骤:步骤一(分解):将原问题分解成两个子问题,然后递归将子问题分解为更小的子问题,直至不能分解为止。步骤二(解决):步骤一将原问题分解到最小子问题,这时,每个最小子问题对应的子序列已经有序了。如将{4,3,5,2}分解为{{4},{3},{5},{2}},每个子问题都是有序的。步骤三(归并),从最小子问题处向上合并,在合并的过程中,将子问题序列中的元素插入到原问题序列的相应位置,这时,子序列将变得有序,重复此过程,直至整个序列有序。
可以看到,归并排序有由两个过程组成,分解过程和归并过程。分解过程将原问题分解成更小的子问题,这时只要将序列arr[p...q...r]分成arr[p...q]和arr[q+1...r]两部分。而归并过程的示意图如下:
下面是C的实现
#define LEN 100
#define SENTRY 10000//哨兵
void merge(int arr[],int p,int q,int r)
{
int n1 = q - p + 1;
int n2 = r - q;
int left[LEN];
int right[LEN];
int i, j;
for (i = 0; i < n1; i++)
{
left[i] = arr[p + i];
}
for (j = 0; j < n2; j++)
{
right[j] = arr[q + j+1];
}
left[n1] = SENTRY;
right[n2] = SENTRY;
i = 0, j = 0;
for (int k = p; k <= r; k++)
{
if (left[i] <= right[j])
{
arr[k] = left[i];
i++;
}
else
{
arr[k] = right[j];
j++;
}
}
}
算法首先将原数组分成两部分,然后再进行上图的合并为一个有序数组的过程。需要注意的是,left和right两部分不总是等长的,它们在合并的过程中也不是同时最后一个元素,因此需要设置一个哨兵位,哨兵位的值比数组所有元素的值都要大,这里设置SENTRY为10000。最后的for循环控制循环的次数,而里面的if...else则选择适当的元素插入到原数组中去,只要left或者right任一个子数组的元素已经全部插入到原数组的适当位置,那么剩下的工作就是将另一个数组的所有元素插入到原数组的最后中去。
归并排序的主过程相对简单,C实现如下:
void merge_sort(int arr[], int p,int r)
{
if (p < r)
{
int q = (p + r) / 2;
merge_sort(arr, p, q);
merge_sort(arr, q + 1, r);
merge(arr, p, q, r);
}
}
merge_sort过程中条件p<r控制子问题不再继续分解为更小的问题,当不能分解为更小的子问题时,这时子问题已经是有序的了,于是开始一步步向上合并,直至整个数组有序。
对于序列int arr[]={3,4,1,2},主过程调用过程如下:
归并过程如下: