归并排序作为一个能够在O(nlogn)的复杂度下完成排序的算法之一,其对于比较大的数据量有着不错的排序效果。下面解析一下归并排序的算法与实现。
归并排序基于分治的思想,也就是将一个大的区间分为两个小的区间,然后只要左边实现了有序,右边实现了有序,接下去也就是并,将两个小区间合并为大区间,实现整体有序。
下面是简单的演示:
初始也就是序列4 1 5 3 2,我们要实现升序序列。
我们以l为左边界,r为有边界,m=(l+r)/2为中点,将区间划分为[l,m],(m,r]两个小区间,然后接下去对让小区间进行归并,实现小区间有序。(这实际上是递归的过程,可以在程序中感受一下,这里就不演示了,就假设经过排序之后实现了有序,先略过过程,因为后面还有比较重要的合并操作)。
递归对小区间排序之后,可以实现对小区间内的有序。
然后要进行和合并操作,这里需要一个额外的数组来暂时存储排序后的数组,然后重新赋值给原数组。
那么如何实现合并?因为左边右边实际上都是升序的,所以可以设置两个指针分别指向l和m+1也就是左边区间和右边区间的第一个,记为p和q。
然后比较,将两个数中的小的先放进暂存数组,比如这里1比较小,那么把1放进数组,p后移。
接下来2小,将2放进暂存数组,q后移。
重复这样的过程...然后就可以得到这样的一个数组。
可以看到这个区间实现了有序,然后再把暂存数组中的数字重新赋值给a的[l,r]区间就好了。需要注意的就是需要注意区间的边界问题,左边区间的所有数取完了,那么只要把右边剩下的数字放进去就好了,就不要继续将左边的指针移到右边区间去了。
参考代码
代码里似乎是把中点划分到了右区间,道理是一样的
#include<stdio.h>
int a[] = { 0,1,5,7,4,2,3,6,6,7,5,3,2,6,0,1,1,1,9,2,1,1,8,9,3,2,1 };
int n = sizeof(a) / sizeof(a[0]) - 1;
void print(){
for (int i= 1; i<=n; i++)
printf("%d ", a[i]);
printf("\n");
}
void swap(int i, int j) {
int x = a[i];
a[i] = a[j];
a[j] = x;
}
//guibing_sort()
int b[30000];//额外存储空间---暂存数组
void merge(int l,int m, int r) {//合并区间
int p = l, q = m;
for (int i = 1; i <= r - l + 1;i++) {
if (p<m&&(a[p]<a[q] || q>r))
b[i] = a[p++];
else b[i] = a[q++];
}
for (int i = 1; i <= r - l + 1; i++)//重新赋值
a[l + i - 1] = b[i];
}
void guibing_sort(int l,int r) {
if (l == r)return;//天然有序
if (r - l == 1) {
if (a[l] > a[r])swap(l,r);
return;
}
int m = (l + r) / 2;
guibing_sort(l, m - 1);//左边排序
guibing_sort(m, r);//右边排序
merge(l,m, r);//合并区间
}
int main()
{
printf("排序前:");
print();
guibing_sort(1,n);
printf("排序后:");
print();
return 0;
}