(五) 数据结构 - 归并排序

归并排序

归并排序是一种基于分而治之的排序技术。最坏情况下的时间复杂度为O(nlogn),它是最受人尊敬的算法之一。归并排序首先将数组分成相等的两半,然后以排序的方式将它们合并。

核心思想

为了理解合并排序,我们采用未排序的数组,如下所示

我们知道归并排序首先将整个数组迭代地分成相等的一半,除非获得原子值。我们在这里看到一个由8个项目组成的数组分为两个大小为4的数组。

这不会更改原件中项目出现的顺序。现在我们将这两个数组分为两半。

我们进一步划分这些数组,并获得无法再划分的原子值

现在,我们将它们分解时的方式完全相同。请注意提供给这些列表的颜色代码。

我们首先比较每个列表的元素,然后以排序的方式将它们组合到另一个列表中。我们看到14和33处于排序位置。我们比较27和10,在2个值的目标列表中,我们先放置10,然后是27。我们更改19和35的顺序,而将42和44顺序放置。

在合并阶段的下一个迭代中,我们比较两个数据值的列表,然后将它们合并为找到的数据值的列表,将所有数据按排序顺序放置。

最终合并后,列表应如下所示:

代码开发

实现思路

归并排序会继续将列表分为相等的一半,直到无法再对其进行划分为止。根据定义,如果它只是列表中的一个元素,则会对其进行排序。然后,合并排序将合并较小的排序列表,同时也将新列表排序。

Step 1−如果列表中的元素已被排序,则返回。
Step 2−将列表递归分为两半,直到无法再将其划分为止。
Step 3−将较小的列表按排序顺序合并到新列表中。

伪代码

package com.paal.demo.c01SortingBasic;

import com.paal.demo.Sort;

import java.util.Arrays;

/**
 * <p/>
 * <li>title: 基础排序-归并排序</li>
 * <li>@author: li.pan</li>
 * <li>Date: 2019/12/7 12:15 下午</li>
 * <li>Version: V1.0</li>
 * <li>Description: </li>
 */
public class MergeSort implements Sort {


    @Override
    public void sort(Integer[] arr) {
        int n = arr.length;
        sort(arr, 0, n - 1);
    }

    //递归使用归并排序,对arr[l....r]的范围进行进行排序
    private static void sort(Integer[] arr, int l, int r) {

        if (l >= r) //当子序列中只有一个元素递归到底的情况
            return;

        int mid = (l + r) / 2;
        sort(arr, l, mid);
        sort(arr, mid + 1, r);
        merge(arr, l, mid, r);

    }

    //将arr[l...mid]和arr[mid+1...r]两部分进行归并
    private static void merge(Integer[] arr, int l, int mid, int r) {

        // 开辟临时空间,合并左半部分已经排好序的数组和右半部分已经排好序的数组
        Integer[] aux = Arrays.copyOfRange(arr, l, r + 1);

        // 初始化,i指向左半部分的起始位置索引;j指向右半部分起始索引位置mid+1
        int i = l, j = mid + 1;
        for (int k = l; k <= r; k++) {  // k指向两个元素比较后归并下一个需要放置的位置

            /**
             * 考虑数组越界
             */
            if (i > mid) {  // 如果左半部分元素已经全部处理完毕,
                arr[k] = aux[j - l];
                j++;
            } else if (j > r) {   // 如果右半部分元素已经全部处理完毕
                arr[k] = aux[i - l];
                i++; // l表示偏移
            }
            /**
             * 真正比较
             */
            else if (aux[i - l] < aux[j - l]) {  // 左半部分所指元素 < 右半部分所指元素
                arr[k] = aux[i - l];
                i++;
            } else {  // 左半部分所指元素 >= 右半部分所指元素
                arr[k] = aux[j - l];
                j++;
            }
        }
    }

}

 /**
     * 希尔排序
     */
    @Test
    public void mergeSortTest() {
        Integer[] integers0 = SortTestHelper.generateRandomArray(100, 0, 1000000);
        Integer[] integers1 = SortTestHelper.generateRandomArray(10000, 0, 1000000);
        Integer[] integers2 = SortTestHelper.generateRandomArray(100000, 0, 1000000);
        System.out.println("------------------------------随机数组--------------------------------");
        System.out.println("插入排序测试1数据量为100"+SortTestHelper.testSort(integers0, new InsertionSort()));
        System.out.println("插入排序测试2数据量为10000"+SortTestHelper.testSort(integers1, new InsertionSort()));
        System.out.println("插入排序测试3数据量为100000"+SortTestHelper.testSort(integers2, new InsertionSort()));
        System.out.println("冒泡排序测试1数据量为100"+SortTestHelper.testSort(integers0, new BubbleSort()));
        System.out.println("冒泡排序测试2数据量为10000"+SortTestHelper.testSort(integers1, new BubbleSort()));
        System.out.println("冒泡排序测试3数据量为100000"+SortTestHelper.testSort(integers2, new BubbleSort()));
        System.out.println("希尔排序测试1数据量为100"+SortTestHelper.testSort(integers0, new ShellSort()));
        System.out.println("希尔排序测试2数据量为10000"+SortTestHelper.testSort(integers1, new ShellSort()));
        System.out.println("希尔排序测试3数据量为100000"+SortTestHelper.testSort(integers2, new ShellSort()));
        System.out.println("归并排序测试1数据量为100"+SortTestHelper.testSort(integers0, new MergeSort()));
        System.out.println("归并排序测试2数据量为10000"+SortTestHelper.testSort(integers1, new MergeSort()));
        System.out.println("归并排序测试3数据量为100000"+SortTestHelper.testSort(integers2, new MergeSort()));
    }

