【JAVA】归并排序

归并排序是建立在归并操作上的有效排序算法,采用分治法。先使子序列有序,再合并成完全有序序列,即二路归并。其速度仅次于快速排序,是稳定排序算法。还介绍了归并操作原理及归并排序稳定、比较和移动次数特点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。(速度仅次于快速排序,为稳定排序算法一般用于对总体无序,但是各子项相对有序的数列。)

算法描述

归并操作的工作原理如下:

第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列

第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置

第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置

重复步骤3直到某一指针超出序列尾,将另一序列剩下的所有元素直接复制到合并序列尾

特点

归并排序是稳定的排序.即相等的元素的顺序不会改变。

如输入记录 1(1) 3(2) 2(3) 2(4) 5(5) (括号中是记录的关键字)时,输出的 1(1) 2(3) 2(4) 3(2) 5(5) 中的2 和 2 是按输入的顺序。

这对要排序数据包含多个信息而要按其中的某一个信息排序,要求其它信息尽量按输入的顺序排列时很重要。

归并排序的比较次数小于快速排序的比较次数,移动次数一般多于快速排序的移动次数。

代码

package 归并排序;

import java.util.ArrayList;
import java.util.List;

/**
 * 归并排序
 */
//顾名思义,先进行递归,后进行合并。

public class Collections {
    /**
     * 递归
     *
     * @param list
     */
    public static void sort(List list) {
        int l = 0;
        //list.size()-1指的是最后一个索引
        int r = list.size() - 1;
        //链表用的是数组,索引从0开始。
    }


    /**
     * 归:分治
     */
    //先将集合传进来,通过数组或链表将其分成两个数组或两个链表。
    //实际上是将两个数组或链表的索引范围从逻辑上分治出来。
    //比如:0~10的数组,我们将其分为0~5的数组和6~10的数组。
    //而实际上还是一个数组,并没有真正分开。

    //list;链表\left;起始索引\right:结束索引
    //在这里要对List进行分,而分的条件是:left < right
    //而left == right的时候就说明只剩一个数组了,
    //即数组里剩下的未比较的元素就剩一个元素了。
    private static void devide(List list, int left, int right) {
        //这里分的时候,要先计算一下中值索引。
        int mid = (left + right) / 2;
        if (left < right) {
            //左边
            devide( list, left, mid );
            //右边
            devide( list, mid + 1, right );
            //这里不断进行递归,只要不像等,就一直在归。
            //一旦left和right相等的时候,就要并了。

            //} else {
            //这里要注意,当左右两边都分完的时候,
            //原数组里就剩下一个元素了。
            //此时,递归也就自动终止并返回分好ed数组了。
            //所以这里不用再判断,而是直接进行合并。

            /*并(merge)*/
            //我们可以将该方法写到下面的块里。
            //但是,为了方便,我们就在下面写并的方法。

            //合并两个数组
            merge( list, left, mid, mid + 1, right );

            //这里我们在写的时候,我们可以反着来写这个方法。
            //将光标放到上面哪一行代码。组合键alt+enter,
            //然后选择Creat method 'merge' in 'Collections'
        }
    }

    /**
     * 并
     *
     * @param list
     * @param left
     * @param mid
     * @param i
     * @param right
     */
    private static void merge(List list, int left, int mid, int i, int right) {

        //合并的时候,我们可以创建一个临时集合。
        Object[] temp = new Object[list.size()];
        //list.size()指的是数组长度

        int index = left;
        int ls = left, le = mid;//左边的起始索引和结束索引
        int rs = i, re = right;//右边的起始索引和结束索引

        //左又都是从第一个开始比,比完后。
        //如果左边的元素小的话将其放到临时数组里,
        //然后左边的索引加一,再比下一个。
        //如果右边的元素小的话将其放到临时数组里,
        //然后右边的索引加一,再比下一个。
        //如此一直到结束。
        //所以我们初始化一个索引为left,来记录索引。
        //index不能从0开始,而应该从left开始。
        //因为,先在我们比较的数组是逻辑数组,
        //它们的起始索引不一定是从0开始的。


        //至少把一边的所有数组元素按顺序放入临时数组。
        while (ls <= le && rs <= re) {
            //这个两个条件都必须满足,不然就退出了。
            //因为这样就意味着有一边已经排完了。

            //if(list.get(ls)<list.get(rs)){...}
            //这里对象是不能直接比的,比的应该是数据。

            //但是我们在这里可以实现Comparable接口。
            Comparable o1 = (Comparable) list.get( ls );
            Comparable o2 = (Comparable) list.get( rs );
            if (o1.compareTo( o2 ) == -1) {
                //这里判断o1是否小于o2

                //o1小于o2时则排左边
                //因为是升序排,所以先将o1放进临时数组里。
                temp[index] = o1;
                ls++;
            } else {
                //o1大于o2时则排右边
                temp[index] = o2;
                rs++;
            }
            index++;
            //至少把一边的所有数组元素按顺序放入临时数组。
            //但是还是会有剩下的元素。

            // 判断左边是否有剩余的元素。
            if (ls <= le) {
                for (int j = ls; j <= le; j++) {
                    temp[index++] = list.get( j );
                }
            }
            //也可能左边没有剩余的元素,那么我们就

            //判断右边是否有剩余元素

            //这里之所以还要等于re的原因是因为
            // temp[index] = o1;如果在这里放完了
            //ls++;会多加一,ls就比le大了。
            //所以,省的那个元素的索引ls正好是等于le的。
            if (rs <= re) {
                for (int k = rs; k <= re; k++) {
                    temp[index++] = list.get( k );
                }
            }

            //将临时数组里的元素放回原来的数组里
            for (int l = left; l <= right; l++) {
                list.set( l, temp[l] );
            }

        }
    }

