排序--05---归并排序

Posted 高高for 循环

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了排序--05---归并排序相关的知识,希望对你有一定的参考价值。

复习递归

正式学习归并排序之前,我们得先复习一下递归算法

定义:

  • 定义方法时,在方法内部调用方法本身,称之为递归.
    在这里插入图片描述

作用:

  • 它通常把一个大型复杂的问题,层层转换为一个与原问题相似的,规模较小的问题来求解。递归策略只需要少量的程序就可以描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。

注意事项:==>容易造成栈内存溢出

  • 在递归中,不能无限制的调用自己,必须要有边界条件,能够让递归结束,因为每一次递归调用都会在栈内存开辟新的空间,重新执行方法,如果递归的层级太深,很容易造成栈内存溢出

在这里插入图片描述

案例:

请定义一个方法,使用递归完成求N的阶乘

在这里插入图片描述

public class Test01 {
    public static void main(String[] args) throws Exception {
        int result = factorial(5);
        System.out.println(result);
    }
    public static int factorial(int n){
        if (n==1){
            return 1;
        }
        return n*factorial(n-1);
    }
}

归并排序

定义:

  • 归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。
  • 将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

分治法:

  • 分治法将问题分(divide)成一些小的问题然后递归求解
  • 而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之
    在这里插入图片描述

排序原理:

  1. 尽可能的一组数据拆分成两个元素相等的子组,并对每一个子组继续拆分,直到拆分后的每个子组的元素个数是 1为止。
  2. 将相邻的两个子组进行合并成一个有序的大组;
  3. 不断的重复步骤2,直到最终只有一个组为止。
    在这里插入图片描述
    在这里插入图片描述

归并原理:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码实现 1:

API设计:

在这里插入图片描述


public class Merge {
    //归并所需要的辅助数组
    private static Comparable[] assist;

    /*
       比较v元素是否小于w元素
    */
    private static boolean less(Comparable v, Comparable w) {
        return v.compareTo(w)<0;
    }

    /*
    数组元素i和j交换位置
     */
    private static void exch(Comparable[] a, int i, int j) {
        Comparable t = a[i];
        a[i] = a[j];
        a[j] = t;
    }


    /*
           对数组a中的元素进行排序
        */
    public static void sort(Comparable[] a) {
        //1.初始化辅助数组assist;
        assist = new Comparable[a.length];
        //2.定义一个lo变量,和hi变量,分别记录数组中最小的索引和最大的索引;
        int lo=0;
        int hi=a.length-1;
        //3.调用sort重载方法完成数组a中,从索引lo到索引hi的元素的排序
        sort(a,lo,hi);
    }

    /*
    对数组a中从lo到hi的元素进行排序
     */
    private static void sort(Comparable[] a, int lo, int hi) {
        //做安全性校验;
        if (hi<=lo){
            return;
        }

        //对lo到hi之间的数据进行分为两个组
        int mid = lo+(hi-lo)/2;//   5,9  mid=7

        //分别对每一组数据进行排序
        sort(a,lo,mid);
        sort(a,mid+1,hi);

        //再把两个组中的数据进行归并
        merge(a,lo,mid,hi);
    }

    /*
    对数组中,从lo到mid为一组,从mid+1到hi为一组,对这两组数据进行归并
     */
    private static void merge(Comparable[] a, int lo, int mid, int hi) {
        //定义三个指针
        int i=lo;    //定义一个指针,指向assist数组中开始填充数据的索引
        int p1=lo;   //定义一个指针,指向第一组数据的第一个元素
        int p2=mid+1; //定义一个指针,指向第二组数据的第一个元素

        //遍历,移动p1指针和p2指针,比较对应索引处的值,找出小的那个,放到辅助数组的对应索引处
        while(p1<=mid && p2<=hi){
            //比较对应索引处的值
            if (less(a[p1],a[p2])){
                assist[i++] = a[p1++];
            }else{
                assist[i++]=a[p2++];
            }
        }

        //遍历,如果p1的指针没有走完,那么顺序移动p1指针,把对应的元素放到辅助数组的对应索引处
        while(p1<=mid){
            assist[i++]=a[p1++];
        }
        //遍历,如果p2的指针没有走完,那么顺序移动p2指针,把对应的元素放到辅助数组的对应索引处
        while(p2<=hi){
            assist[i++]=a[p2++];
        }
        //把辅助数组中的元素拷贝到原数组中
        for(int index=lo;index<=hi;index++){
            a[index]=assist[index];
        }

    }

}

测试类:

    public static void main(String[] args) {
        Integer[] data = { 9, -16, 21, 23, -30, -49, 21, 30, 30 };
        System.out.println("排序之前:\\n" + java.util.Arrays.toString(data));
        Merge.sort(data);
        System.out.println("排序之后:\\n" + java.util.Arrays.toString(data));
    }

在这里插入图片描述

代码实现 2:

MergeSort

public class MergeSort {
    public static void mergeSort(int[] data) {
        // 归并排序
        sort(data, 0, data.length - 1);
    }

    // 将索引从left到right范围的数组元素进行归并排序
    private static void sort(int[] data, int left, int right) {
        if(left < right){
            //找出中间索引
            int center = (left + right)/2;
            sort(data,left,center);
            sort(data,center+1,right);
            //合并
            merge(data,left,center,right);
        }
    }

