归并排序
归并排序
1 归并排序
1.1 原理
归并排序是Divide and Conquer思想,也就是分而治之。先看划分的子问题。
假设对已经存在并且有序的两个序列A、B进行合并,如何进行?很显然可以新建一个序列C,长度是A与B长度之和,从左至右依次扫描A、B,将较小的项放入C直到A、B中所有元素都扫描结束即可。
1.2 实现
对于给定的数组A,A[p..r]与A[r+1..q]两部分均已有序,下面实现对此两部分合并,使A[p..q]最终有序。
void merge(int A[],int p,int r,int q) { int i,j,k; int n1=r-p+1; int n2=q-r; int* L = (int*)malloc((n1+1)*sizeof(int)); int* R = (int*)malloc((n2+1)*sizeof(int)); i=j=0; while(i<n1) { L[i]=A[p+i]; i++; } L[i] = MAXNUM; while(j<n2) { R[j] = A[r+j+1]; j++; } R[j] = MAXNUM; i=0; j=0; for(k=p;k<=q;k++) { if(L[i]<=R[j]) { A[k]=L[i]; i++; } else { A[k]=R[j]; j++; } } free(L); free(R); }
代码中申请数组时多申请一个int的尺寸,用来存放哨兵,这个值为正无穷大,这样即不必检查两个数组的边界,并且会正确把所有元素插入A中,因为任何数都小于正无穷大。 接下去的问题是怎样把一个无序数组来使用上面的方面排序。一个数组分成A[p..r]与A[r+1..q]两部分,对于这两部分再进行划分,以此重复,直到划分的两个子序列已经有序即可。代码实现如下:
void mergeSort(int A[],int start,int end) { int mid=(start+end)/2; if(end > start) { mergeSort(A,start,mid); mergeSort(A,mid+1,end); merge(A,start,mid,end); } }
这是一个bottom-up的问题,它的表现是一棵二叉树,在最底层的序列只包含一个数,很明显单独的数不必进行排序。从最底层依次向上合并到顶点,整个过程也随之结束。
1.3 性能评估
上面提到,它的表现是一棵二叉树,而二叉树的遍历则需要花费O(nlgn)的时间复杂度。具体证明可以参见算法导论。
归并的思想是分治,先把复杂问题分解成可以解决的小问题,再把小问题解决,最后把解决的问题做合并,这样就解决整个问题。 而插入排序的思想是增量(incremental)策略,一点一点增加已排序的数组,直到排序全部完成。两个思想正好相反。