🎨
Notes
  • 持续更新中...
  • articles
    • browser
      • 深入理解现代浏览器 - 导航
      • 深入理解现代浏览器 - 架构
      • 深入理解现代浏览器 - 交互
      • 深入理解现代浏览器 - 渲染器进程
    • dsa
      • DSA - 并查集
      • DSA - 哈希表
      • DSA - AVL 树
      • DSA - 二叉树
      • 快速选择
      • Big O 算法复杂度
      • DSA - 栈和队列
      • DSA - 前缀树 Trie
      • DSA - 图
      • DSA - 链表
      • DSA - 递归
    • typescript
      • TypeScript 学习笔记 - 任意属性 (Indexable Types)
      • 力扣的 TypeScript 面试题
      • TypeScript 学习笔记 - as const
      • TypeScript 学习笔记 - infer
    • network
      • Internet Protocol (IP)
      • 计算机网络基础
      • 如何分辨同源和同站
      • DNS 如何查询 IP 地址?
    • vue
      • Nuxt.js 入门
      • 从零实现一个 Mini Vue
      • 从零实现一个简单的 VDOM 引擎
      • 从零实现一个响应式状态管理
    • sorting
      • 排序 - 归并排序
      • 排序 - 冒泡排序
      • 排序 - 选择排序
      • 排序 - 计数排序
      • 排序 - 插入排序
    • compile
      • Compiler and Interpreter
      • Just-In-Time (JIT) Compilers
      • 编译流程
    • others
      • 什么是上下文无关语法
      • 如何在终端打印出有颜色的字
    • dev-ops
      • github-actions
        • GitHub Action 简介
        • GitHub Actions for CI
    • workflow
      • 用 Node 写一个 cli
      • 如何规范 git commit 信息
      • 如何监听 git hooks
      • 如何规范代码风格 - prettier
      • 如何发布一个 npm package
      • 如何规范代码质量 - eslint
    • design-pattern
      • 代理模式
      • 单例模式
      • 策略模式
    • security
      • 点击劫持
      • CSP 内容安全策略
    • javascript
      • 尾调用优化
      • 4种常见的内存泄漏及解决方法
    • unit-test
      • Test Vuejs Application - Chapter 2
      • Test Vuejs Application - Chapter 1
      • Vue Unit Test Intro
    • performance
      • HTTP 缓存
      • 如何优化图片资源
Powered by GitBook
On this page
  • 算法
  • 复杂度
  • 代码
  • 应用

Was this helpful?

  1. articles
  2. sorting

排序 - 归并排序

合并排序是一种Divide and Conquer(分治)的排序算法,把问题逐步拆分成子问题,直到子问题的规模小到可以解决,再自底向上合并子问题的解,最终得到原问题的解。

算法

Divide

假设 m 是数组 A 的中点,那么我们可以把数组分成 A[0..m] 和 A[m+1..end] 两个部分。

Conquer

继续将每个小数组都拆分成两个部分,直到不能再拆分,也就是数组中只剩下 1 个或 0 个数字的时候(base case)。

Combine

将这两个部分分别排好序后,再将它们合并成一个新的有序数组。将两个有序数组合并成一个有序数组,这个还是比较简单的。

复杂度

  • 时间复杂度:$O(n*log n)$,n 为数组长度。不断地将数组一分为二,这个操作的时间复杂度是 $O(log n)$。每次递归中,合并两个有序数组的时间复杂度是 $O(n)$。

  • 空间复杂度:$O(n)$,合并两个子数组时会需要新建一个临时数组,而这个临时数组的最大长度就是原数组的长度。

代码

JavaScript Code

function mergeSort(arr, left, right) {
    // base case
    if (left >= right) return 0;

    const mid = Math.floor(left + (right - left) / 2);
    mergeSort(arr, left, mid);
    mergeSort(arr, mid + 1, right);

    merge(arr, left, mid, right);
}

function merge(arr, left, mid, right) {
    // 1. 使用一个临时数组,
    // 将有序的左右两部分 [left, mid] 和 [mid + 1, right] 先合并到临时数组 sortedPart 中
    const sortedPart = [];
    let p = left,
        q = mid + 1,
        s = 0;
    while (p <= mid && q <= right) {
        if (arr[p] <= arr[q]) {
            sortedPart[s++] = arr[p++];
        } else {
            sortedPart[s++] = arr[q++];
        }
    }
    while (p <= mid) {
        sortedPart[s++] = arr[p++];
    }
    while (q <= right) {
        sortedPart[s++] = arr[q++];
    }

    // 2. 将 sortedPart 里面的元素一一更新到原数组中的对应位置
    p = 0;
    q = left;
    while (p < sortedPart.length) {
        arr[q++] = sortedPart[p++];
    }
}

或者另一种写法,差不多。

function mergeSort(arr) {
    // base case
    if (arr.length <= 1) return arr;

    // 将数组一分为二,分别排序(两个子数组是拷贝)
    const m = Math.floor(arr.length / 2);
    const sortedLeft = mergeSort(arr.slice(0, m));
    const sortedRight = mergeSort(arr.slice(m));

    // 拿到排好序的 sortedLeft 和 sortedRight后,直接在原数组 arr 上合并成一个有序数组
    let s = 0,
        p = 0,
        q = 0;
    while (p < sortedLeft.length && q < sortedRight.length) {
        if (sortedLeft[p] <= sortedRight[q]) {
            arr[s++] = sortedLeft[p++];
        } else {
            arr[s++] = sortedRight[q++];
        }
    }
    while (p < sortedLeft.length) {
        arr[s++] = sortedLeft[p++];
    }
    while (q < sortedRight.length) {
        arr[s++] = sortedRight[q++];
    }

    // 排好序后这个部分也需要返回
    // arr 本身也是一个更大的数组的一部分
    return arr;
}

应用

PrevioussortingNext排序 - 冒泡排序

Last updated 4 years ago

Was this helpful?

求逆序对问题。

【剑指 Offer 51. 数组中的逆序对】