    public static void main(String[] args) {
        List s = new ArrayList();
        s.add( new Student( 121, "张三", 'm' ) );
        s.add( new Student( 123, "李四", 'm' ) );
        s.add( new Student( 122, "王五", 'm' ) );
        java.util.Collections.sort( s );
        System.out.println( s );
    }
}
[Student{id=121, name='张三', sex=m}, Student{id=122, name='王五', sex=m}, Student{id=123, name='李四', sex=m}]
### 归并排序的基本原理 归并排序是一种经典的序算法,其核心思想是**分治法**。它通过将数组分成若干子序列,对每个子序列进行序,然后再将它们合并为一个有序的序列。具体步骤包括: - **分解**:将待序序列分成两个子序列。 - **解决**:递归地对两个子序列进行归并排序。 - **合并**:将两个有序的子序列合并成一个有序的序列。 归并排序具有稳定性和较好的时间复杂度,适用于大规模数据的序。其时间复杂度为 $ O(N \log N) $,其中 $ N $ 是数据量。这是因为将数列分开成小数列一共要 $ \log N $ 步,每步都是一个合并有序数列的过程,时间复杂度为 $ O(N) $,因此总的时间复杂度为 $ O(N \log N) $。 ### Java 实现归并排序算法 以下是一个基于分治思想的 Java 实现示例: ```java public class MergeSort { // 主方法,用于启动归并排序 public static void mergeSort(int[] array) { if (array == null || array.length <= 1) { return; } mergeSort(array, 0, array.length - 1); } // 递归实现归并排序 private static void mergeSort(int[] array, int left, int right) { if (left >= right) { return; } int mid = (left + right) / 2; mergeSort(array, left, mid); // 对左半部分序 mergeSort(array, mid + 1, right); // 对右半部分序 merge(array, left, mid, right); // 合并两个有序部分 } // 合并两个子数组的方法 private static void merge(int[] array, int left, int mid, int right) { // 创建临时数组 int[] temp = new int[right - left + 1]; int i = left, j = mid + 1, k = 0; // 比较左右子数组元素,按顺序放入临时数组 while (i <= mid && j <= right) { if (array[i] <= array[j]) { temp[k++] = array[i++]; } else { temp[k++] = array[j++]; } } // 将剩余的左半部分元素复制到临时数组 while (i <= mid) { temp[k++] = array[i++]; } // 将剩余的右半部分元素复制到临时数组 while (j <= right) { temp[k++] = array[j++]; } // 将临时数组的元素复制回原数组 for (int p = 0; p < temp.length; p++) { array[left + p] = temp[p]; } } // 测试归并排序 public static void main(String[] args) { int[] data = {38, 27, 43, 3, 9, 82, 10}; System.out.println("序前的数组:"); for (int num : data) { System.out.print(num + " "); } mergeSort(data); System.out.println("\n序后的数组:"); for (int num : data) { System.out.print(num + " "); } } } ``` ### 归并排序的优化方法 归并排序在实现时可以进行一些优化,以进一步提高性能: - **小数组优化**:当子数组长度较小时,可以采用插入序替代归并排序,因为插入序在小规模数据上具有更低的常数因子。 - **避免额外空间拷贝**:可以通过交替使用主数组和辅助数组来减少数据拷贝的开销。 - **自然归并排序**:利用输入数据中已存在的有序段,减少分割步骤,提高效率。 ### 应用场景 归并排序由于其稳定的 $ O(N \log N) $ 时间复杂度,特别适用于: - **外部序**:处理大规模数据,尤其是当数据存储在外部存储器(如硬盘)时。 - **链表序**:归并排序适合链表结构,因为它不需要随机访问,而只需要顺序访问。 - **需要稳定序的场景**:例如在数据库中对记录进行序时,需要保持相同键值的原始顺序。 ### 优缺点分析 **优点**: - 时间复杂度稳定为 $ O(N \log N) $,适用于大规模数据。 - 是一种稳定序算法,适合需要保持相同元素顺序的场景。 - 适用于链表结构的序。 **缺点**: - 需要额外的存储空间,空间复杂度为 $ O(N) $。 - 对于小规模数据,效率不如插入序等简单算法。 ### 归并排序的扩展 归并排序还可以扩展为**多路归并排序**,即每次将数组分成多个子序列进行序,然后再合并。这种变体在处理分布式数据或外部序时非常有用。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-_星耀_-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值