归并排序算法

简介: 归并排序是一种基于分治思想的高效排序算法,通过将序列不断划分为不可再分的子序列,再两两合并完成排序。其时间复杂度为O(nlogn),适用于对序列进行升序或降序排列。

归并排序算法是在分治算法基础上设计出来的一种排序算法,它可以对指定序列完成升序(由小到大)或降序(由大到小)排序,对应的时间复杂度O(nlogn)


归并排序算法实现排序的思路是:

  • 将整个待排序序列划分成多个不可再分的子序列,每个子序列中仅有 1 个元素;
  • 所有的子序列进行两两合并,合并过程中完成排序操作,最终合并得到的新序列就是有序序列。


举个简单的例子,使用归并排序算法对 {7, 5, 2, 4, 1, 6, 3, 0} 实现升序排序的过程是:


1) 将 {7, 5, 2, 4, 1, 6, 3, 0} 分割成多个子序列,每个子序列中仅包含 1 个元素,分割过程如下所示:


图1:归并排序算法分割序列的过程


整个序列不断地被一分为二,最终被分割成 {7}、{5}、{2}、{4}、{1}、{6}、{3}、{0} 这几个序列。


2) 将 {7}、{5}、{2}、{4}、{1}、{6}、{3}、{0} 以“两两合并”的方式重新整合为一个有序序列,合并的过程如下图所示:


图2:归并排序算法整合所有子序列的过程

归并排序算法的具体实现

对比图 1 和图 2 很容易联想到,归并排序算法可以借助递归的思想实现,对应的伪代码如下:

输入 arr[n]                                // 输入要排序的序列

merge_sort(arr[n] , p , q):                // [p , q] 表示对第 p ~ q 区域内的元素进行归并排序

   if p < q :                             // 对 [p , q] 区域不断采用对半分割的方式,最终将整个区域划分为多个仅包含 1 个元素(p==q)的序列

       mid = ⌊(p+q)/2⌋

       merge_sort(arr , p , mid)

       merge_sort(arr , mid+1 , q)

       merge(arr , p , mid , q)          // 调用实现归并过程的代码模块

merge_sort() 用于将整个序列分割成多个子序列,merge() 用来合并这些子序列,合并的实现方式为:

  1. 从 [p, mid] 和 [mid+1, q] 两个区域的元素分别拷贝到 leftarr 和 rightarr 区域。
  2. 从 leftarr 和 rightarr 区域中各个取出第一个元素,比较它们的大小;
  3. 将较小的元素拷贝到 [p, q] 区域,然后从较小元素所在的区域内取出下一个元素,继续进行比较;
  4. 重复执行第 3 步,直至 leftarr 和 rightarr 内的元素全部拷贝到 [p, q] 为止。如果 leftarr 或者 rightarr 有一方为空,则直接将另一方的所有元素依次拷贝到 [p, q] 区域。


对应的伪代码如下:

merge(arr[n] , p , mid , q):                          // 该算法表示将 [p , mid] 和 [mid+1 , q] 做归并操作

   leftnum <- mid - p + 1                            // 统计 [p , mid] 区域内的元素个数

   rightnum <- q - mid                               // 统计 [mid+1 , q] 区域内的元素个数

   leftarr[leftnum] <- arr[p ... mid]                // 分别将两个区域内的元素各自拷贝到另外两个数组中

   rightarr[rightnum] <- arr[mid+1 ... q]

   i <- 1 , j <- 1

   for k <- p to q :             // 从 leftarr 和 rightarr 数组中第 1 个元素开始,比较它们的大小,将较小的元素拷贝到 arr 数组的 [p , q] 区域

       if leftarr[i] ≤ rightarr[j] :

           arr[k] = leftarr[i]

           i <- i+1

       else :

           arr[k] = right[j]

           j <- j+1


