JavaScript实现十大排序算法(图文详解)

article/2025/11/9 13:26:28

冒泡排序

排序的效果图

解法

当前解法为升序

冒泡排序的特点,是一个个数进行处理。第i个数,需要与后续的len-i-1个数进行逐个比较。

为什么是 `len-i-1`个数?

因为数组末尾的i个数,已经是排好序的,确认位置不变的了。

为什么确认位置不变,因为它们固定下来之前,已经和前面的数字都一一比较过了。

function bubbleSort(arr){const len = arr.length;for(let i = 0; i < len - 1; i++){for(let j = 0; j < len - i - 1; j++){if(arr[j] > arr[j+1]){const tmp = arr[j+1];arr[j+1] = arr[j];arr[j] = tmp;}}}return arr;
}

快速排序

概要

快速排序,使用的是分治法的思想。
通过选定一个数字作为比较值,将要排序其他数字,分为 >比较值 和 <比较值,两个部分。并不断重复这个步骤,直到只剩要排序的数字只有本身,则排序完成。

效果图

解法

function quickSort(arr){sort(arr, 0, arr.length - 1);return arr;function sort(arr, low, high){if(low >= high){return;}let i = low;let j = high;const x = arr[i]; // 取出比较值x,当前位置i空出,等待填入while(i < j){// 从数组尾部,找出比x小的数字while(arr[j] >= x && i < j){j--;}// 将空出的位置,填入当前值, 下标j位置空出// ps:比较值已经缓存在变量x中if(i < j){arr[i] = arr[j]i++;}// 从数组头部,找出比x大的数字while(arr[i] <= x && i < j){i++;}// 将数字填入下标j中,下标i位置突出if(i < j){arr[j] = arr[i]j--;}// 一直循环到左右指针i、j相遇,// 相遇时,i==j, 所以下标i位置是空出的}arr[i] = x; // 将空出的位置,填入缓存的数字x,一轮排序完成// 分别对剩下的两个区间进行递归排序sort(arr, low, i - 1);sort(arr, i+1, high);}
}

希尔排序

概要

希尔排序是一种插入排序的算法,它是对简单的插入排序进行改进后,更高效的版本。由希尔(Donald Shell)于1959年提出。
特点是利用增量,将数组分成一组组子序列,然后对子序列进行插入排序。
由于增量是从大到小,逐次递减,所以也称为缩小增量排序

效果图

解法

注意点
插入排序时,并不是一个分组内的数字一次性用插入排序完成,而是每个分组交叉进行。

执行插入时,使用交换法

function shellSort(arr){// 分组规则 gap/2 递减for(let gap = Math.floor(arr.length/2); gap > 0; gap = Math.floor(gap/2)){for(let i = gap; i < arr.length; i++){let j = i;// 分组内数字,执行插入排序,// 当下标大的数字,小于 下标小的数字,进行交互// 这里注意,分组内的数字,并不是一次性比较完,需要i逐步递增,囊括下个分组内数字while(j - gap >= 0 && arr[j] < arr[j - gap]){swap(j, j-gap);j = j - gap;}}}return arr;function swap(a, b){const tmp = arr[a];arr[a] = arr[b];arr[b] = tmp;}
}

执行插入时,使用移动法

function shellSort(arr){for(let gap = Math.floor(arr.length/2); gap > 0; gap = Math.floor(gap/2)){for(let i = gap; i < arr.length; i++){let j = i;const x = arr[j]; // 缓存数字,空出位置while(j - gap >= 0 && x < arr[j-gap]){arr[j] = arr[j - gap]; // 将符合条件的数字,填入空出的位置j = j - gap;}arr[j] = x; // 最后,将缓存的数字,填入空出的位置}}return arr;
}

选择排序

排序的效果图

解法

当前解法为升序

function selectionSort(arr){const len = arr.length;for(let i = 0; i < len-1; i++){let minIndex = i;for(let j = i+1; j < len; j++){if(arr[j] < arr[minIndex]){minIndex = j; // 保存最小数的下标}}const tmp = arr[i];arr[i] = arr[minIndex];arr[minIndex] = tmp;}return arr;
}

归并排序

概要

归并排序,利用分治思想,将大的数组,分解为小数组,直至单个元素。然后,使用选择排序的方式,对分拆的小数组,进行回溯,并有序合并,直至合并为一个大的数组。

效果图

小数组合并的过程

解法


function mergeSort(arr){return sort(arr, 0, arr.length - 1); // 注意右区间是arr.length - 1// sort方法,进行递归function sort(arr, left, right){// 当left !== right时,证明还没分拆到最小元素if(left < right){// 取中间值,分拆为两个小的数组const mid = Math.floor((left+right) / 2);const leftArr = sort(arr, left, mid);const rightArr = sort(arr, mid+1, right);// 递归合并return merge(leftArr, rightArr)}// left == right, 已经是最小元素,直接返回即可return left >= 0 ? [arr[left]] : [];}// 合并两个有序数组function merge(leftArr, rightArr){let left = 0;let right = 0;const tmp = [];// 使用双指针,对两个数组进行扫描while(left < leftArr.length && right < rightArr.length){if(leftArr[left] <= rightArr[right]){tmp.push(leftArr[left++]);}else{tmp.push(rightArr[right++]);}}// 合并剩下的内容if(left < leftArr.length){while(left < leftArr.length){tmp.push(leftArr[left++]);}}if(right < rightArr.length){while(right < rightArr.length){tmp.push(rightArr[right++]);}}return tmp;}}

插入排序

排序的效果图

解法

当前解法为升序

function insertionSort(arr){const len = arr.length;// 注意,i 从 1 开始for(let i = 1; i < len; i++){let preIndex = i - 1;let current = arr[i];// 位置i之前,是已排好序的数字,while的作用是找到一个坑位,给当前数字current插入while(preIndex >= 0 && arr[preIndex] > current){arr[preIndex+1] = arr[preIndex]; // 对大于current的值,往后移一位,给current的插入腾出位置preIndex--;}arr[preIndex+1] = current;}return arr;
}

堆排序

概要

堆的表示形式

逻辑结构的表示如下:

物理数据层的表示如下:

堆排序,是选择排序的优化版本,利用数据结构——树,对数据进行管理。

以大顶堆为例:

  1. 通过构建大顶堆
  2. 将堆顶的最大数拿出,与堆底的叶子节点进行交换
  3. 接着,树剪掉最大数的叶子
  4. 再对堆进行调整,重新变成大顶堆
  5. 返回步骤2,以此循环,直至取出所有数

效果图

在实现代码时,构建大顶堆时,先保证左右子树的有序,再逐步扩大到整棵树。

构建大顶堆

从第一个非叶子节点开始,调整它所在的子树

调整下标1节点的子树后,向上继续调整它的父节点(下标0)所在的子树

最后,完成整个树的调整,构建好大顶堆。

逐个抽出堆顶最大值

堆顶数字与最末尾的叶子数字交换,抽出堆顶数字9。

此时,数字9位置固定下来,树剪掉9所在的叶子。然后,重新构建大顶堆。

大顶堆构建好后,继续抽出堆顶数字8,然后再次重新构建大顶堆。

最后,所有节点抽出完成,代表排序已完成。

解法

以大顶堆为例,对数组进行升序排序

注意点
树的最后一个非叶子节点:(arr.length / 2) - 1
非叶子节点i的左叶子节点: i*2+1
非叶子节点i的右叶子节点: i*2+2

function heapSort(arr){// 初次构建大顶堆for(let i = Math.floor(arr.length/2) - 1; i >= 0; i--){// 开始的第一个节点是 树的最后一个非叶子节点// 从构建子树开始,逐步调整buildHeap(arr, i, arr.length);}// 逐个抽出堆顶最大值for(let j = arr.length -1 ; j > 0; j--){swap(arr, 0, j); // 抽出堆顶(下标0)的值,与最后的叶子节点进行交换// 重新构建大顶堆// 由于上一步的堆顶最大值已经交换到数组的末尾,所以,它的位置固定下来// 剩下要比较的数组,长度是j,所以这里的值length == jbuildHeap(arr, 0, j); }return arr;// 构建大顶堆function buildHeap(arr, i, length){let tmp = arr[i]; for(let k = 2*i+1; k < length; k = 2*k+1){// 先判断左右叶子节点,哪个比较大if(k+1 < length && arr[k+1] > arr[k]){k++;}// 将最大的叶子节点,与当前的值进行比较if(arr[k] > tmp){// k节点大于i节点的值,需要交换arr[i] = arr[k]; // 将k节点的值与i节点的值交换i = k; // 注意:交换后,当前值tmp的下标是k,所以需要更新}else{// 如果tmp大于左右子节点,则它们的子树也不用判断,都是小于当前值break;}}// i是交换后的下标,更新为tmparr[i] = tmp;}// 交换值function swap(arr, i, j){const tmp = arr[i];arr[i] = arr[j];arr[j] = tmp;}
}

计数排序

概要

计数排序的要点,是开辟一块连续格子组成的空间,给数据进行存储。
将数组中的数字,依次读取,存入其值对应的下标中。
储存完成后,再按照空间的顺序,依次读取每个格子的数据,输出即可。

所以,计数排序要求排序的数据,必须是有范围的整数

效果图

解法

function countingSort(arr){let maxValue = Number.MIN_VALUE;let minValue = Number.MAX_VALUE;let offset = 0; // 位移,用于处理负数const result = [];// 取出数组的最大值, 最小值arr.forEach(num => {maxValue = num > maxValue ? num : maxValue;minValue = num > minValue ? minValue : num;});if(minValue < 0){offset = -minValue;}const bucket = new Array(maxValue+offset+1).fill(0); // 初始化连续的格子// 将数组中的每个数字,根据值放入对应的下标中,// `bucket[num] == n`格子的意义:存在n个数字,值为numarr.forEach(num => {bucket[num+offset]++;});// 读取格子中的数bucket.forEach((store, index) => {while(store--){result.push(index - offset);}});return result;}

桶排序

概要

桶排序是计数排序的优化版,原理都是一样的:分治法+空间换时间。
将数组进行分组,减少排序的数量,再对子数组进行排序,最后合并即可得到结果。

效果图

解法

对桶内数字的排序,本文采用的是桶排序递归。其实它的本质是退化到计数排序

function bucketSort(arr, bucketSize = 10){// bucketSize 每个桶可以存放的数字区间(0, 9]if(arr.length <= 1){return arr;}let maxValue = arr[0];let minValue = arr[0];let result = [];// 取出数组的最大值, 最小值arr.forEach(num => {maxValue = num > maxValue ? num : maxValue;minValue = num > minValue ? minValue : num;});// 初始化桶的数量const bucketCount = Math.floor((maxValue - minValue)/bucketSize) + 1; // 桶的数量// 初始化桶的容器// 注意这里的js语法,不能直接fill([]),因为生成的二维下标数组,是同一个地址const buckets = new Array(bucketCount).fill(0).map(() => []);// 将数字按照映射的规则,放入桶中arr.forEach(num => {const bucketIndex = Math.floor((num - minValue)/bucketSize);buckets[bucketIndex].push(num);});// 遍历每个桶内存储的数字buckets.forEach(store => {// 桶内只有1个数字或者空桶,或者都是重复数字,则直接合并到结果中if(store.length <= 1 || bucketSize == 1){result = result.concat(store);return;}// 递归,将桶内的数字,再进行一次划分到不同的桶中const subSize = Math.floor(bucketSize/2); // 减少桶内的数字区间,但必须是最少为1const tmp = bucketSort(store, subSize <= 1 ? 1: subSize);result = result.concat(tmp);});return result;
}

基数排序

概述

基数排序,一般是从右到左,对进制位上的数字进行比较,存入[0, 9]的10个桶中,进行排序。
从低位开始比较,逐位进行比较,让每个进制位(个、十、百、千、万)上的数字,都能放入对应的桶中,形成局部有序。

为什么10个桶?

因为十进制数,是由0-9数字组成,对应的进制位上的数字,都会落在这个区间内,所以是10个桶。

基数排序有两种方式:

  • MSD 从高位开始进行排序

  • LSD 从低位开始进行排序

效果图

解法

当前解法,只适用正整数的场景。
负数场景,需要加上偏移量解决。可参考 计数排序 的解法。

function radixSort(arr){let maxNum = arr[0];// 求出最大的数字,用于确定最大进制位arr.forEach(num => {if(num > maxNum){maxNum = num;}});// 获取最大数字有几位let maxDigitNum = 0;while(maxNum > 0){maxNum = Math.floor(maxNum / 10);maxDigitNum++;}// 对每个进制位上的数进行排序for(let i = 0; i < maxDigitNum; i++){let buckets = new Array(10).fill(0).map(() => []); // 初始化10个桶for(let k = 0; k < arr.length; k++){const bucketIndex = getDigitNum(arr[k], i); // 获取当前进制位上的数字buckets[bucketIndex].push(arr[k]); // 排序的数字放入对应桶中}// 所有数字放入桶中后,现从0-9的顺序将桶中的数字取出const res = [];buckets.forEach(store => {store.forEach(num => {res.push(num); // 注意这里,先存入桶中的数字,先取出,这样才能保持局部有序})});arr = res;}return arr;/** 求出数字每个进制位上的数字,只支持正整数@param num 整数@param digit 位数,从0开始*/function getDigitNum(num, digit){return Math.floor(num / Math.pow(10, digit) % 10)}
}

算法复杂度


作者:我是leon


http://chatgpt.dhexx.cn/article/HPjgcKWO.shtml

相关文章

JS 常见的排序算法

工作中算法不常用&#xff0c;但是排序经常用。因此在这里整理了几种JS中常见的排序算法。 冒泡排序 1、算法思想&#xff1a;判断两个相邻元素&#xff0c;大于则交换位置 2、算法步骤 从数组中第一个数开始&#xff0c;依次与下一个数比较并次交换比自己小的数&#xff0c…

详细解析十大排序算法(js实现)

详细解析十大排序算法js实现 算法概述1.冒泡排序1.1算法描述1.2动图演示1.3代码实现 2.选择排序2.1算法描述2.2动图演示2.3代码实现2.4算法分析 3.插入排序3.1算法描述3.2动图演示3.3代码实现3.4算法分析 4.希尔排序4.1算法描述4.2动图演示4.3代码实现4.4算法分析 5.归并排序5.…

Excel中使用SUMIFS公式时计算结果不正确(原因:有超过15位数字的长编码)

在使用SUMIFS公式进行多条件求和时发现求和结果部分正确&#xff0c;部分错误。 常见原因&#xff1a; 1、公式错误 2、数据格式有问题或者有空格等 仔细检查后发现并不是上述两个原因&#xff0c;而是求和的公式中涉及到超过15位数字的长编码。常见的SUMIF、SUMIFS、COUNTIF等…

mybatis-plus使用

1 mybaitsplus简介 &#xff08;1&#xff09;对于mybatis功能的增强&#xff0c;具体的体现就是对于单表的操作都生成好了&#xff0c;分页&#xff0c;条件查询使用很方便&#xff0c;例如查询操作&#xff0c;直接使用wrapper的实现类&#xff0c;调用方法进行条件的增加&a…

四、Hibernate框架的API (三)-- Session对象

一、Session对象 1.Hibernate最重要的对象,只用使用hibernate与数据库操作&#xff0c;都用到这个对象2.该对象维护了一个Connection对象。代表了与数据库连接的会话。3.该对象实质上是对Connection对象的包装&#xff0c;在Connection对象基础之上&#xff0c;封装了一些方法。…

Criteria的用法

一、Hibernate提供了5种检索对象的方式 1.导航对象图检索方式&#xff1a;根据已经加载的对象导航到其他对象    from Emp e group by e.dept.deptName 2.OID检索方式&#xff1a;按照对象的OID来检索对象   get/load 3.HQL检索方式&#xff1a;使用面向对象的HQL查询语…

第13章:处理Excel电子表格(笔记)

13.1&#xff1a;Excel文档 13.2&#xff1a;安装openpyxl模块 pip install --user --Uopenpuxl2.6.2 这是安装2.6.2版本的&#xff0c;比较新的版本与学习的书籍的信息有一点不兼容 13.3&#xff1a;读取Excel文档 13.3.1&#xff1a;用openpyxl模块打开Excel文档 import …

关于 range.autofilter 和 VBA的 filter

1 range().autofilter容易出错的地方 1.1 range().autofilter的返回值 range().autofilter 返回值总是 true返回值并不是对象&#xff0c;而是执行一个筛选的操作所以 Sub test1_filter5() Dim rng1 As Range Set rng1 Range("b1:b20").AutoFilter(field:1, Crit…

Opencv学习之角点检测

Opencv学习之角点检测 角点检测 在图像处理和计算机视觉领域&#xff0c;兴趣点&#xff08;interest points&#xff09;&#xff0c;也被称作关键点&#xff08;key points&#xff09;、特征点&#xff08;feture points&#xff09;。它被大量用于解决物体识别、图像识别…

VBA-Range.AutoFilter 方法浅析

VBA-Range.AutoFilter 方法的一些“坑” 学到筛选的时候遇到一些小小的“坑”&#xff0c;记录下。 录制出来的宏是这样的&#xff0c; Sub 宏1()宏1 宏Rows("1:1").SelectSelection.AutoFilterActiveSheet.Range("$A$1:$F$1048").AutoFilter field:4, …

Hibernate查询Query By Criterial

提供的检索方式&#xff1a;&#xff08;1&#xff09;导航对象图检索方式 &#xff08;2&#xff09;OID检索方式&#xff08;3&#xff09;HQL检索方式&#xff08;4&#xff09;QBC检索方式[query by Criteria(标准)]&#xff08;5&#xff09;本地SQL检索方式 1、简介 1.…

SLUB和SLAB的区别

转载&#xff1a;http://www.cnblogs.com/tolimit/ 首先为什么要说slub分配器&#xff0c;内核里小内存分配一共有三种&#xff0c;SLAB/SLUB/SLOB&#xff0c;slub分配器是slab分配器的进化版&#xff0c;而slob是一种精简的小内存分配算法&#xff0c;主要用于嵌入式系统。慢…

[linux kernel]slub内存管理分析(7) MEMCG的影响与绕过

文章目录 背景前情回顾描述方法约定 MEMCG总览省流总结简介 slub 相关 memcg机制kernel 5.9 版本之前结构体初始化具体实现 kernel 5.9-5.14kernel 5.14 之后 突破slab限制方法cross cache attackpage 堆风水 总结 背景 前情回顾 关于slab几个结构体的关系和初始化和内存分配…

linux slub分配器,Vi Linux内存 之 Slub分配器(六)

再来看内置式对象&#xff0c;如下图所示。指针位于对象的头部&#xff0c;与对象共用存储空间。这是因为对象被分配出去之前&#xff0c;其存储空间是空闲的可用状态&#xff0c;可用于存放空闲对象指针。对象被分配出去后&#xff0c;也不再需要这个指针了&#xff0c;可以被…

一文给你解决linux内存源码分析- SLUB分配器概述(超详细)

SLUB和SLAB的区别 首先为什么要说slub分配器&#xff0c;内核里小内存分配一共有三种&#xff0c;SLAB/SLUB/SLOB&#xff0c;slub分配器是slab分配器的进化版&#xff0c;而slob是一种精简的小内存分配算法&#xff0c;主要用于嵌入式系统。慢慢的slab分配器或许会被slub取代&…

Linux内存管理(2):SLAB/SLUB系统(基于线性映射)

一、概述 伙伴系统最大限度地解决了内存管理地外碎片问题,但是对于内碎片问题却无能为力。但内核实际使用内存的时候,却大多是小于一个页的单位。为了解决内核自身使用小块内存的碎片问题,Linux引入了基于对象的内存管理(或者叫内存区管理,Memory Area Management),就是SL…

slub分配器学习系列之linux5.10

前言 前一篇文章对 linux5.10 的 slab 分配器底层实现进行了探究与学习。进一步地&#xff0c;本篇文章将对 linux5.10 的 slub 分配器进行探究&#xff0c;对比看看两者的实现有何不同&#xff0c;做了哪些"必要的"改进。 slub 分配器 关于 slub 分配器的基本原理…

linux内核虚拟内存之slub分配器

上一章主要讲述以页为最小单位进行内存分配的伙伴管理算法&#xff0c;较大程度上避免了内存碎片问题。而实际上对内存的申请却不是每次都申请一个页面的&#xff08;比如文件节点&#xff0c;任务描述符等结构体内存&#xff09;&#xff0c;通常是远小于一个内存页面的大小&a…

4.19内核SLUB内存分配器

初始化 内核的大部分管理数据结构都是通过kmalloc分配内存的&#xff0c;那么slab本身结构的内存管理就出现了一个鸡与蛋的问题&#xff0c;slab数据结构所需内存远小于一整页的内存块&#xff0c;这些最适合kmalloc分配&#xff0c;而kmalloc只有在slab初始化完之后才能使用。…

SLUB内存管理之slub初始化

在讲slub内存管理涉及的四个函数之前&#xff0c;先从slub内存分配算法的初始化开始。系统启动时&#xff0c;会进行slub内存分配算法的初始化&#xff0c;函数流程是&#xff1a;start_kernel() -> mm_init()->kmem_cache_init()。在start_kernel()函数中的setup_arch()…