JavaScript中的10种排序算法:从入门到精通

作为前端开发者,排序算法是我们必须掌握的基础知识。无论是在面试中,还是在实际开发中处理数据展示时,排序都是一个常见需求。今天,我将用通俗易懂的方式,带你了解JavaScript中最常见的10种排序算法。

1. 冒泡排序 - 最直观的排序方式

冒泡排序可能是最容易理解的排序算法了。它的基本思想是:重复地遍历要排序的数组,一次比较两个元素,如果它们的顺序错误就交换它们

想象一下水中的气泡,较大的气泡会慢慢浮到水面。在冒泡排序中,较大的元素会"冒泡"到数组的末端。

function bubbleSort(arr) {const n = arr.length;for (let i = 0; i < n - 1; i++) {let swapped = false; // 优化:如果一轮没有交换,说明已经有序for (let j = 0; j < n - 1 - i; j++) {if (arr[j] > arr[j + 1]) {[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; // ES6解构赋值交换元素swapped = true;}}if (!swapped) break; // 如果没有发生交换,提前结束}return arr;
}

特点

  • 时间复杂度:最好情况O(n)(已经有序),最坏O(n²)
  • 空间复杂度:O(1)(原地排序)
  • 稳定排序(相等元素不会改变相对位置)

适用场景:数据量小,或者作为学习排序的入门算法。

2. 选择排序 - 每次选择最小的元素

选择排序的思想是:每次从未排序部分选择最小的元素,放到已排序部分的末尾

function selectionSort(arr) {const n = arr.length;for (let i = 0; i < n; i++) {let minIndex = i; // 假设当前索引是最小值的索引for (let j = i + 1; j < n; j++) {if (arr[j] < arr[minIndex]) {minIndex = j; // 找到更小的值,更新索引}}[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]; // 交换}return arr;
}

特点

  • 时间复杂度:始终O(n²)
  • 空间复杂度:O(1)
  • 不稳定排序(可能改变相等元素的相对位置)

适用场景:数据量小,且不要求稳定排序的情况。

3. 插入排序 - 扑克牌排序方式

插入排序类似于我们整理扑克牌的方式:将数组分为已排序和未排序两部分,每次从未排序部分取出一个元素,插入到已排序部分的正确位置

function insertionSort(arr) {for (let i = 1; i < arr.length; i++) {let current = arr[i]; // 当前要插入的元素let j = i - 1;// 将比current大的元素向后移动while (j >= 0 && arr[j] > current) {arr[j + 1] = arr[j];j--;}arr[j + 1] = current; // 插入到正确位置}return arr;
}

特点

  • 时间复杂度:最好O(n)(已经有序),最坏O(n²)
  • 空间复杂度:O(1)
  • 稳定排序

适用场景:小规模数据,或者基本有序的数据。

4. 希尔排序 - 插入排序的改进版

希尔排序是插入排序的改进版本,也称为"缩小增量排序"。它通过将原始数组分成若干子序列进行插入排序,然后逐步缩小增量直至整个数组有序

function shellSort(arr) {let gap = Math.floor(arr.length / 2); // 初始步长while (gap > 0) {for (let i = gap; i < arr.length; i++) {let temp = arr[i];let j = i;// 对子序列进行插入排序while (j >= gap && arr[j - gap] > temp) {arr[j] = arr[j - gap];j -= gap;}arr[j] = temp;}gap = Math.floor(gap / 2); // 缩小步长}return arr;
}

特点

  • 时间复杂度:平均O(n^1.3),比O(n²)好很多
  • 空间复杂度:O(1)
  • 不稳定排序

适用场景:中等规模的数据排序。

5. 归并排序 - 分而治之的经典算法

归并排序采用分治法的思想:将数组分成两半,分别排序,然后将两个有序数组合并成一个有序数组

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(left, right) {const result = [];while (left.length && right.length) {// 比较两个数组的第一个元素,取较小的放入结果result.push(left[0] < right[0] ? left.shift() : right.shift());}return result.concat(left, right); // 合并剩余元素
}

特点

  • 时间复杂度:始终O(n log n)
  • 空间复杂度:O(n)(需要额外的存储空间)
  • 稳定排序

适用场景:大规模数据排序,特别是需要稳定排序的情况。

6. 堆排序 - 基于二叉堆的排序

堆排序是一种基于**二叉堆(优先队列)**的排序算法。它首先构建一个最大堆,然后不断取出堆顶元素(最大值)放到数组末尾。

function heapSort(arr) {const n = arr.length;// 构建最大堆function heapify(i, heapSize) {let largest = i; // 初始化最大值为当前节点const left = 2 * i + 1; // 左子节点const right = 2 * i + 2; // 右子节点// 如果左子节点存在且大于当前最大值if (left < heapSize && arr[left] > arr[largest]) {largest = left;}// 如果右子节点存在且大于当前最大值if (right < heapSize && arr[right] > arr[largest]) {largest = right;}// 如果最大值不是当前节点,交换并继续堆化if (largest !== i) {[arr[i], arr[largest]] = [arr[largest], arr[i]];heapify(largest, heapSize);}}// 构建最大堆(从最后一个非叶子节点开始)for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {heapify(i, n);}// 逐个取出堆顶元素for (let i = n - 1; i > 0; i--) {[arr[0], arr[i]] = [arr[i], arr[0]]; // 将堆顶元素(最大值)与当前末尾交换heapify(0, i); // 对剩余元素重新堆化}return arr;
}

特点

  • 时间复杂度:始终O(n log n)
  • 空间复杂度:O(1)
  • 不稳定排序

适用场景:需要高效排序且内存有限的情况。

7. 快速排序 - 最常用的排序算法

快速排序也是一种分治法的排序算法。它选择一个"基准"元素,将数组分为两部分:小于基准的和大于基准的,然后递归地对这两部分进行排序。

function quickSort(arr) {if (arr.length <= 1) return arr; // 基线条件const pivot = arr[0]; // 选择第一个元素作为基准(简单实现)const left = []; // 小于基准的元素const 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)]; // 递归排序并合并
}

更高效的实现(原地分区):

function quickSort(arr, left = 0, right = arr.length - 1) {if (left < right) {const pivotIndex = partition(arr, left, right); // 获取分区点quickSort(arr, left, pivotIndex - 1); // 递归排序左半部分quickSort(arr, pivotIndex + 1, right); // 递归排序右半部分}return arr;
}function partition(arr, left, right) {const pivot = arr[right]; // 选择最右边的元素作为基准let i = left; // i是小于基准的元素的边界for (let j = left; j < right; j++) {if (arr[j] < pivot) {[arr[i], arr[j]] = [arr[j], arr[i]]; // 交换元素i++; // 移动边界}}[arr[i], arr[right]] = [arr[right], arr[i]]; // 将基准放到正确位置return i; // 返回基准的索引
}

特点

  • 时间复杂度:平均O(n log n),最坏O(n²)(当数组已经有序时)
  • 空间复杂度:O(log n)(递归调用栈)
  • 不稳定排序

适用场景:大规模数据排序,追求性能的情况。

8. 计数排序 - 非比较排序算法

计数排序是一种非比较排序算法,适用于整数排序。它的基本思想是:统计每个元素出现的次数,然后根据统计结果重建有序数组

function countingSort(arr, maxVal) {const count = new Array(maxVal + 1).fill(0); // 统计每个元素出现的次数for (let num of arr) {count[num]++;}const result = [];for (let i = 0; i <= maxVal; i++) {while (count[i]-- > 0) {result.push(i); // 根据统计结果重建数组}}return result;
}

特点

  • 时间复杂度:O(n + k)(k是数据的范围)
  • 空间复杂度:O(k)
  • 稳定排序(如果实现得当)

适用场景:数据是整数且范围不大的情况。

9. 桶排序 - 分配到多个桶中排序

桶排序将元素分散到多个"桶"中,每个桶内部进行排序,最后合并所有桶的结果。

function bucketSort(arr, bucketSize = 5) {if (arr.length <= 1) return arr;const min = Math.min(...arr);const max = Math.max(...arr);const bucketCount = Math.floor((max - min) / bucketSize) + 1; // 计算桶的数量const buckets = Array.from({ length: bucketCount }, () => []); // 创建桶// 将元素分配到各个桶中for (let num of arr) {const index = Math.floor((num - min) / bucketSize);buckets[index].push(num);}// 对每个桶进行排序并合并结果return buckets.flatMap(bucket => insertionSort(bucket)); // 这里使用了插入排序
}

特点

  • 时间复杂度:O(n + k),最坏O(n²)(当所有元素都在一个桶中)
  • 空间复杂度:O(n + k)
  • 稳定排序(取决于桶内使用的排序算法)

适用场景:数据分布均匀的情况,特别是浮点数排序。

10. 基数排序 - 按位比较的排序

基数排序是一种非比较排序算法,它按照数字的位数从低位到高位依次排序

function radixSort(arr) {const max = Math.max(...arr);let exp = 1; // 从个位开始while (Math.floor(max / exp) > 0) {countingByDigit(arr, exp); // 按当前位排序exp *= 10; // 移动到下一位}return arr;
}function countingByDigit(arr, exp) {const output = new Array(arr.length).fill(0);const count = new Array(10).fill(0); // 0-9的数字// 统计当前位数字出现的次数for (let num of arr) {count[Math.floor(num / exp) % 10]++;}// 计算累计次数for (let i = 1; i < 10; i++) {count[i] += count[i - 1];}// 从后向前遍历,保证稳定性for (let i = arr.length - 1; i >= 0; i--) {const digit = Math.floor(arr[i] / exp) % 10;output[--count[digit]] = arr[i];}// 将排序结果复制回原数组for (let i = 0; i < arr.length; i++) {arr[i] = output[i];}
}

特点

  • 时间复杂度:O(nk)(k是最大数字的位数)
  • 空间复杂度:O(n + k)
  • 稳定排序

适用场景:非负整数排序,特别是位数不多的情况。

排序算法性能对比

排序算法最好时间复杂度最坏时间复杂度平均时间复杂度空间复杂度稳定性
冒泡排序O(n)O(n²)O(n²)O(1)
选择排序O(n²)O(n²)O(n²)O(1)
插入排序O(n)O(n²)O(n²)O(1)
希尔排序O(n log n)O(n²)O(n^1.3)O(1)
归并排序O(n log n)O(n log n)O(n log n)O(n)
堆排序O(n log n)O(n log n)O(n log n)O(1)
快速排序O(n log n)O(n²)O(n log n)O(log n)
计数排序O(n + k)O(n + k)O(n + k)O(k)
桶排序O(n + k)O(n²)O(n + k)O(n + k)
基数排序O(nk)O(nk)O(nk)O(n + k)

总结

  1. 简单排序:冒泡、选择、插入排序适合小规模数据或学习理解
  2. 高效排序:归并、堆、快速排序适合大规模数据
  3. 特殊排序:计数、桶、基数排序适合特定场景(整数、浮点数等)

在实际开发中,JavaScript的Array.prototype.sort()方法已经足够高效,它通常使用快速排序或归并排序的变体。但理解这些底层算法原理,能帮助我们更好地解决问题和优化代码。

希望这篇通俗易懂的排序算法指南对你有所帮助!


推荐更多阅读内容
掌握这些JavaScript技巧,让你的编码效率飙升!
JavaScript 字符串字符删除方法大揭秘
正向代理与反向代理:傻傻分不清楚
JavaScript分钟转时间格式(小时+分钟)的方法及应用
彻底理解 Object.entries() + map():如何把对象转换成指定格式数组?
深入理解 window.open:用法、参数、兼容性与安全实践
彻底清除和禁用浏览器输入框历史记录的终极指南
JavaScript 开发中的高效技巧与基础知识解析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/86395.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/86395.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【微信小程序】6、SpringBoot整合WxJava获取用户手机号

1、手机号快速验证组件 手机号快速验证组件 旨在帮助开发者向用户发起手机号申请&#xff0c;并且必须经过用户同意后&#xff0c;开发者才可获得由平台验证后的手机号&#xff0c;进而为用户提供相应服务。 该能力与手机号实时验证组件的区别为&#xff1a; 手机号快速验证…

redis8.0新特性:原生JSON支持详解

文章目录 一、写在前面二、使用1、基本命令&#xff08;1&#xff09;JSON.SET 设置 JSON 值&#xff08;2&#xff09;JSON.GET 获取 JSON 值&#xff08;3&#xff09;JSON.DEL 删除 JSON 值&#xff08;4&#xff09;JSON.MGET 批量获取&#xff08;5&#xff09;JSON.MSET …

QT网络调试助手开发全指南,软件设计图预研,后续文档跟进补充

网络调试助手 1 TCP网络调试助手 1.1 项目概述 网络相关的一些基础概念学习QTcpServer 学习QTcpClient 学习TextEdit特定位置输入文字颜色学习网络通信相关知识点 复习巩固之前UI控件 程序运行如下图所示 1.2 开发流程 1.3 QTtcp 服务器的关键流程 工程建立&#xff0c;需要在…

网络分层模型与协议体系技术研究报告

网络分层模型是计算机网络体系结构的核心框架&#xff0c;它通过将复杂的网络通信过程分解为多个层次&#xff0c;使网络设计、实现和维护变得更加模块化和标准化。 一、分层模型概念 1、OSI七层模型的详细解析 开放系统互连参考模型&#xff08;OSI/RM&#xff09;是国际标…

C++面向对象7——C继承与C++继承对比、C++继承详解

继承 C语言与C继承机制的对比与实现 一、C语言模拟继承的实现方法 C语言不支持面向对象编程的原生继承机制&#xff0c;但可以通过结构体嵌套和函数指针组合来模拟。 1. 结构体嵌套实现"is-a"关系 // 基类&#xff1a;Shape typedef struct {int x;int y; } Sha…

运维打铁: Windows 服务器基础运维要点解析

文章目录 思维导图一级节点&#xff1a;Windows 服务器基础运维要点 详细内容解析系统安装与配置硬件准备安装介质选择系统安装过程初始配置 日常监控与维护性能监控服务状态检查日志管理 安全管理账户与权限管理防火墙配置病毒防护 备份与恢复备份策略制定备份工具使用恢复测试…

Python实例题:基于量子计算的优化算法实现(量子计算、优化理论)

目录 Python实例题 题目 问题描述 解题思路 关键代码框架 难点分析 扩展方向 Python实例题 题目 基于量子计算的优化算法实现&#xff08;量子计算、优化理论&#xff09; 问题描述 开发一个基于量子计算的优化算法实现&#xff0c;包含以下功能&#xff1a; 量子计…

基本算法--蓝桥杯备考

1.前缀和 1.定义 假设有一个数组a[n],要计算它的前j个元素的和为 a[0]a[1]...a[j-1] 时间复杂度为O(j)&#xff0c;且随着j的变大时间复杂度越来越大。 使用了前缀和算法则为 sum[j]-sum[j-1] 时间复杂度是O(1)&#xff0c;且数据越大优势越明显。 2.例题一 详解见《可…

pgsql 中各个字符串的区别

PostgreSQL 提供了多种字符串类型&#xff0c;它们在存储方式、长度限制和适用场景上有所不同。以下是主要字符串类型的详细对比和区别&#xff1a; 一、核心字符串类型对比 CHAR(n)/CHARACTER(n) 特点&#xff1a;固定长度字符串&#xff0c;不足部分用空格填充最大长度&…

ubuntu中lightdm干嘛的?

在 Ubuntu 或其他 Linux 发行版中&#xff0c;LightDM 是一个轻量级的 显示管理器&#xff08;Display Manager&#xff09;&#xff0c;负责图形化登录界面、用户认证和会话启动。以下是它的核心作用、特点及类似替代品的对比&#xff1a; 1. LightDM 的核心作用 功能说明图形…

GraphQL注入 -- GPN CTF 2025 Real Christmas

part 1 服务器会每段时间禁用已注册的账号,此处存在漏洞 def deactivate_user_graphql(email):graphql_endpoint current_app.config["GRAPHQL_ENDPOINT"]query f"""mutation {{deactivateUser (user: {{email: "{email}"}}){{ success…

【机器学习深度学习】非线性激活函数

目录 前言 一、什么是激活函数&#xff1f; 1.1 作用 二、如果没有激活函数&#xff0c;会发生什么&#xff1f; 2.1 先看一张图理解“线性”的局限 2.2 核心认知&#xff1a;为什么非线性如此重要&#xff1f; 三、非线性激活函数到底解决了什么问题&#xff1f; 1. 引…

国外开源客服系统chathoot部署,使用教程

目录 一、系统版本要求&#xff1a; 二、部署步骤 2.1 安装docker 和docker-compose 2.2 准备docker-compose.yaml 2.3 初始化数据库 2.4 安装nginx 2.6 启动项目 三、使用教程 一、系统版本要求&#xff1a; linux ubuntu 22.042核4G 40GB&#xff08;或以上&#xf…

什么是回归测试?什么时候需要做回归测试?

回归测试详解&#xff1a;概念、时机与最佳实践 1. 什么是回归测试&#xff1f; 回归测试&#xff08;Regression Testing&#xff09; 是指在对软件进行修改&#xff08;如修复Bug、新增功能、优化代码&#xff09;后&#xff0c;重新执行已有测试用例&#xff0c;以确保&am…

Android-Layout Inspector使用手册

Layout Inspector Android Layout Inspector 是 Android Studio 中用于调试应用布局的工具 启动方法&#xff1a; 通过下载Layout Inspector插件&#xff0c;在 “View - Tool Windows - Layout Inspector” 或 “Tools - Layout Inspector” 启动。 主要界面区域&#xff1a…

postgreSQL 数据库字典导出工具

为满足项目验收文档需求&#xff0c;开发了一个基于Python的PostgreSQL数据字典导出工具。 废话不多说&#xff0c;先分享一下 软件截图 数据字典文件样式,文件格式为docx 软件源码 基于python开发&#xff0c; import tkinter as tk from tkinter import ttk, messagebox …

【AI解析】 CppNumericalSolvers:一个现代化的 C++17 纯头文件优化库 示例代码解析

一个轻量级仅头文件的 C17 库&#xff0c;提供针对&#xff08;无&#xff09;约束非线性函数及表达式模板的数值优化方法 https://github.com/PatWie/CppNumericalSolvers CppNumericalSolvers 库 include 目录下的文件及其功能说明 根目录文件 文件名功能说明function.h(主函…

第3篇:Gin的请求处理——获取客户端数据(Gin文件上传,接收JSON数据)

引言&#xff1a;Context是Gin的"瑞士军刀" 在Gin框架中&#xff0c;Context就像一把多功能的瑞士军刀&#xff0c;封装了所有与请求相关的操作。新手开发者常犯的错误是只把它当作参数传递的工具&#xff0c;却忽略了它强大的数据处理能力。 想象一个场景&#xf…

启动hardhat 项目,下载依赖的npm问题

Windows 环境 Hardhat 依赖安装问题排查指南 &#x1f6a8; 问题描述 在 Windows 环境下安装 Hardhat 项目依赖时&#xff0c;遇到以下错误&#xff1a; npm ERR! code ETARGET npm ERR! notarget No matching version found for nomicfoundation/edr^0.11.1. npm ERR! nota…

大数据里的拉链表:数据版本管理的时间胶囊

哈喽各位数据打工人&#xff5e;今天咱们来聊聊大数据领域一个超实用的神器 ——拉链表&#xff01;听起来像时尚单品&#xff1f;NoNoNo&#xff0c;它可是数据仓库里管理历史数据的宝藏工具✨ 就算你是刚入门的小白也能轻松听懂&#xff0c;咱们全程少玩比喻多讲人话&#xf…