自底向上,自顶向下,归并排序 的 递归,非递归

归并排序

Merge为归并算法的核心操作,优先介绍,
即将两个已排序的数组合并到一个新的数组中的操作。

arrA: 2,4,6
arrB: 3,5,7
merge
arrC: 2,3,4,5,6,7

归并排序可分为自顶向下自底向上两种方法。

  • 自顶向下是指先将整个数组二分切割,再将切割得到后的子数组二分切割,直至所有子数组仅包含1个元素,之后再进行逐层Merge合并,因此最好使用递归实现。
  • 自底向上是指,直接对整个数组执行:
    将相邻的2元素Merge, 再将相邻的4元素Merge … 直至整个数组均Merge完毕即排序完毕。

Merge

注意:两区间分别为闭区间 和 左开右闭,此原则全程保持不变!即:[low,mid] 与(mid,high]

//将arr中,[low,mid] 与(mid,high] 合并
    public static void merge(int[] arr, int low, int mid, int h) {
        //left:左子数组起始下标,right:右子数组起始下标  x:arr上更新的下标
        int left = low, right = mid + 1, x = low;
        int[] tmp = new int[arr.length];
        //拷贝数组
        System.arraycopy(arr, 0, tmp, 0, arr.length);
        //终止条件最好用x 而不要用left,right
        while (x <= h) {
            if (left > mid ) {//当left超过mid代表left中用完
                arr[x++] = tmp[right++];
            } else if (right > h ) { //当right过h代表right中用完
                arr[x++] = tmp[left++];
            } else {
                if (tmp[left] <= tmp[right]) {//选小的,若为>= 则为升序排序
                    arr[x++] = tmp[left++];
                } else {
                    arr[x++] = tmp[right++];
                }
            }
        }
    }

自顶向下(递归)

//自顶向下
    public static void Sort(int[] arr, int l, int h) {
        if (l < r) {  //终止条件,当l==r 
            int m = (l + h) / 2;  //自动向下取整,无所谓
            Sort(arr, l, m);  //排序l-m
            Sort(arr, m + 1, h); //排序m+1-h
            merge(arr, l, m, h); //合并
        }
    }

自底向上(非递归)

len 的定义很重要:

  1. 要单独定义。
  2. 定义为每次两组的大小,而不是一组的大小。(不是必须的)

内层循环的终止条件很重要, j + len / 2 < length;不需要-1,这是一组零一个的长度。

     //自底向上
    public static void Sort(int[] arr) {
        int length = arr.length;
        int len = 0; //为了最后一次的归并,把len单独定义
        //用len控制窗口大小,将窗口内的两个子数组merge,不需要<=length, 当=length时为整体的,下面已经单独写了。
        for ( len = 2; len < length; len = len * 2) {//窗口为长度2,4,8...
            //j推动窗口移动
            // 因此窗口中两个数组分别为  [j,j+len/2-1]   [(j+len/2),j+len-1]
            // 注意终止条件为第二个子数组的首小于数组长度(被包含),以此过滤掉不够一组的情况。
            for (int j = 0; j + len / 2  < length; j += len) {
                if (j + len - 1 < length) {
                 	//右断点在范围,两组健全
                    merge(arr, j, j + len / 2 - 1, j + len - 1);
                } else {
                	//一组健全,一组残废
                    merge(arr, j, j + len / 2 - 1, length - 1);
                }
            }
        }
        //看这里!!需要找到目前排序的长度,用到了‘len’,
        // 最后把所有归并一次 [0,len/2-1] [len/2 ,length-1]
        merge(arr, 0, len / 2 - 1, length - 1);
    }

其中最需要注意的即为非递归中的限制条件与下标,与终止条件的判断。
存在两个比较绕的点,容易导致数量控制有误。

  1. 数量容量与下标差总差1:当需要x个元素时,下标之差为x-1;
    eg: 若a[i]-a[j] 代表2个元素,i-j=1
  2. 数组尾标与长度总差1:数组尾标与长度总是差1导致终止条件易写错;
    这里我们可以把在数组内翻译为 "< length"
    超过数组翻译为 >=length,注意不能丢掉等号。
    因此,“数组内包含一组半”意思为第二个数组的头包含在内,尾不包含在内
    即:(j+len/2<length && j+len-1>=length)

全部代码:

import java.util.Arrays;

//归并排序
public class MergeSort {

    //将arr中,low——mid 与mid+1——high 合并
    public static void merge(int[] arr, int low, int mid, int h) {
        //left:左子数组起始下标,right:右子数组起始下标  x:arr上更新的下标
        int left = low, right = mid + 1, x = low;
        int[] tmp = new int[arr.length];
        //拷贝数组
        System.arraycopy(arr, 0, tmp, 0, arr.length);
        //终止条件最好用x 而不要用left,right
        while (x <= h) {
            if (left > mid ) {//当left超过mid代表left中用完
                arr[x++] = tmp[right++];
            } else if (right > h ) { //当right过h代表right中用完
                arr[x++] = tmp[left++];
            } else {
                if (tmp[left] <= tmp[right]) {//选小的,若为>= 则为升序排序
                    arr[x++] = tmp[left++];
                } else {
                    arr[x++] = tmp[right++];
                }
            }
        }
    }

//自顶向下
    public static void Sort(int[] arr, int l, int h) {
        if (l < r) {  //终止条件,当l==r 
            int m = (l + h) / 2;  //自动向下取整,无所谓
            Sort(arr, l, m);  //排序l-m
            Sort(arr, m + 1, h); //排序m+1-h
            merge(arr, l, m, h); //合并
        }
    }

     //自底向上
    public static void Sort(int[] arr) {
        int length = arr.length;
        int len = 0; //为了最后一次的归并,把len单独定义
        //用len控制窗口大小,将窗口内的两个子数组merge,不需要<=length, 当=length时为整体的,下面已经单独写了。
        for ( len = 2; len < length; len = len * 2) {//窗口为长度2,4,8...
            //j推动窗口移动
            // 因此窗口中两个数组分别为  [j,j+len/2-1]   [(j+len/2),j+len-1]
            // 注意终止条件为第二个子数组的首小于数组长度(被包含),以此过滤掉不够一组的情况。
            for (int j = 0; j + len / 2  < length; j += len) {
                if (j + len - 1 < length) {
                 	//右断点在范围,两组健全
                    merge(arr, j, j + len / 2 - 1, j + len - 1);
                } else {
                	//一组健全,一组残废
                    merge(arr, j, j + len / 2 - 1, length - 1);
                }
            }
        }
        //看这里!!需要找到目前排序的长度,用到了‘len’,
        // 最后把所有归并一次 [0,len/2-1] [len/2 ,length-1]
        merge(arr, 0, len / 2 - 1, length - 1);
    }
    public static void main(String[] args) {
        int[] arr = {2, 5, 1, 3, 4};
//        Sort(arr, 0, arr.length - 1);
        Sort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值