归并排序的js实现

83 阅读3分钟

一、归并排序思想

  1. 归并排序就是先把左半边数组排好序,再把右半边数组排好序,然后把两半数组合并,类似二叉树的后序遍历。
// 定义:排序 nums[lo..hi]
void sort(int[] nums, int lo, int hi) {
    if (lo == hi) {
        return;
    }
    int mid = (lo + hi) / 2;
    // 利用定义,排序 nums[lo..mid]
    sort(nums, lo, mid);
    // 利用定义,排序 nums[mid+1..hi]
    sort(nums, mid + 1, hi);

    /****** 后序位置 ******/
    // 此时两部分子数组已经被排好序
    // 合并两个有序数组,使 nums[lo..hi] 有序
    merge(nums, lo, mid, hi);
    /*********************/
}

// 将有序数组 nums[lo..mid] 和有序数组 nums[mid+1..hi]
// 合并为有序数组 nums[lo..hi]
void merge(int[] nums, int lo, int mid, int hi);


/* 二叉树遍历框架 */
void traverse(TreeNode root) {
    if (root == null) {
        return;
    }
    traverse(root.left);
    traverse(root.right);
    /****** 后序位置 ******/
    print(root.val);
    /*********************/
}

2 对比快速排序:快速排序是先将一个元素排好序,然后再将剩下的元素排好序,类似二叉树的前序遍历。

void sort(int[] nums, int lo, int hi) {
    if (lo >= hi) {
        return;
    }
    // 对 nums[lo..hi] 进行切分
    // 使得 nums[lo..p-1] <= nums[p] < nums[p+1..hi]
    int p = partition(nums, lo, hi);
    // 去左右子数组进行切分
    sort(nums, lo, p - 1);
    sort(nums, p + 1, hi);
}
/* 二叉树遍历框架 */
void traverse(TreeNode root) {
    if (root == null) {
        return;
    }
    /****** 前序位置 ******/
    print(root.val);
    /*********************/
    traverse(root.left);
    traverse(root.right);
}

参考:labuladong.github.io/algo/di-yi-…

二、归并js实现,版本1(使用temp,存入排序后的数组)

// 归并排序
function mergeSort(arr, left, right) {
  if (left >= right) return
  let mid = Math.floor((left + right) / 2)
  mergeSort(arr, left, mid)
  mergeSort(arr, mid + 1, right)
  merge(arr, left, mid, right)
}
function merge(arr, left, mid, right) {
  let temp = []
  let i = left
  let j = mid + 1
  let k = 0
  while (i <= mid && j <= right) {
    if (arr[i] <= arr[j]) {
      temp[k++] = arr[i++]
    } else {
      temp[k++] = arr[j++]
    }
  }
  while (i <= mid) {
    temp[k++] = arr[i++]
  }
  while (j <= right) {
    temp[k++] = arr[j++]
  }
  for (let i = 0; i < k; i++) {
    arr[left + i] = temp[i]
  }
}

// 测试
const arr3 = [1, 3, 2, 5, 4, 6, 7, 8, 9, 10]
mergeSort(arr3, 0, arr3.length - 1)
console.log('mergesort', arr3)

三、归并js实现,优化版2(减少temp的创建和释放)

使用temp,存入排序前的数组。只是是变更了merge函数

function mergeSort(arr, left, right) {
  if (left >= right) return
  let mid = Math.floor((left + right) / 2)
  mergeSort(arr, left, mid)
  mergeSort(arr, mid + 1, right)
  merge(arr, left, mid, right)
}
const temp = []
function merge(arr, left, mid, right) {
  for (let i = left; i <= right; i++) {
    temp[i] = arr[i]
  }
  // 比较左右两边的元素,谁小就先放谁
  let i = left
  let j = mid + 1
  while (i <= mid && j <= right) {
    if (temp[i] <= temp[j]) {
      arr[left++] = temp[i++]
    } else {
      arr[left++] = temp[j++]
    }
  }
  // 如果左边还有剩余,直接放到结果数组中
  while (i <= mid) {
    arr[left++] = temp[i++]
  }
  // 如果右边还有剩余,直接放到结果数组中
  while (j <= right) {
    arr[left++] = temp[j++]
  }
}

四、算法分析

image.png 执行的次数是二叉树节点的高度,每次执行的复杂度就是每个节点代表的子数组的长度,所以总的时间复杂度就是整棵树中「数组元素」的个数。

所以从整体上看,这个二叉树的高度是 logN,其中每一层的元素个数就是原数组的长度 N,所以总的时间复杂度就是 O(NlogN)。