用JS实现各大排序算法

排序算法对于大部分程序员来说都不陌生,很多人第一次在编码的过程中遇到的第一个算法可能就是排序算法。而且很多编程语言内部就内置了排序算法,比如 JSJava 中的 sort

本文会介绍如何怎么用 JS 写我们常见的几种排序算法(冒泡排序、插入排序、选择排序、归并排序、快速排序)

一些概念

在正片开始前,先说明2个概念

  1. 原地排序
    空间复杂度为O(1)的排序算法(即除了数据存储本身的空间不需要额外的辅助空间)。 冒泡、插入、选择都是原地排序的算法。
  2. 算法的稳定性
    待排序的序列中值相等的元素,在经过排序算法排序之后,原有的先后顺序保持不变。

冒泡排序

  • 时间复杂度:最好O(n), 最差O(n^2)
  • 是否原地排序:✅
  • 是否稳定排序:✅
  • 思路:相邻的元素一一比较
js 复制代码
/** 冒泡排序 */
const bubbleSort = (arr) => {
  let swapped = false; // 是否产生过交换
  const length = arr.length;
  for (let i = 0; i < length -  1; i++) {
    for (let j = 0; j < length - i - 1; j++) {
      // 交换
      if (arr[j] > arr[j + 1]) {
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
        swapped = true;
      }
      
    } 
    if (!swapped) {
       return arr;
    }
    swapped = false;
  }
  return arr;
};

插入排序

  • 时间复杂度:最好O(n), 最差O(n^2)
  • 是否原地排序:✅
  • 是否稳定排序:✅
  • 思路:将数据分为已排序区间未排序区间,取未排序区间的元素在已排序区间中找到合适的位置插入,并且保持已排序区间的数据一直有序
js 复制代码
const insertSort = (arr) => {
  const length = arr.length;
  let target;
  for(let i = 0 ;i < length; i++) {
    target = arr[i];
    let j = i - 1;
    for(; j >=0 ;j--) {
      if (arr[j] > target) {
        arr[j + 1] = arr[j] // 移动数据
      } else {
        break;
      }
    }
    arr[j + 1] = target // 插入数据
  }

  return arr;
};

选择排序

  • 时间复杂度:最好最差都是O(n^2)
  • 是否原地排序:✅
  • 是否稳定排序:❌
  • 思路:将数据分为已排序区间未排序区间,取未排序区间的元素中最小的元素,把它放在已排序区间的末尾
js 复制代码
const selectSort = (arr) => {
  let min;
  const length = arr.length;
  for (let i = 0; i < length - 1; i++) {
    min = i;
    for (let j = i + 1; j < length; j++) {
      if(arr[j] < arr[min]) {
        min = j;
      }
    }
    [arr[min], arr[i]] = [arr[i], arr[min]];
  }

  return arr;
};

归并排序

  • 时间复杂度:O(nlogn)
  • 空间复杂度 O(n)
  • 是否原地排序:❌
  • 是否稳定排序:✅
  • 思路:分治思想,把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起,这样整个数组就都有序了。(递归)
  • 图解:
js 复制代码
const merge = (leftArray, rightArray) => {
  const mergeArray: number[] = [];
  while (leftArray.length && rightArray.length) {
    if (leftArray[0] < rightArray[0]) {
      mergeArray.push(leftArray.shift());
    } else {
      mergeArray.push(rightArray.shift());
    }
  }
  return mergeArray.concat(leftArray, rightArray);
}


const mergeSort = (arr) => {
  if (arr.length < 2) {
    return arr;
  }
  const mid = Math.floor(arr.length / 2);
  // 将数组分成两部分
  const leftArr = mergeSort(arr.slice(0, mid));
  const rightArr = mergeSort(arr.slice(mid, arr.length));
  // 合并左右数组
  return merge(leftArr, rightArr);
}

快速排序

  • 时间复杂度:O(nlogn)
  • 是否原地排序:✅ (根据实现方式区分)
  • 是否稳定排序:❌
  • 思路:
  1. 先从数组中找一个基准数
  2. 让其他比它大的元素移动到数列一边,比他小的元素移动到数列另一边,从而把数组拆解成两个部分。
  3. 再对左右区间重复第二步,直到各区间只有一个数

这里根据是否原地排序可以分为两种写法
原地快排

js 复制代码
quickSort = (arr) => {
  // 交换的方法
  const swap = (swapArr, a, b) => {
    [swapArr[a], swapArr[b]] = [swapArr[b], swapArr[a]];
  };

  // 分区函数
  const partition = (partArray, left, right) => {
    const pivot = partArray[right];
    let storeIndex = left;
    // 交换元素 保证原地快排
    for(let i = left; i < right; i++) {
      if(partArray[i] < pivot) {
        swap(partArray, i, storeIndex);
        storeIndex++;
      }
    }
    swap(partArray, right, storeIndex);
    return storeIndex;
  };

  const sort = (sortArray, left, right) => {
    if (left > right) {
      return;
    }
    const pivotIndex = partition(sortArray, left, right);
    sort(sortArray, left, pivotIndex - 1);
    sort(sortArray, pivotIndex + 1, right);
  }

  sort(arr, 0, arr.length - 1);
  return arr;
};

非原地快排

js 复制代码
const quickSort = (arr) => {
  if(arr.length < 2) {
    return arr
  }
  const pivotIndex = Math.floor(arr.length / 2)
  const pivot = arr.splice(pivotIndex, 1)[0] // 找到分区点
  const leftArray = [], rightArray = []
  for(let i = 0; i < arr.length; i++) {
    if(arr[i] < pivot) {
      leftArray.push(arr[i])
    } else {
      rightArray.push(arr[i])
    }
  }
  return [...quickSort(leftArray), pivot, ...quickSort(rightArray)]
}
相关推荐
_.Switch44 分钟前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光1 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   1 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   1 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web1 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常1 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇2 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr2 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho3 小时前
【TypeScript】知识点梳理(三)
前端·typescript
安冬的码畜日常4 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js