归并排序的分析与Java实现

简介:

归并操作(merge),也叫归并算法,指的是将两个已经排序的序列合并成一个序列的操作。
归并排序算法依赖归并操作。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
归并排序在众多排序算法中既是稳定排序,效率也比较高,同时,归并排序不仅可以用于内排序,还可以用于外排序。

1.两个有序数列的合并

设两个有序数列放在同一向量中相邻的位置上:R[low..m],R[m+1..high],先将它们合并到一个局部的暂存向量 R1中,待合并完成后将 R1 复制回 R[low..high]中。
(1)合并过程
合并过程中,设置 i,j 和 p 三个指针,其初值分别指向这三个记录区的起始位置。

合并时依次比较 R[i]和 R[j]的关键字,取关键字较小的记录复制到 R1[p]中,然后将被复制记录的指针 i 或 j 加 1,以及指向复制位置的指针 p 加 1。

重复这一过程直至两个输入的子文件有一个已全部复制完毕,此时将另一非空的子文件中剩余记录依次复制到 R1 中即可。

(2)动态申请 R1
实现时,R1 是动态申请的,因为申请的空间可能很大,所以在工程上应用时,可能需要加入申请空间是否成功的处理。

2.归并排序的实现

(1)二路归并的思路
将数组划均分为两个子数组;
对两个字数组进行排序;
将排序好的两个字数组归并。
所谓 N路归并 是指将数组均分为N个子数组,将字数组排序后再归并。二路归并是归并排序的最一般的情况。


(2)归并排序实现的一般化过程
申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
设定两个指针,最初位置分别为两个已经排序序列的起始位置
比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
重复步骤3直到某一指针到达序列尾
将另一序列剩下的所有元素直接复制到合并序列尾

(3)实现过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
public  class  MergeSort {
     
     public  static  void  main(String[] args){
         int [] arr= new  int []{ 8 , 7 , 6 , 5 , 4 , 3 , 2 , 1 };
         for ( int  i= 0 ;i<arr.length;i++){
             System.out.print(arr[i]+ "," );          
         }
         //想起引用传递和值传递的问题,数组是引用传递,不需要什么返回值
         sort(arr,arr.length);
         System.out.println( "排序后" ); 
         for ( int  i= 0 ;i<arr.length;i++){
             System.out.print(arr[i]+ "," );          
         }
     }
     
     public  static  void  sort( int [] arr, int  n){
         if (arr== null  || n< 2 ){
             return ; //边界情况
         }
         divide(arr, 0 ,n- 1 );
     }
 
     /**
      *
      * @param arr 待排序数组
      * @param left 待排序区间左侧下标
      * @param right 待排序区间右侧下标
      */
     public  static  void  divide( int [] arr, int  left, int  right){
         
         if (left>=right){
             return ;
         }
         int  m=(left+right)/ 2 ; //从中间开始分成两个区间进行归并
         divide(arr,left,m); //一直递归划分直到区间长度为1
         divide(arr,m+ 1 ,right);
         //开始逐级往上进行归并操作
         binaryMerge(arr,left,m,right); //第一次进行merge操作的递归栈最深层此时是merge(arr,0,0,1)
     }
     
     /**
      * 对数组arr[left...right]位置进行递归的归并排序
      * @param arr
      * @param left
      * @param rigtht
      * @param temp
      */
     public  static  void  binaryMerge( int [] arr, int  left, int  m, int  right){
         /**
          * 归并排序的时间复杂度为O(n),指的就是最大长度为n的临时数组
          */
         int [] temp= new  int [right-left+ 1 ];
         int  l=left; //
         int  r=m+ 1 ; //
         int  index= 0 ; //
         
         while (l<=m && r<=right){
             if (arr[l]<arr[r]){
                 temp[index]=arr[l];
                 index++;
                 l++;
             } else {
                 temp[index]=arr[r];
                 index++;
                 r++;
             }
         }
         
         /**
          * 交换完了如果哪一侧数组还有剩余,则全部赋值
          * 实际的一次归并下面的两个while循环只会执行一个
          * warn!不要漏了等于的情况!否则会每两个元素丢失一个元素
          */
         while (l<=m){
             temp[index]=arr[l];
             index++;
             l++;
         }
         
         while (r<=right){
             temp[index]=arr[r];
             index++;
             r++;
         }
         
         /**
          * 用临时数组的部分排序的序列全部替换待排序数组中对应的部分
          * 这里应该用temp.length,不能用arr.length
          */
         for ( int  i= 0 ;i<temp.length;i++){
             //注意是left,此时的l是已经变化了的
             arr[left+i]=temp[i];
         }
         
     }
     
}

  

