归并排序(Merge Sort)

简介: 算法介绍算法描述动图演示算法分析代码实现参考

算法介绍


 归并排序(Merge Sort)是利用分治的思想实现的一种稳定的排序方法,该算法采用经典的分治(divide-and-conquer)策略,分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案合并在一起,即分而治之。


算法描述


 一、分(Divide):将N个元素分成两个含N/2个元素的子序列。


 二、治(Conquer):用合并排序法对两个子序列递归的排序。


 三、合(Combine):合并两个已排序的子序列以得到排序结果。


动图演示


20210522174728447.gif

算法分析


    时间复杂度:O(NlogN)


    空间复杂度:O(N)


    稳定性:稳定


    归并排序以O(NlogN)的最坏时间复杂度运行,而使用的比较次数几乎是最优的。算法中最基本的操作是合并两个已排序的表,合并的时间很显然是线性的。合并时使用了两个辅助数组的额外空间,并且为了合并的方便设置哨兵


    虽然归并排序的运行时间是O(NlogN),但是它有一个明显的问题,即合并两个已排序数组的时候用到线性附加内存数据的多次拷贝明显减慢了排序速度,因此较少使用归并排序算法进行内部排序。


    归并排序与其他O(NlogN)的排序算法比较,归并排序的运行时间严重依赖于比较元素和在数组(以及临时数组)中移动元素的相对开销。值得一提的是,这些开销是与语言相关的。


     例如,在Java中,进行一次元素比较可能是昂贵的(因为比较可能不容易被内嵌,从而动态调度的开销可能会减慢执行速度),但是移动元素则是省时的(因为它们是引用的赋值,而不是庞大对象的拷贝)。归并排序使用所有流行的排序算法中最少的比较次数,因此是使用Java的通用排序算法中的上好的选择。事实上,它就是标准Java类库中泛型排序所使用的算法。


     在C++的泛型排序中,如果对象庞大,那么拷贝对象可能需要很大的开销,而由于编译器具有主动执行内嵌优化的能力,因此比较对象常常是相对省时的。在这种情况下,如果我们还能够使用更少的数据移动,那么有理由让一个算法多使用一些比较。快速排序则达到了这种平衡,并且是C++库中通常所使用的排序算法。在Java中,快速排序也用作基本类型的标准库排序。


代码实现

// C++
void mergeSort(int a[], int length, int left, int right) {  // 归并排序(递归版),启动时left = 0,right = length- 1
  int mid = left + (right - left) / 2;  // 中间(分割)位置 
  if (left < right) {  // 递归出口 
  mergeSort(a, length, left, mid);  // 递归排序左序列 
  mergeSort(a, length, mid + 1, right);  // 递归排序右序列
  merge(a, left, mid, right);  // 合并左右子序列 
  }
}
void merge(int a[], int left, int mid, int right) {  // 合并左右子序列 
  int n1 = mid - left + 1;  // 左序列元素个数 
  int n2 = right - mid;  // 右序列元素个数 
  int L[n1 + 1];  // 左辅助数组 
  int R[n2 + 1];  // 右辅助数组 
  for (int i = 0; i < n1; i++) {  // 初始化左辅助数组
  L[i] = a[left + i];
  }
  for (int i = 0; i < n2; i++) {  // 初始化右辅助数组
  R[i] = a[mid + i + 1];
  }
  // 哨兵,用于合并时的边界判断 
  const int INF = 0x7fffffff;  // 32字节int最大值 
  L[n1] = INF;  // 以无穷大(32字节int最大值)为数组结束标志 
  R[n2] = INF;  // 以无穷大(32字节int最大值)为数组结束标志
  int i = 0;  // L数组指针 
  int j = 0;  // R数组指针 
  for (int k = left; k <= right; k++) {  // 每次选取两数组中较小者 
  if (L[i] <= R[j]) {  // L[i] <= R[j]
    a[k] = L[i];  // L[i]填入 
    i++;  // L数组指针后移 
  } else {  // L[i] > R[j] 
    a[k] = R[j];  // R[j]填入 
    j++;  // R数组指针后移 
  }
  }
}


