归并排序与快排:分治算法的双子星

72 阅读3分钟

前言

在算法的星空中,归并排序与快速排序如同双子星般闪耀。它们共享O(n log n)的时间复杂度,却用截然不同的方式演绎分治思想。作为前端开发者,理解这对"双子星"能让你在性能优化中游刃有余。

分治思想:算法的黄金法则

先明白分治思想是什么。
分治思想是解决复杂问题的核心策略,其本质是"化繁为简"。它包含三个关键步骤:

  1. 分解:将原问题拆分成多个规模更小的子问题
  2. 解决:递归地解决这些子问题(当子问题足够简单时,直接求解)
  3. 合并:将子问题的解组合成原问题的解

下面要讲的两种算法都离不开这个思想。

归并排序:稳定的编织者

归并排序通过递归拆分数组,再合并两个有序子数组。其稳定性(相同元素顺序不变)使其在需要保持原始顺序的场景中不可替代。

function mergeSort(arr) {
  if (arr.length <= 1) return arr; // 递归终止条件

  const mid = Math.floor(arr.length / 2);
  const left = mergeSort(arr.slice(0, mid)); // 递归拆分左半
  const right = mergeSort(arr.slice(mid));   // 递归拆分右半

  return merge(left, right); // 合并有序子数组
}

function merge(arr1, arr2) {
  let res = [], i = 0, j = 0;
  while (i < arr1.length && j < arr2.length) {
    // 比较元素,小的放入结果
    if (arr1[i] <= arr2[j]) res.push(arr1[i++]);
    else res.push(arr2[j++]);
  }
  // 拼接剩余元素(一个数组已遍历完)
  return res.concat(arr1.slice(i)).concat(arr2.slice(j));
}

时间复杂度稳定O(n log n),空间复杂度O(n),稳定排序

快速排序:高效的分区魔术师

快排通过选择基准元素(pivot)将数组分为左右两部分,递归排序。其原地操作特性大幅降低空间开销。

function quickSort(arr) {
  if (arr.length <= 1) return arr;
  
  const pivot = arr[0]; // 基准元素(可优化为随机选择)
  const left = [], right = [];
  
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] < pivot) left.push(arr[i]); // 小于基准
    else right.push(arr[i]); // 大于等于基准
  }
  
  return [...quickSort(left), pivot, ...quickSort(right)];
}

平均时间复杂度O(n log n),最坏O(n²)(可优化),空间复杂度O(log n),不稳定排序

算法总结与对比

特性归并排序快速排序
时间复杂度稳定O(n log n)平均O(n log n)
空间复杂度O(n)O(log n)
稳定性✅ 稳定❌ 不稳定
适用场景链表排序、稳定需求数组排序、内存受限

当需要稳定排序时(如按时间排序的事件日志),归并排序更可靠; 但在内存受限的场景中(如大型数组),其原地操作特性使其成为多数语言标准库的选择。

实战练习:力扣75题 - 颜色分类

image.png 这道题完美演示了快排的分区思想——无需完整排序,一次遍历即可完成分类。

function sortColors(nums) {
  let left = 0, right = nums.length - 1, i = 0;
  
  while (i <= right) {
    if (nums[i] === 0) {
      // 0放左边:交换后i前进(左边换来的元素已处理)
      [nums[left], nums[i]] = [nums[i], nums[left]];
      left++;
      i++;
    } else if (nums[i] === 2) {
      // 2放右边:交换后i不前进(右边换来的元素待处理)
      [nums[right], nums[i]] = [nums[i], nums[right]];
      right--;
    } else {
      i++; // 1直接跳过
    }
  }
}

这道题是快排分区思想的精简版。将数组分为<1(0)、=1(1)、>1(2)三部分,一次遍历完成,时间复杂度O(n),空间复杂度O(1)。

结语:算法选择的艺术

归并排序与快排不是非此即彼的抉择,而是算法智慧的双翼——当需要轻盈的效率时,快排如疾风般掠过;当需要沉稳的可靠时,归并排序如磐石般伫立。掌握这对双子星,便能在代码的海洋中精准定位,让每一次排序都成为问题的优雅解构,让每一段算法都闪耀着思维的光芒。

"算法不是代码的堆砌,而是问题的精准解构。" —— 归并与快排,正是这种解构思维的完美体现。