结合伪代码,如下是用归并排序算法对 {7, 5, 2, 4, 1, 6, 3, 0} 进行升序排序的C语言程序:

  1. #include <stdio.h>
  2. //实现分割操作的函数
  3. void merge_sort(int* arr, int p, int q);
  4. //实现归并操作的函数
  5. void merge(int* arr, int p, int mid, int q);

  6. int main() {
  7. int i = 0;
  8. int arr[8] = { 7,5,2,4,1,6,3,0 };
  9. //对 arr 数组中第 1 至 8 个元素进行归并排序
  10. merge_sort(arr, 1, 8);
  11. while (i < 8)
  12. {
  13. printf("%d ", arr[i]);
  14.        i++;
  15. }
  16. return 0;
  17. }
  18. //实现分割操作的函数,[p,q] 用于指定归并排序的区域范围,
  19. void merge_sort(int* arr, int p, int q) {
  20. int mid;
  21. if (arr == NULL || p > q || p == q) {
  22. return ;
  23. }
  24.    mid = (p + q) / 2;
  25. //将 [p,q] 分为[p,mid] 和 [mid+1,q] 区域
  26. merge_sort(arr, p, mid);
  27. merge_sort(arr, mid + 1, q);
  28. //对分好的 [p,mid] 和 [mid,q] 进行归并操作
  29. merge(arr, p, mid, q);
  30. }
  31. //实现归并操作的函数,归并的 2 个区域分别为 [p,mid] 和 [mid+1,q]
  32. void merge(int* arr, int p, int mid, int q) {
  33. int i,j,k;
  34. int leftarr[100], rightarr[100];
  35. int numL = mid - p + 1;
  36. int numR = q - mid;
  37. //将 arr 数组中 [p,mid] 区域内的元素逐一拷贝到 leftarr 数组中
  38. for (i = 0; i < numL; i++) {
  39.        leftarr[i] = arr[p - 1 + i];
  40. }
  41. //将 arr 数组中 [mid+1,q] 区域内的元素逐一拷贝到 rightarr 数组中
  42.    leftarr[i] = 2147483647;
  43. for (i = 0; i < numR; i++) {
  44.        rightarr[i] = arr[mid + i];
  45. }
  46.    rightarr[i] = 2147483647;
  47.    i = 0;
  48.    j = 0;
  49. //逐一比较 leftarr 和 rightarr 中的元素,每次将较小的元素拷贝到 arr 数组中的 [p,q] 区域内
  50. for (k = p; k <= q; k++) {
  51. if (leftarr[i] <= rightarr[j]) {
  52.            arr[k - 1] = leftarr[i];
  53.            i++;
  54. }
  55. else {
  56.            arr[k - 1] = rightarr[j];
  57.            j++;
  58. }
  59. }
  60. }


如下是用归并排序算法对 {7, 5, 2, 4, 1, 6, 3, 0} 进行升序排序的 Java 程序:

  1. public class Demo {
  2. //实现归并排序算法的分割操作
  3. public static void merge_sort(int[] arr, int p, int q) {
  4. // 如果数组不存在或者 [p.q] 区域不合理
  5. if (arr == null || p >= q) {
  6. return;
  7. }
  8. //对[p,q]区域进行分割
  9. int mid = (p + q) / 2;
  10. merge_sort(arr, p, mid);
  11. merge_sort(arr, mid + 1, q);
  12. //对分割的 [p,mid] 和 [mid+1,q] 区域进行归并
  13. merge(arr, p, mid, q);
  14. }
  15. //实现归并排序算法的归并操作
  16. public static void merge(int[] arr, int p, int mid, int q) {
  17. int numL = mid - p + 1;
  18. int numR = q - mid;
  19. //创建 2 个数组,分别存储 [p,mid] 和 [mid+1,q]区域内的元素
  20. int[] leftarr = new int[numL + 1];
  21. int[] rightarr = new int[numR + 1];
  22. int i;
  23. for (i = 0; i < numL; i++) {
  24.            leftarr[i] = arr[p - 1 + i];
  25. }
  26. //将 leftarr 数组中最后一个元素设置为足够大的数。
  27.        leftarr[i] = 2147483647;
  28. for (i = 0; i < numR; i++) {
  29.            rightarr[i] = arr[mid + i];
  30. }
  31. //将 rightarr 数组中最后一个元素设置为足够大的数。
  32.        rightarr[i] = 2147483647;
  33. int j = 0;
  34.        i = 0;
  35. //对 leftarr 和 rightarr 数组中存储的 2 个区域的元素做归并操作
  36. for (int k = p; k <= q; k++) {
  37. if (leftarr[i] <= rightarr[j]) {
  38.                arr[k - 1] = leftarr[i];
  39.                i++;
  40. } else {
  41.                arr[k - 1] = rightarr[j];
  42.                j++;
  43. }
  44. }
  45. }
  46. public static void main(String[] args) {
  47. int[] arr = new int[] { 7, 5, 2, 4, 1, 6, 3, 0 };
  48. //对 arr 数组中第 1 至 8 个元素进行归并排序
  49. merge_sort(arr, 1, 8);
  50. for (int i : arr) {
  51.            System.out.print(i + " ");
  52. }
  53. }
  54. }


