(SUB)归并排序时间、空间复杂度

时间avg时间min时间max空间avg稳定性

O(nlog(n))

e37880c6cb519241cbe5e6bab69fc61d.png

O(nlog(n))

(此处可优化最小时间复杂度至o(n)见代码注释及下图)

在这里插入图片描述

O(nlog(n))

1bcb4d9ef1294fcd66f0d7ea26e37575.png

o(n)(Divide-and-Conqure)

临时数组在合并后空间会释放

o(1)(In-place Algorithm原地归并排序

https://www.cnblogs.com/xiaorenwu702/p/5880841.html)

稳定性:取决于合并函数的写法 (若以[start:middle]<=[middle:terminal]做判断则稳定,反之不稳定)

:稳定

:空间复杂度为o(n),以空间换时间。算法中需要来回复制结果数组和原序列,很耗时,所以归并排序一般用于外排序

适用数列较大要求稳定

归并排序改进措施

  1. 直接将辅助数组作为参数传入,而不直接使用静态数组
  2. 小排序问题:划分为小序列,做直接插入排序,再采用归并排序。一般可以将归并排序的时间缩短 10% ~ 15%;
  3. 判断测试数组是否已经有序:如果 arr[mid] <= arr[mid+1],我们就认为数组已经是有序的并跳过merge() 方法,可以是任意有序的子数组算法的运行时间变为线性的。
  4. 不回写:这个策略是最重点要讲述的策略。写回操作在大排序问题时非常浪费时间,我们就思考一种不回写策略来解决这个问题。merge() 方法中不将元素复制到辅助数组,节省数组复制的时间。调用两种排序方法,一种:将数据从输入数组排序到辅助数组;另一种:将数据从辅助数组排序到输入数组。
    重点:在每个层次交换输入数组和辅助数组的角色。
  5. 最长无逆序子序列:我们经过分析知道,归并排序的基础是两个有序子序列的合并,那么我们可以通过寻找最长无逆序子序列来优化归并排序的比较次数。例如,(4,5,63,7,1)这个序列,我们找到三个无逆序子序列,直接对这三个子序列进行合并即可,减少比较次数。
  6. 递归:消除递归,避免递归过程的时间消耗。
package main.Test;


/**
 * 归并排序优化方案
 */
public class GuiBingSort {

    // 数组的长度为 7 时,切换
    private static final int CUTOFF = 7;

    private static void merge(Comparable[] src, Comparable[] dst, int lo, int mid, int hi) {
        int indexI = lo;
        int indexJ = mid + 1;
        for (int indexK = lo; indexK <= hi; indexK++) {
            if (indexI > mid) {
                dst[indexK] = src[indexJ++];
            } else if (indexJ > hi) {
                dst[indexK] = src[indexI++];
            } else if (less(src[indexJ], src[indexI])) {
                dst[indexK] = src[indexJ++];
            } else {
                dst[indexK] = src[indexI++];
            }
        }
    }

    // 递归方式
    private static void sort(Comparable[] src, Comparable[] dst, int lo, int hi) {
        // 优化方案②:应该在子数组长度为 7 的时候切换到插入排序
        if (hi <= lo + CUTOFF) {
            insertionSort(dst, lo, hi);
            System.arraycopy(dst, lo, src, lo, hi - lo + 1);
            return;
        }
        int mid = lo + ((hi - lo) >> 1);

        // 优化方案④:在每个层次交换输入数组和辅助数组的角色
        sort(dst, src, lo, mid);
        sort(dst, src, mid + 1, hi);

        //优化方案③:判断测试数组是否已经有序
        if (!less(src[mid + 1], src[mid])) {
            System.arraycopy(src, lo, dst, lo, hi - lo + 1);
            return;
        }

        // 优化方案④:merge() 方法中不将元素复制到辅助数组
        merge(src, dst, lo, mid, hi);
    }

//    非递归
    private static void sort(Comparable[] src, Comparable[] dst) {
        int n = src.length;
        int len = 1;
        int flag = 1;
        while (len < n) {
            if (flag == 1) {//通过flag来判定当前是奇数回合还是偶数回合
                flag = 0;
                int i;
                if (len <= CUTOFF) {
                    for (i = 0; i < n + 1 - 2 * len; i = i + 2 * len) {
                        insertionSort(dst, i, i + 2 * len - 1);
                        System.arraycopy(dst, i, src, i, 2 * len);
                    }
                    if (i + len - 1 < n) {
                        insertionSort(dst, i, n - 1);
                        System.arraycopy(dst, i, src, i, n - i);
                    } else {
                        for (; i < n; i++) dst[i] = src[i];
                    }
                } else {
                    for (i = 0; i < n + 1 - 2 * len; i = i + 2 * len) {
                        merge(src, dst, i, i + len - 1, n - 1);
                    }
                    if (i + len - 1 < n) {
                        merge(src, dst, i, i + len - 1, n - 1);
                    } else {
                        for (; i < n; i++) dst[i] = src[i];
                    }
                }
                len *= 2;
            } else {
                flag = 1;
                int i;
                if (len <= CUTOFF) {
                    for (i = 0; i < n + 1 - 2 * len; i = i + 2 * len) {
                        insertionSort(src, i, i + 2 * len - 1);
                        System.arraycopy(src, i, dst, i, 2 * len);
                    }
                    if (i + len - 1 < n) {
                        insertionSort(src, i, n - 1);
                        System.arraycopy(src, i, dst, i, n - i);
                    } else {
                        for (; i < n; i++) src[i] = dst[i];
                    }
                } else {
                    for (i = 0; i < n + 1 - 2 * len; i = i + 2 * len) {
                        merge(dst, src, i, i + len - 1, n - 1);
                    }
                    if (i + len - 1 < n) {
                        merge(dst, src, i, i + len - 1, n - 1);
                    } else {
                        for (; i < n; i++) src[i] = dst[i];
                    }
                }
                len *= 2;
            }
        }
        if (flag == 1) {
            for (int p = 0; p < n; p++) {
                dst[p] = src[p];
            }
        }

    }

//  入口
    public static void sort(Comparable[] dst, int type) {
        // 优化方案①:直接将辅助数组作为参数传入
        Comparable[] src = dst.clone();
        if (type == 0) {
            sort(src, dst, 0, dst.length - 1);
        } else if (type == 1) {
            sort(src, dst);
        }

    }

    private static void insertionSort(Comparable[] arr, int lo, int hi) {
        for (int indexI = lo; indexI <= hi; indexI++) {
            for (int indexJ = indexI; indexJ > lo && less(arr[indexJ], arr[indexJ - 1]); indexJ--) {
                exchange(arr, indexJ, indexJ - 1);
            }
        }
    }

    /**
     * 比较两个元素的大小
     *
     * @param comparableA 待比较元素A
     * @param comparableB 待比较元素B
     * @return 若 A < B,返回 true,否则返回 false
     */
    private static boolean less(Comparable comparableA, Comparable comparableB) {
        return comparableA.compareTo(comparableB) < 0;
    }

    /**
     * 将两个元素交换位置
     *
     * @param arr    待交换元素所在的数组
     * @param indexI 第一个元素索引
     * @param indexJ 第二个元素索引
     */
    private static void exchange(Comparable[] arr, int indexI, int indexJ) {
        Comparable temp = arr[indexI];
        arr[indexI] = arr[indexJ];
        arr[indexJ] = temp;
    }

    /**
     * 打印数组的内容
     *
     * @param arr 待打印的数组
     */
    private static void show(Comparable[] arr) {
        for (int index = 0; index < arr.length; index++) {
            System.out.print(arr[index] + " ");
        }
        System.out.println();
    }

    /**
     * 判断数组是否有序
     *
     * @param arr 待判断数组
     * @return 若数组有序,返回 true,否则返回 false
     */
    public static boolean isSort(Comparable[] arr) {
        for (int index = 1; index < arr.length; index++) {
            if (less(arr[index], arr[index - 1])) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        Comparable[] a = {102,1,2,4,3,6,3,5,2,1002,1223,1212};
        sort(a, 1);
        System.out.println(isSort(a));
        show(a);

    }

}
<think>嗯,用户想知道归并排序在最好和最坏情况下的时间复杂度表现。我需要根据提供的引用资料来整理答案。首先,归并排序是基于分治法的,所以应该拆分成分解、解决子问题、合并这几个步骤。根据引用[2],分解时间是常数O(1),解决问题时间是递归处理两个子问题,即2T(n/2),合并的时间是O(n)。所以总的时间复杂度是T(n)=2T(n/2)+O(n)。然后这个递归式可以用递归树来解,结果是O(n log n)。引用[2]还提到无论最好、最坏还是平均情况,都是O(n log n),因为不管数据如何分布,分解和合并的步骤都是固定的,合并操作总是需要比较和移动元素。那为什么用户会问最好和最坏情况呢?可能其他排序算法在这两种情况有差异,比如快速排序,但归并排序不同。需要强调归并排序的稳定性,合并过程中是否有序不影响步骤,所以时间复杂度稳定。可能用户混淆了归并和其他排序的区别,需要明确说明归并的最好和最坏情况都是相同的。然后引用[3]提到归并排序时间复杂度是O(n)空间复杂度O(n),但这里应该是指合并操作的时间。引用[5]也说明递归分解后的子问题处理是T(N/2),所以综合起来,无论数据如何,归并的时间复杂度都是O(n log n)。所以结论是,归并排序在最好和最坏情况下的时间复杂度均为O(n log n),稳定性好。需要引用相关引用资料来支持这一点,尤其是引用[2]和引用[5]。</think>### 归并排序时间复杂度分析 归并排序时间复杂度在**最好情况**、**最坏情况**和**平均情况下均为 $O(n \log n)$**。这一结论基于其分治法的实现特性,具体分析如下: --- #### **时间复杂度稳定性的原因** 1. **分治法的固定流程** 归并排序无论输入数据如何分布,始终遵循以下步骤: - **分解**:将长度为 $n$ 的序列均分为两个子序列,时间为 $O(1)$; - **递归排序**:对两个子序列分别递归排序,时间为 $2T(n/2)$; - **合并**:合并两个有序子序列,时间为 $O(n)$。 总时间公式为: $$ T(n) = 2T\left(\frac{n}{2}\right) + O(n) $$ 通过递归树展开或主定理可得,时间复杂度为 $O(n \log n)$[^2][^5]。 2. **合并操作的稳定性** 合并阶段需要逐个比较和移动元素,无论原始数据是否部分有序,合并操作的时间始终为 $O(n)$[^3]。因此,数据分布对时间复杂度无影响。 --- #### **与快速排序的对比** 快速排序的性能依赖于基准元素的选择,**最坏情况时间复杂度为 $O(n^2)$**(如数据已有序且基准固定)。而归并排序因固定的分治策略避免了此类问题[^1]。 --- #### **空间复杂度** 归并排序需要额外的空间存储临时合并结果,空间复杂度为 $O(n)$[^3]。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值