执行结果

------------------------------随机数组--------------------------------
插入排序测试1数据量为100排序时长为: 0.001s
插入排序测试2数据量为10000排序时长为: 0.084s
插入排序测试3数据量为100000排序时长为: 5.868s
冒泡排序测试1数据量为100排序时长为: 0.0s
冒泡排序测试2数据量为10000排序时长为: 0.061s
冒泡排序测试3数据量为100000排序时长为: 9.069s
希尔排序测试1数据量为100排序时长为: 0.0s
希尔排序测试2数据量为10000排序时长为: 0.004s
希尔排序测试3数据量为100000排序时长为: 0.008s

代码优化

  • 虽然归并排序是nlogn级别的算法, 但是在数组数据量比较小的时候, 插入排序的效率仍然是高于归并排序的, 所以可以在对数组分解到足够小之后, 使用插入排序, 然后再递归进行归并排序。

  • 如果一个数组是近乎有序的, 或者说是完全有序的, 上述步骤会有很多无用的merge操作, 所以可以在进行merge前增加一个判断, 效率也会有一定的提高。

伪代码

package com.paal.demo.c01SortingBasic.optimize;

import com.paal.demo.Sort;
import com.paal.demo.c01SortingBasic.InsertionSort;

import java.util.Arrays;

/**
 * <p/>
 * <li>title: 归并排序优化</li>
 * <li>@author: li.pan</li>
 * <li>Date: 2019/12/7 12:15 下午</li>
 * <li>Version: V1.0</li>
 * <li>Description:
 * 优化1: 对于arr[mid] <= arr[mid+1]的情况,不进行merge
 * 优化2: 对于小规模数组, 使用插入排序
 * </li>
 */
public class MergeSortOptimize implements Sort {


    @Override
    public void sort(Integer[] arr) {
        int n = arr.length;
        sort(arr, 0, n - 1);
    }

    //递归使用归并排序,对arr[l....r]的范围进行进行排序
    private static void sort(Integer[] arr, int l, int r) {

        /**
         *  优化2: 对于小规模数组, 使用插入排序
         */
        if( r - l <= 15 ){
            insertionSort(arr, l, r);
            return;
        }

        int mid = (l + r) / 2;
        sort(arr, l, mid);
        sort(arr, mid + 1, r);

        /**
         * 优化1: 对于arr[mid] <= arr[mid+1]的情况,不进行merge
         * 对于近乎有序的数组非常有效,但是对于一般情况,有一定的性能损失
         */
        if (arr[mid] > arr[mid + 1])
            merge(arr, l, mid, r);

    }

    //将arr[l...mid]和arr[mid+1...r]两部分进行归并
    private static void merge(Integer[] arr, int l, int mid, int r) {

        // 开辟临时空间,合并左半部分已经排好序的数组和右半部分已经排好序的数组
        Integer[] aux = Arrays.copyOfRange(arr, l, r + 1);

        // 初始化,i指向左半部分的起始位置索引;j指向右半部分起始索引位置mid+1
        int i = l, j = mid + 1;
        for (int k = l; k <= r; k++) {  // k指向两个元素比较后归并下一个需要放置的位置

            /**
             * 考虑数组越界
             */
            if (i > mid) {  // 如果左半部分元素已经全部处理完毕,
                arr[k] = aux[j - l];
                j++;
            } else if (j > r) {   // 如果右半部分元素已经全部处理完毕
                arr[k] = aux[i - l];
                i++; // l表示偏移
            }
            /**
             * 真正比较
             */
            else if (aux[i - l] < aux[j - l]) {  // 左半部分所指元素 < 右半部分所指元素
                arr[k] = aux[i - l];
                i++;
            } else {  // 左半部分所指元素 >= 右半部分所指元素
                arr[k] = aux[j - l];
                j++;
            }
        }
    }