(4)时间和空间复杂度
归并排序的最好、最坏和平均时间复杂度都是O(nlogn),而空间复杂度是O(n),
比较次数介于(nlogn)/2和(nlogn)-n+1,赋值操作的次数是(2nlogn)。因此可以看出,归并排序算法比较占用内存,但却是效率高且稳定的排序算法。

3.空间复杂度为O(1)的归并排序

归并排序的空间复杂度为O(N),
通过优化可以把空间复杂度降为O(1),通过手摇算法,
但实际上通过手摇算法后,此时的时间复杂度会上升。

4.多路归并排序

归并排序的思想可以用于外排序。
外排序是相对内排序而言的,在常规的小规模排序过程中,都是直接在内存中对数据进行排序处理的,而对于数据量极大的排序问题,这种方式是不现实的。

这个时候就要通过外排序来进行,先将数据划分成多个规模能在内存中处理的子集,对各个子集排序后存放在临时的磁盘文件上,然后再将这些子集归并到输出文件中。

这个过程要使用到多路归并。

 



本文转自邴越博客园博客,原文链接:http://www.cnblogs.com/binyue/p/3421123.html,如需转载请自行联系原作者

相关文章
|
5天前
|
监控 Java 开发者
Java一分钟之-Java性能分析与调优:JProfiler, VisualVM等工具
【5月更文挑战第21天】本文介绍了Java性能优化的两个利器——JProfiler和VisualVM。JProfiler通过CPU Profiler、内存分析器和线程视图帮助解决过度CPU使用、内存泄漏和线程阻塞问题;VisualVM则聚焦于GC行为调整和类加载优化,以减少内存压力和提高应用性能。使用这些工具进行定期性能检查,是提升Java应用效率的关键。
20 0
|
1天前
|
存储 分布式计算 Java
深入探究JAVA编程语言:概念、应用与实例分析
**JAVA**是广泛应用的高级编程语言,以其易学性、跨平台能力和高效的性能著称。它采用面向对象编程,强调封装、继承和多态,且具备平台无关性、内置安全性和多线程支持。JAVA广泛应用于Web开发(如JSP、Servlet)、移动应用(Android开发)、大数据处理(Hadoop、Spark)和桌面应用。通过一个计算两数之和的简单示例,展示了JAVA的易读性和面向对象特性,帮助读者理解JAVA在实际开发中的运用。
|
2天前
|
算法 搜索推荐 Java
Java数据结构 -- 常见算法分析(查找算法、排序算法)精解详解!!!
Java数据结构 -- 常见算法分析(查找算法、排序算法)精解详解!!!
6 0
|
11天前
|
Java 关系型数据库 MySQL
【Java Spring开源项目】新蜂(NeeBee)商城项目运行、分析、总结
【Java Spring开源项目】新蜂(NeeBee)商城项目运行、分析、总结
164 4
|
11天前
|
Java
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
28 1
|
11天前
|
Java
JAVA循环结构分析与设计
JAVA循环结构分析与设计
20 1
|
11天前
|
网络协议 物联网 Java
Go与Java:在物联网领域的适用性分析
本文对比分析了Go和Java在物联网领域的适用性。Go语言因其轻量级、高效和并发特性,适合资源受限的物联网设备,特别是处理并发连接和数据流。Java则凭借跨平台性、丰富的生态系统和企业级应用能力,适用于大型物联网系统和复杂业务场景。两者在物联网领域各有优势,开发者可根据项目需求选择合适的语言。
|
11天前
|
Java
【Java基础】面向对象和内存分析
【Java基础】面向对象和内存分析
21 0
|
11天前
|
存储 分布式计算 大数据
使用 Java 进行大数据处理和分析
【4月更文挑战第19天】本文探讨了Java在大数据处理中的关键作用,涉及Hadoop框架、HDFS数据存储、MapReduce编程模型及Spark等数据分析工具。还包括数据预处理、可视化、性能优化、安全与隐私保护以及完整处理流程。Java在金融、医疗、电商等领域有广泛应用,为大数据洞察和决策提供支持,但同时也需要开发者具备深厚的技术背景和实践经验。
|
11天前
|
Java
java如何实现一个高效的二叉搜索树(BST)?请给出时间复杂度分析。
这段内容介绍了Java中使用AVL树实现高效二叉搜索树的方法。AVL树是一种自平衡树,节点子树高度差不超过1,确保操作时间复杂度为O(log n)。代码包括了`Node`类和`AVLTree`类,实现了节点、插入、删除、查找和平衡旋转等方法。通过旋转操作,维持树的平衡,保证了搜索效率。
17 6