归并排序
“归”即从小序列归入大序列,“并”即将两个有序序列合并成一个有序序列,归并排序在我个人理解看来就是层层归并过程。
如对数组array={16,7,13,10,9,15,3,2,5,8,12,1,11,4,6,14}进行排序:
两份有序合成一份有序……两份有序合成一份有序,最终有序。
递归排序(Merging Sort)就是利用归并(分治)的思想实现的排序方法。它的原理是假设初始序列有n个记录,则可以看成是n个有序的子序列,每个自子序列的长度为1,然后两两归并,得到n/2个长度为2或1(多出来的)的有序子序列;再两两归并,……,如此重复,直至得到一个长度为n的有序序列为止,这种排序方法成为2路归并排序。
“归”部分
“归”即从小序列归入大序列,当我们以递归方式实现递归排序,首先要将大序列通过不断划分最终形成合理的小序列,之后才是“归”的过程。
将大序列划分为小序列,我们在2路归并排序中是划分出两个子序列,因此我们需要左序列和右序列的首尾位置。
在后来的“归”的过程中我还伴随着“并”(排序),因此我们同样需要大序列的首尾位置。而大序列的首、尾和左序列的首、右序列的尾是重合的,无需多做定义。
即我们可以以首尾下标来划分子序列,在一次递归中我们需要四个数据,s大序列首部,l1左序列尾部,s2右序列首部,l大序列尾部,递归结束条件可以为s1>l2(序列元素不能小于1).
代码实现如下:
public void mergingSort(int[] array) {
MSort(array,0,array.length-1);
}
public static void MSort(int[] array, int s, int l) {
//当序列不能继续划分,直接退出
if(s>=l) {
return;
}
//左序列尾
int l1 = (s+l)/2;
//右序列首
int s2 = l1+1;
//归并左序列
MSort(array,s,l1);
//归并右序列
MSort(array,s2,l);
//对【s,l】区间序列排序
Merge(array,s,l1,l);
}
“并”部分
“并”即将两个有序序列合并成一个有序序列。在“归”代码中,左右序列归并后,归并至的Merge()方法部分就是我们“并”的部分。
将两有序序列合并成一个有序序列可以采用四指针法,即s1存储左序列首部下标,l1存储左序列尾部下表,s2存储右序列首部下标,l2存储右序列尾部下标。
因为两序列有序,因此s1、s2下标元素是两序列的最小值,对比array[s1]和array[s2],将小的那一个存入额外的数组空间,再将该位置下标++,使s1或s2一直指向两序列未排序部分的最小值,重复这一过程,直至其中一个序列被遍历完。
其中一个序列被遍历完,另一个序列中可能还有元素未排序,因为剩下的元素已经有序,且绝对大于额外空间中已排序元素,所以直接按顺序插入额外空间中即可。
代码实现:
public static void Merge(int[] array, int s, int mid, int l) {
//创建一个数组来存储排序后的序列,最后拷贝回原序列
int[] tmpArr = new int[(l-s)+1];
//序列首尾
int s1 = s;
int l2 = l;
int l1 = mid;
int s2 = l1+1;
int i = 0;
//四指针排序
while(s1 <= l1 && s2 <= l2) {
if(array[s1] <= array[s2]) {
tmpArr[i++] = array[s1++];
} else {
tmpArr[i++] = array[s2++];
}
}
//将左或右序列剩下的元素插入tmpArr
while(s1 <= l1) {
tmpArr[i++] = array[s1++];
}
while(s2 <= l2) {
tmpArr[i++] = array[s2++];
}
//把排好序的数组拷贝回array
for(i = 0; i < tmpArr.length; i++) {
array[s+i] = tmpArr[i];
}
}
归并排序复杂度分析
归并排序的时间复杂度,一趟归并需要将 array[1]~array[n]中相邻的长度为h的有序序列进行两两归并。并将结果放到 tmpArr[1]~tmpArr[n]中,这需要将待排序序列中的所有记录扫描一遍,因此耗费O(n)时间,而由完全二叉树的深度可知,整个归并排序需要进行[logn]次,因此,总的时间复杂度为O(nlogn),而且这是归并排序算法中最好、最坏和平均的时间性能。
由于归并排序在归并过程中需要与原始记录序列同样数量的存储空间存放归并结果,因此空间复杂度为O(n)。
另外,对代码进行仔细研究,发现Merge 函数中有if(SR[i]<SR[j])语句,这就说明它需要两两比较,不存在跳跃,因此归并排序是一种稳定的排序算法。也就是说,归并排序是一种比较占用内存,但却效率高且稳定的算法。
非递归实现
非递归实现非常简单,重点在于确定子序列首尾。
方法是定义整形(int)gap表示每一组序列的元素数量,依据gap划分的组别进行归并,大小序列划分依靠gap*=2来实现。
public static void MSort(int[] array, int s, int l) {
if(array .length == 0) return;
int gap = 1;
//不能超过本身数量
while(gap < array.length) {
for(int i = 0; i < array.length; i = i+gap*2) {
//左首
int s1 = i;
//左尾
int mid = i+gap-1;
//右尾
int l2 = mid+gap;
//序列不能越界
if(mid >= array.length) {
mid = array.length-1;
}
if(l2 >= array.length) {
l2 = array.length-1;
}
Merge(array,s1,mid,l2);
}
gap*=2;
}
}
博主是Java新人,每位同志的支持都会给博主莫大的动力,如果有任何疑问,或者发现了任何错误,都欢迎大家在评论区交流“ψ(`∇´)ψ