// Java
class MergeSort {
    // 归并排序驱动程序1(全排序)
    public void mergeSort(int[] array) {
        mergesort(array, 0, array.length - 1);  // 调用私有方法排序
    }
    // 归并排序驱动程序2(部分排序)
    public void mergeSort(int[] array, int left, int right) {
        mergesort(array, left, right);  // 调用私有方法排序
    }
    private void mergesort(int[] array, int left, int right) {
        if (left < right) {  // 递归出口
            int mid = left + (right - left) / 2;  // 中间(分割)位置
            mergesort(array, left, mid);  // 递归排序左序列
            mergesort(array, mid + 1, right);  // 递归排序右序列
            merge(array, left, mid, right);  // 合并左右子序列
        }
    }
    // 合并左右子序列
    private void merge(int[] array, int left, int mid, int right) {
        int leftlength = mid - left + 1;  // 左序列元素个数
        int rightlength = right - mid;  // 右序列元素个数
        int[] arrayL = new int[leftlength + 1];  // 左辅助数组
        int[] arrayR = new int[rightlength + 1];  // 右辅助数组
        for (int i = 0; i < leftlength; ++i) {  // 初始化左辅助数组
            arrayL[i] = array[left + i];
        }
        arrayL[leftlength] = Integer.MAX_VALUE;// 哨兵,用于合并时的边界判断
        for (int i = 0; i < rightlength; ++i) {  // 初始化右辅助数组
            arrayR[i] = array[mid + i + 1];
        }
        arrayR[rightlength] = Integer.MAX_VALUE;// 哨兵,用于合并时的边界判断
        int i = 0;  // L数组指针
        int j = 0;  // R数组指针
        for (int k = left; k <= right; ++k) {  // 每次选取两数组中较小者
            if (arrayL[i] <= arrayR[j]) {
                array[k] = arrayL[i++];  // 填入较小者,指针后移
            } else {
                array[k] = arrayR[j++];  // 填入较小者,指针后移
            }
        }
    }
}



参考


算法导论

数据结构与算法分析(Java语言描述)

数据结构(C语言版)

————————————————

版权声明:本文为CSDN博主「Acx7」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/Acx77/article/details/117166826

相关文章
|
4天前
|
搜索推荐 算法 Java
sort-07-merge sort 归并排序
这是一个关于排序算法的系列文章摘要。文章涵盖了多种排序算法的详细解释,包括冒泡排序、快速排序、选择排序、堆排序、插入排序、希尔排序、归并排序、计数排序、桶排序以及大文件外部排序。归并排序是一种效率为O(nlogn)的排序算法,基于分治法,将序列分成两半,分别排序后再合并。文章提供了Java实现的递归和迭代版本。在归并排序的递归实现中,代码通过不断拆分和合并子序列完成排序,而迭代实现则是通过逐步增大子序列长度并进行两两归并来排序。整个系列可在GitHub找到相关源码。
|
4天前
|
搜索推荐 算法 Java
sort-05-insert sort 插入排序算法详解
这是一个关于排序算法的系列文章总结,包括冒泡排序、快速排序、选择排序、堆排序、插入排序等10种排序算法的详细讲解和Java实现。插入排序是一种简单直观的排序算法,通过构建有序序列并逐个插入新元素来排序。提供的Java代码展示了插入排序的实现过程,并附有测试示例。所有算法已开源在GitHub项目[https://github.com/houbb/sort](https://github.com/houbb/sort)中。
|
4天前
|
搜索推荐 算法 Java
sort-03-SelectSort 选择排序算法详解
这是一个关于排序算法的系列文章摘要,包括了各种排序算法的介绍和Java实现。文章列举了冒泡排序、快速排序、选择排序、堆排序、插入排序、希尔排序、归并排序、计数排序、桶排序以及大文件外部排序的链接和简要说明。其中,选择排序算法被详细解释,它是通过找到未排序序列中的最小元素并将其放到正确位置来逐步构建有序序列的。Java实现中,选择排序的`doSort`方法通过两层循环实现,时间复杂度为`O(N^2)`,是稳定的排序算法。整个排序项目已开源在GitHub。
|
4天前
|
搜索推荐 算法 Java
sort-08-counting sort 计数排序
这是一个关于排序算法的系列文章摘要。文章详细介绍了多种排序算法,包括冒泡排序、快速排序、选择排序、堆排序、插入排序、希尔排序、归并排序、计数排序、桶排序以及大文件外部排序。计数排序是一种线性时间复杂度的稳定排序算法,由 Harold H. Seward 在1954年提出。基础版计数排序通过创建一个与最大元素等长的新数组来统计每个元素出现的次数,然后填充排序结果。改良版则考虑了空间浪费问题,通过找出最小值来减少数组长度。此外,还提出了使用 TreeMap 来扩展排序算法以适应非数字元素的情况。
|
2月前
|
搜索推荐 C++ 索引
常闲排序算法-merge讲解
常闲排序算法-merge讲解
10 1
|
2月前
|
搜索推荐 C++ 索引
常闲排序算法merge讲解
常闲排序算法merge讲解
23 0
|
11月前
|
存储 搜索推荐
十大排序之Merge Sort 归并排序
十大排序之Merge Sort 归并排序
2-路插入排序(Two-Way Insertion Sort)
算法介绍 算法描述 算法分析 代码实现 参考
|
搜索推荐 算法 数据可视化
【基础篇】9 # 排序:冒泡排序(Bubble Sort)、插入排序(Insertion Sort)、选择排序(Selection Sort)
【基础篇】9 # 排序:冒泡排序(Bubble Sort)、插入排序(Insertion Sort)、选择排序(Selection Sort)
76 0
【基础篇】9 # 排序:冒泡排序(Bubble Sort)、插入排序(Insertion Sort)、选择排序(Selection Sort)
|
算法 测试技术
LeetCode 88. 合并两个有序数组 Merge Sorted Array
LeetCode 88. 合并两个有序数组 Merge Sorted Array