- 原地排序算法
- 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操作的左右两边都是已经有序的,接下来看图分析:
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,合并左右两边
- 外层循环边界:length<end-begin+1,步长小于数组长度,保证可以调用merge左右都有数
- 内层循环边界:
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分别取10000
和100000
- 复制数组要开辟辅助空间,对效率影响较大
- 由于原地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));
}