归并排序 递归 非递归 原地 详解及优化效率分析

  • 原地排序算法
  • merge算法及辅助空间优化
  • 归并排序递归非递归算法
  • 运行效率分析

原地排序算法详解

原地归并排序也即为对merge的优化,不需要借助辅助空间。
首先,要清楚如下定义:
要交换一组数:{4,5,6,1,2,3}中{4,5,6}和{1,2,3}的位置只需要如下操作即可:(自己试两遍就懂了)

  • 先反转 {4,5,6},得到{6,5,4,1,2,3} ;
  • 再反转 {1,2,3},得到{6,5,4,3,2,1} ;
  • 最后反转整体 得 {1,2,3,4,5,6}。

然后,由于只是对merge的优化,主体还是采用递归or非递归的方式,故每次merge操作的左右两边都是已经有序的,接下来看图分析:
1
2

private void mergeByInplace(int[] array, int begin, int mid, int end){
    int j=mid;
    // 循环直到begin和j相等,或j到达末端end,意为一段合并完了,已然有序
    while(begin<j&&j<end){
        // 跳出条件为左边第一个比右边j大 或 i=mid
        while(begin<mid&&array[begin]<array[j]) begin++;
        // 进入条件入左边大于右边or i=mid 跳出条件为右边第一个比左边i小 交换i~mid-1和index~j-1
        while(j<=end&&array[j]<array[begin]) j++;
        swapRange(array,begin,mid,j-1);
        // 更改i的指向为交换后的位置
        begin+=j-mid;
        // 更新mid的位置为第二段首,即为j的位置
        mid=j;
    }
}

private void swapRange(int[] array, int i, int mid, int end) {
    reverse(array,i,mid-1);
    reverse(array,mid,end);
    reverse(array,i,end);
}

private void reverse(int[] array, int begin, int end) {
    while(begin<end){
        array[begin]^=array[end];
        array[end]^=array[begin];
        array[begin]^=array[end];
        begin++;
        end--;
    }
}

merge算法及辅助空间优化

  • 原始merge算法
private void merge(int[] array, int begin, int mid, int end) {
   // 开启辅助空间复制数组,每次merge都要开启length长
   int[] temp = Arrays.copyOf(array, array.length);
   int left=begin,right=mid,i=begin;
   while(left<mid&&right<=end){
       if(temp[left]<=temp[right]){
           array[i++]=temp[left++];
       }else{
           array[i++]=temp[right++];
       }
	}
    // 右边right未到end,则为右边后面的数大,原地不动即可,因为temp是从array原数组中复制的
    while(left<mid){ 
        array[i++]=temp[left++];
    }
}
  • 优化辅助空间开辟merge算法
    每次开辟与排序数组array空间一致的辅助空间,但实际用的空间仅为begin~end,所以可以仅开辟需要排序的空间,注意下标的转换问题即可。
public void mergePlus(int[] array, int begin, int mid, int end) {
    // 开辟begin~end长度的复制空间,同时复制数组
    int[] temp = Arrays.copyOfRange(array, begin,end+1);
	// 处理下标
    int i=begin,left,right;
    end-=begin;
    mid-=begin;
    begin=0;
    left=begin;
    right=mid;
	…………//同上述merge代码
}

归并排序递归&非递归算法

递归
先划分,再合并

private void mergeByRecursion(int[] array,int begin,int end){
	if(begin<end){
		int mid = (begin+end)>>1;
        mergeByRecursion(array,begin,mid);
        mergeByRecursion(array,mid+1,end);
        //merge(array,begin,mid+1,end);
        //mergePlus(array,begin,mid+1,end);
        mergeByInplace(array,begin,mid+1,end);
    }
}

非递归
取步长length从1逐步倍增*2,合并左右两边

  1. 外层循环边界:length<end-begin+1,步长小于数组长度,保证可以调用merge左右都有数
  2. 内层循环边界:left+length<=end,left+length的位置即为mid也就是右边开始第一个数的位置
private void mergeByNoRecursion(int[] array,int begin,int end){
	int length = 1,left,mid,right;
	// 外层循环倍增步长length
    for(;length<end-begin+1;length*=2){
    	//内层循环自左往右一次merge左右length长数段
       for(left=begin;left+length<=end;left+=2*length){
           mid=left+length>end?end:left+length;
           right=mid+length-1>end?end:mid+length-1;
           //merge(array,left,mid,right);
           //mergePlus(array,left,mid,right);
           mergeByInplace(array,left,mid,right);
       }
    }
}

运行效率分析

随机生成size大小的数组,size分别取10000100000

  • 复制数组要开辟辅助空间,对效率影响较大
  • 由于原地merge没有利用辅助空间,用空间换时间,其执行效率要慢于优化空间后的merge
  • 由于不优化空间merge复制数组耗时较大效率要慢于原地merge
@Test
public void test01(){
    int size = 100000; // 10000 && 100000 && 10000000
    Random random = new Random();
    int[] array=new int[size];
    for(int i=0;i<size;i++){
        array[i]=random.nextInt(size);
    }
    int[] arrays = Arrays.copyOf(array,array.length);
    System.out.println("========不优化空间or优化空间or原地排序========");
    long before = System.currentTimeMillis();
    sortByMerge(array,0,array.length);
    System.out.println("recuresion time: "+(System.currentTimeMillis()-before));
    long before1 = System.currentTimeMillis();
    sortByMerge1(arrays,0,array.length);
    System.out.println("norecursion time: "+(System.currentTimeMillis()-before1));
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值