归并算法即一种分治策略,将大问题划分为n个小问题,然后对n小问题求解组合成大问题的解。
在使用递归的分治排序中,涉及三个问题:一、小问题的不可划分的或者终止条件;二、数组的拆分;三、已排序的两个数组的合并。
以下代码是不使用递归的分治排序算法,考虑可以结合多线程,使速度更快。
package main;
import util.MRand;
import util.MTime;
/***
* 归并排序
* @author Administrator
*
*/
public class MergeSort extends Thread {
/**
* 归并排序进行升序排序,不使用哨兵
* @param a待排序数组
*/
private static void sortAsc(int[] a){
if (a.length<=1){
return ;
}
//拆分左右数组部分
int leftLength = a.length/2;//左边部分的数组长度
int rightLength = a.length-a.length/2;//右边部分的数组长度
int L[] = new int[leftLength];
int R[] = new int[rightLength];
for(int i = 0;i<leftLength;i++){//赋值
L[i]=a[i];
}
for(int i = 0;i<rightLength;i++){//赋值
R[i] = a[leftLength+i];
}
sortAsc(L);
sortAsc(R);
int l = 0,r = 0;
for(int i=0;i<a.length;i++){//对排序好的L、R数组进行排序
if(L[l]>R[r]){
a[i]=R[r];
r++;
if(r==rightLength){
for(int j=i+1;j<a.length;j++){
a[j]=L[l];
l++;
}
break;
}
}else{
a[i]=L[l];
l++;
if(l==leftLength){
for(int j=i+1;j<a.length;j++){
a[j]=R[r];
r++;
}
break;
}
}
}
}
/**
* 归并排序进行降序排序,不使用哨兵
* @param a待排序数组
*/
private static void sortDesc(int[] a){
if (a.length<=1){
return ;
}
int leftLength = a.length/2;//左边部分的数组长度
int rightLength = a.length-a.length/2;//右边部分的数组长度
int L[] = new int[leftLength];
int R[] = new int[rightLength];
for(int i = 0;i<leftLength;i++){//赋值
L[i]=a[i];
}
for(int i = 0;i<rightLength;i++){//赋值
R[i] = a[leftLength+i];
}
sortDesc(L);//分治左边数组
sortDesc(R);//分治右边数组
int l = 0,r = 0;
for(int i=0;i<a.length;i++){//对排序好的L、R数组进行排序
if(L[l]<R[r]){
a[i]=R[r];
r++;
if(r==rightLength){
for(int j=i+1;j<a.length;j++){
a[j]=L[l];
l++;
}
break;
}
}else{
a[i]=L[l];
l++;
if(l==leftLength){
for(int j=i+1;j<a.length;j++){
a[j]=R[r];
r++;
}
break;
}
}
}
}
/***
*
* @param a排序数组a,不使用哨兵,升序
*/
public static void sort(int[] a){
sortAsc(a);
}
/***
* @param a排序数组a,不使用哨兵
* @param orderBy 值为desc为降序,否则为升序
*/
public static void sort(int[] a,String orderBy){
if(orderBy!=null && orderBy.toLowerCase().equals("desc")){
sortDesc(a);
}else{
sortAsc(a);
}
}
public static void main(String[] args) {
int k=10;
while(k>0){
int a[] = MRand.getRandInt(10000);
int b[] = MRand.getRandInt(1000000);
int c[] = MRand.getRandInt(10000);
MTime.setStartTime();
MTime.getDValue("开始");
sort(a);
MTime.getDValue("1万条数据,归并排序耗时:");
sort(b);
MTime.getDValue("100万条数据,归并排序耗时:");
InsertSort.sort(c);
MTime.getDValue("1万条数据,插入排序耗时:");
k--;
}
}
}
在和上一节的插入排序进行速度的比较结果如下:
开始0
1万条数据,归并排序耗时:5
100万条数据,归并排序耗时:188
1万条数据,插入排序耗时:55
开始0
1万条数据,归并排序耗时:1
100万条数据,归并排序耗时:158
1万条数据,插入排序耗时:34
开始0
1万条数据,归并排序耗时:2
100万条数据,归并排序耗时:150
1万条数据,插入排序耗时:11
开始0
1万条数据,归并排序耗时:1
100万条数据,归并排序耗时:164
1万条数据,插入排序耗时:10
分治算法是用空间换来时间的算法,递归很消耗内存。
此外在待排序的数字值小于某个数时,插入排序性能将由于归并排序,所以可以将归并排序改进,将叶节点变粗,即在问题缩小到一定规模时(而不是不可划分),使用插入排序,再返回排序好的数组,使用归并排序的合并机制来合并数组。
时间复杂度分析:对于归并排序,每次将问题分解成两个小问题,即T(n)=2T(n/2)+D(n)+C(n),其中D(n)表示将分解子数组的时间,C(n)表示合并子数组的时间,D(n)+C(n)=R(n)。问题规模为n,则树深log2 n,则根据主定理证明,T(n)=nlog2 n。
此外对于基本排序好的数列,使用插入排序的速度优于归并排序。