JS-手写系列:四大排序算法

0 阅读3分钟

前言

在前端面试中,算法虽不是全部,但却是衡量逻辑思维能力的硬指标。掌握基础排序算法不仅能应对面试,更能加深对“时间复杂度”和“空间复杂度”的理解。本文将带你手写快速排序、选择排序、插入排序和冒泡排序。


🚀 算法性能概览

在开始手写之前,我们先通过表格对比各算法的性能:

排序算法平均时间复杂度空间复杂度稳定性
快速排序O(nlogn)O(n \log n)O(logn)O(\log n)不稳定
选择排序O(n2)O(n^2)O(1)O(1)不稳定
直接插入排序O(n2)O(n^2)O(1)O(1)稳定
冒泡排序O(n2)O(n^2)O(1)O(1)稳定

这份算法笔记非常实用,涵盖了前端面试中最基础且高频的四大排序算法。为了让它在掘金获得更好的阅读量,我为你重新设计了标题、增加了算法复杂度对比表,并使用 TypeScript 规范了代码。


一、 快速排序 —— 分而治之

1. 核心思路

通过一个“基准值”(flage)将数组分为两部分,左边放小于基准值的数,右边放大于基准值的数,然后递归对左右两部分进行同样操作,直到子数组长度为 1。

2. 实现

  const arr = [1, 2, 6, 5, 4, 8];
  const quickSort = (arr) => {
    if (arr.length <= 1) {
      return arr;
    }
    let index = Math.floor(arr.length / 2);
    let flage = arr.splice(index, 1)[0]; //注意splice提取出来的是数组
    const left = [];
    const right = [];
    arr.forEach((item) => {
      if (item < flage) {
        left.push(item);
      } else {
        right.push(item);
      }
    });
    //node是一个数,concat连接的是数组
    return quickSort(left).concat([flage]).concat(quickSort(right));
  };
  console.log(quickSort(arr));

二、 选择排序 —— 寻找最小值

1. 核心思路

从未排序的序列中找到最小(大)元素,存放到排序序列的起始位置,然后再从剩余未排序元素中继续寻找,直到排序完成。

2. 实现

const arr = [1, 2, 6, 5, 4, 8];
const selectSort = (arr) => {
for (let i = 0; i < arr.length; i++) {
  let index = i;
  let min = arr[i];
  for (let j = i + 1; j < arr.length; j++) {
    if (arr[j] < min) {
      index = j;
      min = arr[j];
    }
  }
  [arr[i], arr[index]] = [arr[index], arr[i]];
}
return arr;
};
console.log(selectSort(arr));

三、 直接插入排序 —— 扑克牌式排序

1. 核心思路

将数组分为“已排序”和“未排序”两部分。从第二位开始,取出一个数(tmp),在已排序区域中从后往前遍历,如果 tmp 更小,就将已排序的数向后移动,直到找到 tmp 的合适插入位置。

2. 实现

  const arr = [1, 2, 6, 5, 4, 8];
  const insertSort = (arr) => {
    //将数组从前往后以此排序,从1直接开始,因为前面那个数不影响
    for (let i = 1; i < arr.length; i++) {
      //tmp为要插入是数
      let tmp = arr[i];
      let j = i - 1;
      for (; j >= 0; j--) {
        //如果tmp比这个前面的数大或者相等,又比后面的数小,直接跳出并将tmp插入
        if (tmp >= arr[j]) {
          break;
        }
        //如果tmp比当前这个数小,就将当前这个数往后移动
        //一直移动到当前这个数小于或者等于tmp
        else {
          arr[j + 1] = arr[j];
        }
      }
      //不能将j设置为块级作用域,要不然下面的j会显示无定义
      arr[j + 1] = tmp;
    }
    return arr;
  };
  console.log(insertSort(arr));

四、 冒泡排序 —— 两两比对

1. 核心思路

通过相邻元素的比较和交换,使较大的元素逐渐“浮”到数组的末尾。每一轮循环都会确定当前剩余序列中的一个最大值。

2. 实现

  const arr = [1, 2, 6, 5, 4, 8];
  const bubbleSort = (arr) => {
    //将数组从前往后以此排序,从1直接开始,因为前面那个数不影响
    for (let i = 0; i < arr.length - 1; i++) {
      for (let j = 0; j < arr.length - i - 1; j++)
        if (arr[j] > arr[j + 1]) {
          [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
        }
    }
    return arr;
  };
  console.log(bubbleSort(arr));

💡 总结

  • 快排:最高效,但需要注意基准值的选取和递归栈空间。
  • 插入排序:在数组几乎已经有序的情况下,性能非常接近 O(n)O(n)
  • 选择与冒泡:更适合教学和理解基础算法逻辑,实际生产中较少直接使用。