    // 对arr[l...r]的区间使用InsertionSort排序
    public static void insertionSort(Integer[] arr, int l, int r){

        for( int i = l + 1 ; i <= r ; i ++ ){
            Integer e = arr[i];
            int j = i;
            for( ; j > l && arr[j-1]>e ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;
        }
    }

}

/**
     * 归并排序优化测试
     */
    @Test
    public void mergeOptimizeSortTest() {
        Integer[] integers0 = SortTestHelper.generateRandomArray(100, 0, 1000000);
        Integer[] integers1 = SortTestHelper.generateRandomArray(10000, 0, 1000000);
        Integer[] integers2 = SortTestHelper.generateRandomArray(100000, 0, 1000000);
        System.out.println("------------------------------随机数组--------------------------------");
        System.out.println("插入排序测试1数据量为100" + SortTestHelper.testSort(integers0, new InsertionSort()));
        System.out.println("插入排序测试2数据量为10000" + SortTestHelper.testSort(integers1, new InsertionSort()));
        System.out.println("插入排序测试3数据量为100000" + SortTestHelper.testSort(integers2, new InsertionSort()));
        System.out.println("归并排序测试1数据量为100" + SortTestHelper.testSort(integers0, new MergeSort()));
        System.out.println("归并排序测试2数据量为10000" + SortTestHelper.testSort(integers1, new MergeSort()));
        System.out.println("归并排序测试3数据量为100000" + SortTestHelper.testSort(integers2, new MergeSort()));
        System.out.println("归并排序优化测试1数据量为100" + SortTestHelper.testSort(integers0, new MergeSortOptimize()));
        System.out.println("归并排序优化测试2数据量为10000" + SortTestHelper.testSort(integers1, new MergeSortOptimize()));
        System.out.println("归并排序优化测试3数据量为100000" + SortTestHelper.testSort(integers2, new MergeSortOptimize()));

        Integer[] integers00 = SortTestHelper.generateNearlyOrderedArray(100, 50);
        Integer[] integers11 = SortTestHelper.generateNearlyOrderedArray(10000, 5000);
        Integer[] integers22 = SortTestHelper.generateNearlyOrderedArray(100000, 50000);
        System.out.println("------------------------------近乎有序数组--------------------------------");
        System.out.println("插入排序测试1数据量为100" + SortTestHelper.testSort(integers00, new InsertionSort()));
        System.out.println("插入排序测试2数据量为10000" + SortTestHelper.testSort(integers11, new InsertionSort()));
        System.out.println("插入排序测试3数据量为100000" + SortTestHelper.testSort(integers22, new InsertionSort()));
        System.out.println("归并排序测试1数据量为100" + SortTestHelper.testSort(integers00, new MergeSort()));
        System.out.println("归并排序测试2数据量为10000" + SortTestHelper.testSort(integers11, new MergeSort()));
        System.out.println("归并排序测试3数据量为100000" + SortTestHelper.testSort(integers22, new MergeSort()));
        System.out.println("归并排序优化测试1数据量为100" + SortTestHelper.testSort(integers00, new MergeSortOptimize()));
        System.out.println("归并排序优化测试2数据量为10000" + SortTestHelper.testSort(integers11, new MergeSortOptimize()));
        System.out.println("归并排序优化测试3数据量为100000" + SortTestHelper.testSort(integers22, new MergeSortOptimize()));
    }

执行结果

------------------------------随机数组--------------------------------
插入排序测试1数据量为100排序时长为: 0.001s
插入排序测试2数据量为10000排序时长为: 0.12s
插入排序测试3数据量为100000排序时长为: 8.578s
归并排序测试1数据量为100排序时长为: 0.0s
归并排序测试2数据量为10000排序时长为: 0.013s
归并排序测试3数据量为100000排序时长为: 0.025s
归并排序优化测试1数据量为100排序时长为: 0.0s
归并排序优化测试2数据量为10000排序时长为: 0.001s
归并排序优化测试3数据量为100000排序时长为: 0.003s
------------------------------近乎有序数组--------------------------------
插入排序测试1数据量为100排序时长为: 0.0s
插入排序测试2数据量为10000排序时长为: 0.039s
插入排序测试3数据量为100000排序时长为: 2.764s
归并排序测试1数据量为100排序时长为: 0.0s
归并排序测试2数据量为10000排序时长为: 0.0s
归并排序测试3数据量为100000排序时长为: 0.009s
归并排序优化测试1数据量为100排序时长为: 0.0s
归并排序优化测试2数据量为10000排序时长为: 0.0s
归并排序优化测试3数据量为100000排序时长为: 0.0s
发布了87 篇原创文章 · 获赞 69 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/lp284558195/article/details/103448354