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

归并排序

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

核心思想

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

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

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

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

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

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

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

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

代码开发

实现思路

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

Step 1−将列表递归分为两半,直到无法再将其划分为止。
Step 2−将若干个组两两合并,保证合并后的序列有序。
step 3-重复第二步操作直到只剩下一组,排序完成。

伪代码

package lipan.top.notes.basicsort.impl;

import lipan.top.notes.basicsort.ISort;

import java.util.Arrays;

/**
 * 

*

  • title: 基础排序-归并排序
  • *
  • @author: li.pan
  • *
  • Date: 2019/12/7 12:15 下午
  • *
  • Version: V1.0
  • *
  • Description:
  • */ public class MergeSort implements ISort { @Override public void sort(Integer[] arr) { int n = arr.length; sort(arr, 0, n - 1); } //递归使用归并排序,对arr[l....r]的范围进行进行排序 0,10 0/5 0/2 private static void sort(Integer[] arr, int l, int r) { System.out.println("sort: arr=" + Arrays.toString(arr) + ",l=" + l + ",r=" + r); if (l >= r) { //当子序列中只有一个元素递归到底的情况 System.out.println("------------------------------------------------------------------------------------------------"); return; } int mid = (l + r) / 2; // l=0 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) { System.out.println("merge: arr=" + Arrays.toString(arr) + ",l=" + l + ",mid=" + mid + ",r=" + r); // 开辟临时空间,合并左半部分已经排好序的数组和右半部分已经排好序的数组 Integer[] aux = Arrays.copyOfRange(arr, l, r + 1); System.out.println("merge: arr=" + Arrays.toString(aux) + ",l=" + l + ",mid=" + mid + ",r=" + r); // 初始化,i指向左半部分的起始位置索引;j指向右半部分起始索引位置mid+1 int i = l, j = mid + 1; for (int k = l; k <= r; k++) { // k指向两个元素比较后归并下一个需要放置的位置 System.out.println("第K=" + k + "趟"); /** * 考虑数组越界 */ if (i > mid) { // 如果左半部分元素已经全部处理完毕, arr[k] = aux[j - l]; j++; } else if (j > r) { // 如果右半部分元素已经全部处理完毕 arr[k] = aux[i - l]; i++; // l表示偏移 } /** * 真正比较: 两两比较放入新的序列arr */ else if (aux[i - l] < aux[j - l]) { // 左半部分所指元素 < 右半部分所指元素 arr[k] = aux[i - l]; i++; } else { // 左半部分所指元素 >= 右半部分所指元素(右边的放置待排序前面) arr[k] = aux[j - l]; j++; } } System.out.println("\n"); } }

    执行结果

    ------------------------------随机数组--------------------------------
    插入排序测试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 lipan.top.notes.basicsort.impl.optimize;
    
    
    import lipan.top.notes.basicsort.ISort;
    
    import java.util.Arrays;
    
    /**
     * 

    *

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

    执行结果

    ------------------------------随机数组--------------------------------
    插入排序测试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

     

    你可能感兴趣的:(数据结构,java,数据结构,归并排序)