    // 将两个数组进行归并,归并前两个数组已经有序,归并后依然有序
    private static void merge(int[] data, int left, int center, int right) {
        int[] tempArr = new int[data.length];
        int mid = center + 1;
        int third = left;
        int temp = left;
        while (left <= center && mid <= right) {
            if (data[left] - data[mid] <= 0) {
                tempArr[third++] = data[left++];
            } else {
                tempArr[third++] = data[mid++];
            }
        }
        while (mid <= right) {
            tempArr[third++] = data[mid++];
        }
        while (left <= center) {
            tempArr[third++] = data[left++];
        }
        while (temp <= right) {
            data[temp] = tempArr[temp++];
        }
    }

    public static void main(String[] args) {
        int[] data = { 9, -16, 21, 23, -30, -49, 21, 30, 30 };
        System.out.println("排序之前:\\n" + java.util.Arrays.toString(data));
        mergeSort(data);
        System.out.println("排序之后:\\n" + java.util.Arrays.toString(data));
    }
}

在这里插入图片描述

对象排序:

public class MergeSort02 {
    public static void mergeSort(DataWrap[] data) {
        // 归并排序
        sort(data, 0, data.length - 1);
    }

    // 将索引从left到right范围的数组元素进行归并排序
    private static void sort(DataWrap[] data, int left, int right) {
        if(left < right){
            //找出中间索引
            int center = (left + right)/2;
            sort(data,left,center);
            sort(data,center+1,right);
            //合并
            merge(data,left,center,right);
        }
    }

    // 将两个数组进行归并,归并前两个数组已经有序,归并后依然有序
    private static void merge(DataWrap[] data, int left, int center, int right) {
        DataWrap[] tempArr = new DataWrap[data.length];
        int mid = center + 1;
        int third = left;
        int temp = left;
        while (left <= center && mid <= right) {
            if (data[left].compareTo(data[mid]) <= 0) {
                tempArr[third++] = data[left++];
            } else {
                tempArr[third++] = data[mid++];
            }
        }
        while (mid <= right) {
            tempArr[third++] = data[mid++];
        }
        while (left <= center) {
            tempArr[third++] = data[left++];
        }
        while (temp <= right) {
            data[temp] = tempArr[temp++];
        }
    }

    public static void main(String[] args) {
        DataWrap[] data = { new DataWrap(9, ""), new DataWrap(-16, ""),
                new DataWrap(21, "*"), new DataWrap(23, ""),
                new DataWrap(-30, ""), new DataWrap(-49, ""),
                new DataWrap(21, ""), new DataWrap(30, "*"),
                new DataWrap(30, "") };
        System.out.println("排序之前:\\n" + java.util.Arrays.toString(data));
        mergeSort(data);
        System.out.println("排序之后:\\n" + java.util.Arrays.toString(data));
    }
}

在这里插入图片描述

归并排序 稳定

时间复杂度分析:

解析:

归并排序是分治思想的最典型的例子,上面的算法中,对a[lo...hi]进行排序,先将它分为a[lo...mid]和a[mid+1...hi]两部分,分别通过递归调用将他们单独排序,最后将有序的子数组归并为最终的排序结果。该递归的出口在于如果一个数组不能再被分为两个子数组,那么就会执行merge进行归并,在归并的时候判断元素的大小进行排序。
在这里插入图片描述

时间复杂度为O(nlogn);

归并排序的缺点:

  • 需要申请额外的数组空间,导致空间复杂度提升,是典型的以空间换时间的操作

归并排序与希尔排序性能测试:

在这里插入图片描述

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;

public class SortCompare {
    //调用不同的测试方法,完成测试
    public static void main(String[] args) throws Exception{
        //1.创建一个ArrayList集合,保存读取出来的整数
        ArrayList<Integer> list = new ArrayList<>();

        //2.创建缓存读取流BufferedReader,读取数据,并存储到ArrayList中;
        BufferedReader reader = new BufferedReader(new InputStreamReader(SortCompare.class.getClassLoader().getResourceAsStream("reverse_arr.txt")));
        String line=null;
        while((line=reader.readLine())!=null){
            //line是字符串,把line转换成Integer,存储到集合中
            int i = Integer.parseInt(line);
            list.add(i);
        }

        reader.close();


        //3.把ArrayList集合转换成数组
        Integer[] a = new Integer[list.size()];
        list.toArray(a);
        //4.调用测试代码完成测试
       // testInsertion(a);
        // testShell(a);  //17

        testMerge(a);
    }



    //测试希尔排序
    public static void testShell(Integer[] a){
        //1.获取执行之前的时间
        long start = System.currentTimeMillis();
        //2.执行算法代码
        Shell.sort(a);
        //3.获取执行之后的时间
        long end = System.currentTimeMillis();
        //4.算出程序执行的时间并输出
        System.out.println("希尔排序执行的时间为:"+(end-start)+"毫秒");

    }

    //测试插入排序
    public static void testInsertion(Integer[] a){
        //1.获取执行之前的时间
        long start = System.currentTimeMillis();
        //2.执行算法代码
        Insertion.sort(a);
        //3.获取执行之后的时间
        long end = System.currentTimeMillis();
        //4.算出程序执行的时间并输出
        System.out.println("插入排序执行的时间为:"+(end-start)+"毫秒");
    }


    //测试插入排序
    public static void testMerge(Integer[] a){
        //1.获取执行之前的时间
        long start = System.currentTimeMillis();
        //2.执行算法代码
        Merge.sort(a);
        //3.获取执行之后的时间
        long end = System.currentTimeMillis();
        //4.算

以上是关于排序--05---归并排序的主要内容,如果未能解决你的问题,请参考以下文章

自然归并排序一个链表

归并排序(2017-09-05)

排序之归并排序的算法思想及实现代码

排序之外部排序

归并排序:步骤讲解与代码实现

python代码实现归并排序(Merge Sort )