排序算法——归并排序
如果觉得前面的几种排序很简单的话,那么对于归并排序算法的实现就稍微难一点点了,我相信很多同学学完数据结构后,并不能很熟练的写出归并算法,
然而,归并排序以及归并排序的变体在互联网在线笔试题是很常见的。接下来就来学习数组的归并排序,为何要强调是数组的归并排序,因为,今天在OJ上刷题
的时候,遇到了单链表的归并排序,因此,有必要将其区分开来,后面会写一篇单链表归并排序算法。
这里所说的归并指的是二路归并,即将两个有序的子表,归并为一个表,使这个表中的元素仍然有序。假设有两个子表存放在同一个数组相邻的位置上:
A[s, mid]表示第一个子表,A[mid+1, t]表示第二个子表,现在如果把这两个子表合并成,并且使的数组A[s, t]有序?可以借助额外的内存空间R,同时从子表
A[s, mid]和A[mid+1, t]中取元素进行比较,将较小的存在辅助空间R中,最后将辅助空间R中的元素全部复制回数组A中,排序完成。
两个子表的合并算法,代码如下:
#include <stdlib.h> #include <stdio.h> void Merge(int A[], int s, int mid, int t) // 给定数组的区间以及中间元素的位置索引 { int i = s, j = mid + 1, k = 0; int *R; R = (int *)malloc(sizeof(int)*(t-s+1)) // 动态分配内存 while(i<=mid && j<=t) { if(A[i] < A[j]) // 将第一个子表中的元素存放在R中 { R[k] = A[i]; k++; i++; } else { R[k] = A[j]; // 将第二个子表中的元素存放在R中 k++; j++; } } while(i<=mid) // 将剩余的部分存入R { A[k] = A[i]; k++; i++ } while(j<=t) // 将剩余的部分存入R { A[k] = A[j]; k++; j++; } for(k = 0, i = s; i <= t; i++, k++) // 将R中的元素拷贝到A中 A[i] = R[k]; }
Merge()函数只是实现了一次归并,如果一个序列,被划分为多个长度为length的子序列(最后一个子序列长度可以小于length),那么就需要多次两两合并,
也就需要多次调用Merge()函数,让相邻的两个子序列合并。这里需要注意的是:如果一个序列被分为偶数个子序列,那么相邻两个子序列恰好可以两两合并,
如果一个序列被分为奇数个子序列,则最后一个子序列不参与合并。
多个子序列两两合并的代码如下:
void MultiMerge(int A[], int length, int n) { int i; for(i = 0; i+2*length-1 < n; i = i+2*length) // 将相邻的子表两两合并 Merge(A, i, i+length-1, i+2*length-1); if(i+length-1 < n) // 如果i+2*length-1>=n 且 i+length-1<n Merge(A, i, i+length-1; n-1); // 说明序列被分为偶数个子序列,且最后一个子序列长度小于length }
现在完成了将多个子序列两两合并,但是这只实现了归并排序的一趟排序。因此,我们可以将数组A看做是n个长度为1的子序列,然后将其子序列两两合并,然后,
再将其看做n/2个长度为2的子序列,然后两两合并,以此类推;最后整个序列A都是有序的。
二路归并算法实现如下:
void MergeSort(int A[], int n) { int length; for(length = 1; length < n; length = 2*length) MultiMerge(A, length, n); }
归并排序的时间复杂度为O(nlog2n),时间复杂度为0(n),归并排序是稳定的排序。