如下是用归并排序算法对 {7, 5, 2, 4, 1, 6, 3, 0} 进行升序排序的 Python 程序:

  1. #实现归并排序算法中的分割操作,[p,q]为指定分割区域
  2. def merge_sort(arr,p,q):
  3. #列表中没有数据,或者 [p,q]区域不存在
  4. if len(arr) == 1 or p >= q:
  5. return
  6. #对 [p,q] 区域进行分割
  7.    mid = int( (p + q) / 2 )
  8. merge_sort(arr,p,mid)
  9. merge_sort(arr,mid+1,q)
  10. #归并 [p,mid] 和 [mid+1,q] 区域
  11. merge(arr,p,mid,q)
  12. #实现归并排序算法中的归并操作,归并区域为 [p.mid] 和 [mid+1,q]
  13. def merge(arr,p,mid,q):
  14.    numL = mid - p + 1;
  15.    numR = q - mid;
  16. #分别将 [p,mid] 和 [mid+1,q] 区域内的元素拷贝到 leftarr 和 rightarr 列表中
  17.    leftarr = arr[p-1:p+numL-1]
  18.    rightarr = arr[mid:mid+numR]
  19. # 2 个列表末尾添加一个足够大的数
  20.    leftarr.append(float('inf'))
  21.    rightarr.append(float('inf'))
  22.    i=0
  23.    j=0
  24.    k=p
  25. #逐个比较 leftarr 和 rightarr 列表中的元素,每次将较小的元素添加到 arr 列表中的 [p,q] 区域内
  26. while k <= q:
  27. if leftarr[i] <= rightarr[j]:
  28.            arr[k-1] = leftarr[i]
  29.            i = i + 1
  30. else:
  31.            arr[k-1] = rightarr[j]
  32.            j = j + 1
  33.        k = k + 1

  34. arr = [7, 5, 2, 4, 1, 6, 3, 0]
  35. #对 arr 数组中第 1 至 8 个元素做归并排序操作
  36. merge_sort(arr, 1, 8)
  37. print(arr)


以上程序的输出结果均为:

0 1 2 3 4 5 6 7

相关文章
|
1月前
|
算法 Java C语言
弗洛伊德算法求最短路径
弗洛伊德算法用于寻找加权图中各顶点间的最短路径,适用于无向图和有向图。算法基于动态规划思想,通过枚举中间顶点来更新路径,确保最终得到最短路径。该算法要求路径权值非负,否则可能出错。
105 0
|
1月前
|
存储 算法
最小生成树的概念与思想
数据结构中的图存储结构包含顶点和边,分为连通图与非连通图。生成树是包含所有顶点、任意两点间仅有一条通路的极小连通图。最小生成树则是权值总和最小的生成树,常用于解决道路建设等实际问题,常用算法有普里姆算法和克鲁斯卡尔算法。
43 0
|
1月前
|
算法 Java C语言
汉诺塔问题
汉诺塔问题源自印度古老传说,涉及将一组圆盘从一根柱子移动到另一根,遵循特定规则。文章详细介绍了该问题的背景、解决思路以及如何使用分治算法实现,同时提供了C语言、Java和Python的代码示例。
128 0
|
1月前
|
算法
回溯算法的基本思想
本节介绍回溯算法,通过图1中从A到K的路径查找示例,说明其与穷举法的异同。回溯算法通过“回退”机制高效试探各种路径,适用于决策、优化和枚举问题。
43 0
|
1月前
|
算法 搜索推荐
分治算法的基本思想
分治算法通过将复杂问题拆分成多个独立的小问题,逐一解决后再合并结果。适合处理大规模数据,常见应用包括排序、查找最大值/最小值及汉诺塔等问题。
53 0
|
1月前
|
存储 算法 Java
寻找图中是否存在路径
本节介绍了如何使用并查集判断图中两个顶点之间是否存在有效路径。通过将图的边信息存储到并查集中,同一连通区域的顶点会处于同一集合。只需比较两个顶点的根节点是否相同,即可判断它们是否连通。文中提供了C语言、Java和Python的实现代码,并通过示例验证了算法的正确性。
33 0
|
2月前
|
存储 NoSQL 关系型数据库
1-MongoDB相关概念
MongoDB 是一种高性能、无模式的文档型数据库,适合需要灵活数据模型、高扩展性和大规模数据存储的应用场景。适用于新项目快速开发、高并发读写、海量数据存储及地理文本查询等需求,且支持类似 JSON 的 BSON 数据格式,灵活易扩展。
50 0
|
1月前
|
存储 算法 Java
求数组中的最大值和最小值
本文介绍了在程序中如何查找数组中的最大值和最小值,重点讲解了两种算法:普通算法和分治算法。普通算法通过遍历数组直接比较元素大小,找出最值;而分治算法则通过递归将数组划分成更小的部分,分别找出各部分的最大值,最终合并结果得到整个数组的最大值。文章以 {3,7,2,1} 为例,详细演示了两种算法的实现过程,并提供了 C、Java 和 Python 的代码示例。
89 0
|
1月前
|
算法
贪心算法的基本思想
贪心算法是一种简单且常用的算法,每一步选择当前最优解,追求局部最优。文章通过纸币拼凑实例说明其原理,并指出贪心算法并不总能得到全局最优解,最后介绍了其在部分背包问题和经典算法中的应用。
54 0