归并排序分两种,自顶向下和自底向上:
一、自顶向下
这种方式的思路是,将数组两等分的递归拆成最小的数组(也就是1/0个元素),然后在一个一个的拼接起来。
相比于自底向上,这种是更容易理解的,因为拆分不需要我们手动进行。
/**
* 自顶向下的归并排序,用到的是递归
* @param nums
*/
public void mergeSortT2B(int[] nums,int left,int right){
if(left==right) return;
int mid = (left+right)/2;
mergeSortT2B(nums,left,mid); //先把左半边排好序
mergeSortT2B(nums,mid+1,right); //再把右半边排好序
merge(nums,left,mid,right); //左右合并
}
/**
* 合并两个排序数组部分(left-mid)(mid+1,right)
* @param nums
* @param left
* @param mid
* @param right
*/
public void merge(int[] nums,int left,int mid,int right){
int length = right-left+1;
int[] temp = new int[length]; //一个用来暂存排序数组的数组
int cur = 0; //排序数组指针
int lcur = left; //左边数组指针
int rcur = mid+1; //右边数组指针
while(lcur<=mid && rcur<=right){
if(nums[lcur]<nums[rcur]){
temp[cur] = nums[lcur];
lcur++;
}else {
temp[cur] = nums[rcur];
rcur++;
}
cur++;
}
while(lcur<=mid){
temp[cur] = nums[lcur];
cur++;
lcur++;
}
while(rcur<=right){
temp[cur] = nums[rcur];
cur++;
rcur++;
}
if (length >= 0) System.arraycopy(temp, 0, nums, left, length);
}
二、自底向上
这种其实比自顶向下少了一个拆分的步骤,直接从最小的开始合并,用的是迭代的方式。这样做就避免了递归开栈,少了更多资源消耗。
但是这种的问题就在于想着容易,不好写。
/**
* 自底向上的归并排序
* @param nums
*/
public void mergeSortB2T(int[] nums){
int length = nums.length;
for (int x=1;x<length;x+=x){ //每一次归并的数组大小x,从1开始
for (int y = 0; y < length-x; y+=2*x) { //每两个数组的起始元素下标y
merge(nums,y,y+x-1,Math.min(y+2*x-1, length-1));
}
}
}
所以要借鉴别人的,一旦写出过一次,理解了思路,下一次自己写就好写了。
分两层循环,
- 外层的x表示每一次归并的数组大小,从1开始,每次翻一倍
- 内层的y表示每两个需要合并数组的起始位置的下标,每次加两倍的x。
三、性能测试
public static void main(String[] args) {
MergeSort mergeSort = new MergeSort();
int[] nums1 = ArrayUtils.getRandomArray(1000000);
int[] nums2 = ArrayUtils.getRandomArray(1000000);
long start = System.currentTimeMillis();
mergeSort.mergeSortT2B(nums1,0,nums1.length-1);
long end = System.currentTimeMillis();
System.out.println("自顶向下耗时:"+(end-start)+"ms");
System.out.println();
start = System.currentTimeMillis();
mergeSort.mergeSortB2T(nums2);
end = System.currentTimeMillis();
System.out.println("自底向上耗时:"+(end-start)+"ms");
}
获取两个大小相等的随机数组,观察其耗时。