归并排序问题的Java实现

思路

  • 归并排序用到的是分治的思想:
  1. 分割递归地将数组平均分成两个子数组subarr1和subarr2,直到子数组长度为1,无法再次分割
  2. 归并:合并排序好的两个子数组,且使得合并后的数组仍是有序的

归并排序动画示意:

动图来自维基百科,侵删!在这里插入图片描述


  • 所要做的工作主要就两个:
  1. 递归分割
  2. 归并
1. 递归分割
private static void mergeSort(int[] arr, int left, int right) {
        // 直到子数组长度为1,无法再次分割,return返回,停止分割
        if (left >= right) return;
        // 计算数组中间下标
        // 奇数个元素,左子数组元素比右子数组多一个
        // 偶数个则元素个数相等
        int mid = (left + right) / 2;
        // 递归分割左右子数组
        mergeSort(arr, left, mid);
        mergeSort(arr, mid + 1, right);
        // 将两个子数组合并
        merge(arr, left, mid, right);
    }
  • 递归分治,相当于用一颗完全二叉树去分割原数组,根据完全二叉树的特性,时间复杂度为O(logn)。
  • 方法通过将下标作为传参直接在原数组所在内存上进行递归操作,mid变量占用额外储存空间O(1)。
2. 归并
private static void merge(int[] arr, int left, int mid, int right) {
        // 临时辅助数组,储存右子数组
        int[] temp = new int[right - mid];
        // 将右子数组copy到辅助数组temp中
        for (int i = mid + 1; i <= right; i++) {
            temp[i - mid - 1] = arr[i];
        }
        int tempIndex = temp.length - 1;
        // 将排序好的数组B合并到排序好的数组A中(假设A数组的总长度可以容纳B)
        while (tempIndex >= 0 && mid >= left) {
            arr[right--] = temp[tempIndex] > arr[mid]? temp[tempIndex--] : arr[mid--];
        }
        // 若辅助数组(数组B)还没比较完,而A的前半部分有效长度的数字已经比较完了,则依次将B加入A
        while (tempIndex >= 0) {
            arr[right--] = temp[tempIndex--];
        }
    }
  • 每次合并操作,占用额外储存空间为O(n/2)。
  • 赋值操作次数O(n)次,比较O(n/2)~O(n-1)次,因此每次归并时间复杂度为O(n)。

总的来说,归并排序空间复杂度不超过O(n),时间复杂度为O(nlogn)。


  • 随机生成数组的方法:
public static int[] getRandomArr(int length, int rangeL, int rangeR) {
        if (rangeL >= rangeR) throw new RuntimeException("范围输入有误");
        int[] arr = new int[length];
        for (int i = 0; i < length; i++) {
            arr[i] = (int) (Math.random() * (rangeR - rangeL + 1)) + rangeL;
        }
        return arr;
    }

总的代码如下:

public class MergeSort {
    public static void main(String[] args) {
        int[] arr = getRandomArr(4, 1, 10);
        for (int i : arr) System.out.print(i + " ");
        mergeSort(arr);
        System.out.println();
        for (int i : arr) System.out.print(i + " ");
    }

    public static void mergeSort(int[] arr) {
        mergeSort(arr, 0, arr.length - 1);
    }

    private static void mergeSort(int[] arr, int left, int right) {
        // 直到子数组长度为1,无法再次分割,return返回,停止分割
        if (left >= right) return;
        // 计算数组中间下标
        // 奇数个元素,左子数组元素比右子数组多一个
        // 偶数个则元素个数相等
        int mid = (left + right) / 2;
        // 递归分割左右子数组
        mergeSort(arr, left, mid);
        mergeSort(arr, mid + 1, right);
        // 将两个子数组合并
        merge(arr, left, mid, right);
    }

    private static void merge(int[] arr, int left, int mid, int right) {
        // 临时辅助数组,储存右子数组
        int[] temp = new int[right - mid];
        // 将右子数组copy到辅助数组temp中
        for (int i = mid + 1; i <= right; i++) {
            temp[i - mid - 1] = arr[i];
        }
        int tempIndex = temp.length - 1;
        // 将排序好的数组B合并到排序好的数组A中(假设A数组的总长度可以容纳B)
        while (tempIndex >= 0 && mid >= left) {
            arr[right--] = temp[tempIndex] > arr[mid]? temp[tempIndex--] : arr[mid--];
        }
        // 若辅助数组(数组B)还没比较完,而A的前半部分有效长度的数字已经比较完了,则依次将B加入A
        while (tempIndex >= 0) {
            arr[right--] = temp[tempIndex--];
        }
    }

    public static int[] getRandomArr(int length, int rangeL, int rangeR) {
        if (rangeL >= rangeR) throw new RuntimeException("范围输入有误");
        int[] arr = new int[length];
        for (int i = 0; i < length; i++) {
            arr[i] = (int) (Math.random() * (rangeR - rangeL + 1)) + rangeL;
        }
        return arr;
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值