实现JavaScript中的数组排序功能

一、引言

在JavaScript中,数组是一种常用的数据结构,而排序是处理数组的常见任务。对于JavaScript中的数组排序,我们可以通过多种方式来实现。本篇博客将详细介绍如何使用JavaScript实现数组排序功能,并分享一些感悟。

二、实现过程

  1. 冒泡排序

冒泡排序是一种简单的排序算法,它重复地遍历待排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。

以下是一个简单的冒泡排序实现:

function bubbleSort(arr) {  let len = arr.length;  for (let i = 0; i < len - 1; i++) {  for (let j = 0; j < len - 1 - i; j++) {  if (arr[j] > arr[j + 1]) {        // 如果前一个元素大于后一个元素,交换它们  let temp = arr[j];  arr[j] = arr[j + 1];  arr[j + 1] = temp;  }  }  }  return arr;  
}
  1. 快速排序

快速排序是一种分而治之的排序算法。它首先选择一个“基准”元素,然后将所有小于基准的元素移动到其左侧,将所有大于基准的元素移动到其右侧。然后对左侧和右侧的元素再次进行快速排序。

以下是一个简单的快速排序实现:

function quickSort(arr) {  if (arr.length < 2) { return arr; }  // 基本结束条件:如果数组只有一个元素,则已经排好序了  let pivot = arr[0];  // 选择第一个元素作为基准元素  let less = [];  // 存放所有小于基准的元素  let greater = [];  // 存放所有大于基准的元素  for (let i = 1; i < arr.length; i++) {  // 将其他元素与基准进行比较,并放入相应的数组中  if (arr[i] < pivot) {  less.push(arr[i]);  } else {  greater.push(arr[i]);  }  }  return quickSort(less).concat(pivot, quickSort(greater));  // 对左侧和右侧的元素递归进行快速排序,并将结果连接起来形成最终的排序数组  
}

 

插入排序:

  插入排序的基本思想是将数组分为已排序和未排序两部分,初始时已排序部分包含一个元素,之后从未排序部分取出元素,并在已排序部分找到合适的插入位置插入,并保持已排序部分一直有序,重复此过程,直到未排序部分元素为空。

function insertionSort(arr) {  for (let i = 1; i < arr.length; i++) {  let key = arr[i];  let j = i - 1;  while (j >= 0 && arr[j] > key) {  arr[j + 1] = arr[j];  j = j - 1;  }  arr[j + 1] = key;  }  return arr;  
}
  1. 选择排序

  2. 选择排序的基本思想是每一次从未排序部分选择最小的元素,将其放到已排序部分的末尾,重复此过程,直到未排序部分元素为空。

function selectionSort(arr) {  for (let i = 0; i < arr.length - 1; i++) {  let minIndex = i;  for (let j = i + 1; j < arr.length; j++) {  if (arr[j] < arr[minIndex]) {  minIndex = j;  }  }  let temp = arr[minIndex];  arr[minIndex] = arr[i];  arr[i] = temp;  }  return arr;  
}

 

  1. 归并排序:

  2. 归并排序是一种分治策略的排序算法,将数组分成两部分,分别对两部分进行排序,然后将有序的部分合并起来。这个过程递归进行,直到得到最终的有序数组。

function mergeSort(arr) {  if (arr.length < 2) { return arr; }  // 基本结束条件:如果数组只有一个元素,则已经排好序了  let mid = Math.floor(arr.length / 2);  // 找到中间点  let left = arr.slice(0, mid);  // 分割数组为两部分  let right = arr.slice(mid);  // 分割数组为两部分  return merge(mergeSort(left), mergeSort(right));  // 递归对左右两部分进行归并排序,并将结果合并起来形成最终的排序数组  
}  
function merge(left, right) {  // 将两个已排序的数组合并成一个已排序的数组  let result = [];  // 存放合并后的已排序数组的数组  while (left.length && right.length) {  // 当左右两个数组都还有元素时,将较小的元素放入result数组中,并从相应的数组中删除该元素  if (left[0] < right[0]) {  result.push(left.shift());  // 将左侧元素放入result数组中,并从left数组中删除该元素  } else {  result.push(right.shift());  // 将右侧元素放入result数组中,并从right数组中删除该元素  }  }  // 当其中一个数组的元素已经全部取出并放入result数组中时,将另一个数组剩下的元素直接全部放入result数组中(因为这些元素已经是已排序的了)  while (left.length) { result.push(left.shift()); }  // 如果左数组还有元素,将其全部放入result数组中  while (right.length) { result.push(right.shift()); }  // 如果右数组还有元素,将其全部放入result数组中  return result;  // 返回合并后的已排序数组  
}

 堆排序(Heap Sort):

  1. 堆排序是一种利用堆这种数据结构所设计的一种排序算法。它通过将一个无序数组构建成一个大顶堆或小顶堆,然后将堆顶元素(最大值或最小值)与堆尾元素互换,之后将剩余元素重新调整为大顶堆或小顶堆,以此类推,直到整个数组有序。

    当然,下面我将通过一个简单的例子来演示堆排序(Heap Sort)的过程。

    假设我们有一个无序数组:[4, 10, 3, 5, 1]。我们要将这个数组按照从小到大的顺序排列。在这个例子中,我们将使用小顶堆来进行排序。

    步骤 1:构建小顶堆

    首先,我们需要将数组转换成一个小顶堆。对于给定的数组,我们可以从最后一个非叶子节点开始,向上调整堆。

    数组的初始状态(索引从0开始):

    [4, 10, 3, 5, 1]  0   1   2   3   4

    从索引 (n-2)/2 开始,这里是 (5-2)/2 = 1,即元素 10。但由于 10 已经是叶子节点,我们不需要调整它。接着我们考虑它的前一个元素,即索引为 0 的元素 4

    调整后的堆(通过比较和交换):

    [3, 10, 4, 5, 1]  // 与子节点10比较,需要交换  0   1   2   3   4
    [1, 10, 4, 5, 3]  // 与子节点10和4比较,需要交换两次(先与4交换,再与10交换)  0   1   2   3   4

    现在我们有了一个小顶堆。

    步骤 2:交换堆顶元素与末尾元素

    接下来,我们将堆顶元素(最小值)与末尾元素交换位置:

    [1, 10, 4, 5, 3]  // 交换前  
    [3, 10, 4, 5, 1]  // 交换后,将最小值1放到了末尾位置  0   1   2   3   4
     

    交换后,我们移除了最小值 1,并且将其放到了正确的位置上。现在剩下的数组部分 [3, 10, 4, 5] 不再是一个小顶堆,我们需要重新调整它。

    步骤 3:重新调整堆

    重新调整剩余部分 [3, 10, 4, 5] 为小顶堆:

    [3, 5, 4, 10]  // 调整后的堆,通过与子节点比较和交换得到  0   1   2   3

    注意,我们现在不考虑已经排序好的最后一个元素 1

    重复步骤 2 和 3

    继续这个过程,直到整个数组有序:

    [3, 5, 4, 10]  // 当前堆状态  
    [4, 5, 3, 10]  // 交换堆顶和末尾元素  
    [4, 3, 5, 10]  // 重新调整剩余部分为小顶堆  
    [3, 4, 5, 10]  // 再次交换堆顶和末尾元素,现在数组已经有序了
     

    最终我们得到了一个有序的数组 [1, 3, 4, 5, 10]。需要注意的是,在实际执行过程中,我们不需要每次都重新构建整个堆,而只需要调整受影响的子堆部分。这里为了简单说明,我展示了整个数组的状态。在实际实现中,我们可以通过维护一个堆的数据结构来更有效地进行这些操作。

 希尔排序(Shell Sort):

希尔排序是一种基于插入排序的算法,通过比较相距一定间隔的元素,使得在较远元素比较时能够克服插入排序的局限性。希尔排序的时间复杂度介于O(n^2)和O(n log n)之间,具体取决于间隔序列的选择。

下面是一个希尔排序的例子:

假设我们有一个无序数组:[69, 56, 12, 136, 3, 55, 46, 99, 88, 25]

步骤 1:选择间隔序列

首先,我们需要选择一个间隔序列。常见的间隔序列有 [n/2, n/4, n/8, ..., 1] 或 [3, 7, 15, ..., n/2] 等。在这里,我们选择 [n/2, n/4, n/8, ..., 1] 作为间隔序列。

步骤 2:进行插入排序

接下来,我们按照间隔序列的顺序,对数组进行插入排序。

首先,取间隔 d=10/2=5,将数组分成两组进行直接插入排序:

[69, 56, 12, 136, 3, 55, 46, 99, 88, 25]  // 第一组  
[25, 3, 12, 55, 56, 88, 99, 12, 136, 69]  // 第二组

对每组分别进行插入排序:

[3, 12, 25, 55, 56, 69]  // 第一组插入排序结果  
[12, 25, 3, 55, 56, 88]  // 第二组插入排序结果

接下来,取 d=d/2=2,将数组分成两组进行直接插入排序:

[3, 12, 25, 55, 56, 69]  // 第一组  
[12, 25, 3, 55, 56, 88]  // 第二组

对每组分别进行插入排序:

[3, 12, 25]  // 第一组插入排序结果  
[3, 12]  // 第二组插入排序结果

最后,取 d=d/2=1,将整个数组作为一个序列进行直接插入排序:

[3, 12, 25]  // 进行一次插入排序后得到最终结果 [25, 12, 3]

最终我们得到了一个有序的数组 [25, 12, 3]。可以看到,通过希尔排序,我们能够更快地将数组排序。

 计数排序(Counting Sort):

计数排序是一种非比较型整数排序算法,适用于正整数数组的排序。它通过统计每个元素出现的次数,将其按照出现次数放入新的数组中。计数排序在处理大数据集时具有较好的性能表现,但不适用于非整数或小数元素的排序。

以下是一个简单的计数排序的例子:

假设我们有以下整数数组:

arr = [4, 2, 7, 1, 5, 2]

首先,我们需要找到数组中的最大值和最小值,以确定计数数组的范围。在这个例子中,最小值为1,最大值为7。

然后,我们创建一个计数数组count,其长度为(最大值 - 最小值 + 1)。在这个例子中,计数数组长度为(7 - 1 + 1) = 7。

count = [0, 0, 0, 0, 0, 0, 0]


接下来,我们遍历原始数组,统计每个元素出现的次数,并在计数数组中相应位置累加。

计数数组count更新后为:

[1, 2, 0, 1, 1, 0, 1]


现在,我们可以根据计数数组还原排序后的数组。我们从计数数组中取出元素,其位置表示原始数组中的值,元素的值表示原始数组中该值出现的次数。然后,我们将这些值按照计数数组的顺序放回原始数组。

最终得到的排序后的数组为:

[1, 2, 2, 4, 5, 7]

这就是计数排序的基本过程。

 基数排序(Radix Sort):

基数排序是一种非比较型整数排序算法,适用于正整数或负整数的排序。它通过将整数按位数切割成不同的数字,然后按每个位数分别比较。基数排序在处理具有相同值元素的数组时具有较好的性能表现。

下面是一个基数排序的例子:

假设我们有一个整数数组:

arr = [170, 45, 75, 90, 802, 24, 2, 66]

首先,我们需要找到数组中最大数的位数,以便确定每个桶的个数。在这个例子中,最大数的位数是3位。

然后,我们创建一个长度为10的桶数组,每个桶用于存储一个位数的数字。桶的索引表示该位数的值,桶中的元素表示原始数组中该位数的数字。

接下来,我们按照从最低位到最高位的顺序遍历整数的每一位数,将每个数字放入相应的桶中。在这个例子中,我们先从个位开始。

个位上的数字有:[0, 2, 4, 6, 8, 9]
十位上的数字有:[1, 2, 4, 5, 7, 9]
百位上的数字有:[1, 2, 6, 8]

最后,我们将每个桶中的元素按照顺序合并起来,得到最终的排序结果:

[2, 24, 45, 66, 75, 90, 170, 802]

这就是基数排序的基本过程。注意,在实际应用中,我们需要根据具体情况选择合适的基数排序实现方式,例如选择从最高位开始排序还是从最低位开始排序,以及如何处理进位等情况。

三、总结和感悟

在实现数组排序功能的过程中,我们分别介绍了冒泡排序和快速排序两种算法。冒泡排序虽然简单易懂,但效率较低,适用于较小的数组排序。而快速排序虽然实现起来稍显复杂,但其平均时间复杂度为O(nlogn),具有较好的性能表现,适用于较大的数组排序。在实际应用中,我们可以根据具体需求选择合适的排序算法。

通过这次实践,我深刻体会到算法在编程中的重要性。掌握多种算法不仅可以提高我们的编程能力,还可以让我们在处理问题时更加得心应手。同时,我也认识到算法的学习是一个不断深入的过程,需要我们不断地实践和总结。在未来的学习和工作中,我将继续努力深入学习算法,提高自己的编程能力。

 

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

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

相关文章

SQL的一些基本语句

SQL&#xff08;Structured Query Language&#xff09;是一种用于管理关系型数据库的语言。下面是一些常用的SQL基本语句&#xff1a; 创建表格&#xff1a; CREATE TABLE table_name (column1 datatype,column2 datatype,column3 datatype,... );插入数据&#xff1a; INSERT…

详解C语言入门程序:HelloWorld.c

#include <stdio.h> // 头文件&#xff0c;使用<>编译系统会在系统头文件目录搜索在C语言中&#xff0c;#include 是预处理指令&#xff0c;用于将指定的头文件内容插入到当前源文件中。这里的 <stdio.h> 是一个标准库头文件&#xff0c;其中包含了与输入输出…

MySQL之CRUD、常见函数及union查询

目录 一. CRUD 1.1 什么是crud 1.2 SELECT(查询) 1.3 INSERT(新增) 1.4 UPDATE(修改) 1.5 DELETE(删除) 二. 函数 2.1 常见函数 2.2 流程控制函数 2.3 聚合函数 三. union与union all 3.1 union 3.2 union all 3.3 具体不同 3.4 结论 四. 思维导图 一. CRUD 1.1 什么是crud…

解析:Eureka的工作原理

Eureka是Netflix开源的一个基于REST的的服务发现注册框架&#xff0c;它遵循了REST协议&#xff0c;提供了一套简单的API来完成服务的注册和发现。Eureka能够帮助分布式系统中的服务提供者自动将自身注册到注册中心&#xff0c;同时也能够让服务消费者从注册中心发现服务提供者…

【愚公系列】2023年12月 HarmonyOS应用开发者高级认证(完美答案)

&#x1f3c6; 作者简介&#xff0c;愚公搬代码 &#x1f3c6;《头衔》&#xff1a;华为云特约编辑&#xff0c;华为云云享专家&#xff0c;华为开发者专家&#xff0c;华为产品云测专家&#xff0c;CSDN博客专家&#xff0c;CSDN商业化专家&#xff0c;阿里云专家博主&#xf…

express框架搭建后台服务

express 1. 使用express创建web服务器&#xff1a;2. 中间件中间件分类&#xff1a; 3.解决跨域问题&#xff1a;1. CORS2.JSONP 1. 使用express创建web服务器&#xff1a; 1. 导入express2. 创建web服务器3. 启动web服务器// 1. 导入express const express require(express)/…

6. Mybatis 缓存

6. Mybatis 缓存 MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率MyBatis系统中默认定义了两级缓存 一级缓存二级缓存 默认情况下&#xff0c;只有一级缓存&#xff08;SqlSession级别的缓存&#xff0c;也称为本地缓存&…

Transforer逐模块讲解

本文将按照transformer的结构图依次对各个模块进行讲解&#xff1a; 可以看一下模型的大致结构&#xff1a;主要有encode和decode两大部分组成&#xff0c;数据经过词embedding以及位置embedding得到encode的时输入数据 输入部分 embedding就是从原始数据中提取出单词或位置&…

ubuntu22.04配置双网卡绑定提升带宽

这里写自定义目录标题 Bonding简介配置验证参考链接 Bonding简介 bonding(绑定)是一种linux系统下的网卡绑定技术&#xff0c;可以把服务器上n个物理网卡在系统内部抽象(绑定)成一个逻辑上的网卡&#xff0c;能够提升网络吞吐量、实现网络冗余、负载均衡等功能&#xff0c;有很…

软件设计师考试的知识点

这里先总结一下考试的知识点。 上午的考试考题中只有单选题&#xff0c;涉及范围很广&#xff0c;但是考查不深。 上午的考试知识点以及分数比重&#xff1a; 知识点 分数 说明 比例 软件工程基础知识 13 开发方法与开发模型、数据流图与数据字典、结构化设计、测试方法…

2023年工作初体验

23年终于正式入职&#xff0c;参与了正式上线的电商平台、crm平台等项目的研发&#xff0c;公司规模较小&#xff0c;气氛融洽&#xff0c;没有任何勾心斗角、末位淘汰&#xff0c;几乎没什么压力。虽然是我的第一家公司&#xff0c;但实际是个适合养老的公司&#xff08;笑 总…

双击shutdown.bat关闭Tomcat报错:未设置关闭端口~

你们好&#xff0c;我是金金金。 场景 当我startup.bat启动tomcat之后&#xff0c;然后双击shutdown.bat关闭&#xff0c;结果报错了~ 排查 看报错信息很明显了&#xff0c;未配置关闭端口&#xff0c;突然想起来了我在安装的时候都选的是默认的配置&#xff0c;我还记得有这…

快速批量运行命令

Ansible 是 redhat 提供的自动化运维工具&#xff0c;它是 Python编写&#xff0c;可以通过 pip 安装。 pip install ansible 它通过任务(task)、角色(role)、剧本(playbook) 组织工作项目&#xff0c;适用于批量化系统配置、软件部署等需要复杂操作的工作。 但对于批量运行命…

简单罗列一下jdk常见的垃圾收集器

1. Serial Collector 类型&#xff1a;单线程收集器。工作模式&#xff1a;使用标记-压缩算法进行老年代的垃圾收集&#xff0c;标记-复制算法进行年轻代的垃圾收集。特点&#xff1a;简单高效&#xff0c;适用于单核处理器或小型堆内存。在进行垃圾收集时&#xff0c;会暂停所…

nginx日志目录详解

Nginx 默认会打印访问日志&#xff08;access log&#xff09;和错误日志&#xff08;error log&#xff09;。这些日志对于监控和调试网站非常有用。以下是关于如何配置和查看 Nginx 日志的一些基本信息&#xff1a; 配置 Nginx 日志 访问日志&#xff08;Access Log&#xf…

宝塔部署nuxt3项目问题解决

使用宝塔部署nuxt3项目一直没成功&#xff0c;网站502&#xff0c;要不就是资源加载不出来 测试使用宝塔版本8.0.4 添加node项目方式失败&#xff0c;项目更目录设置到server,无法设置运行目录为public, 导致网站资源加载不出来&#xff0c;设置到.output目录&#xff0c;会提…

继电器组开发控制

也是通过树莓派IO口的控制来实现继电器组的开发 继电器组有四根信号线&#xff0c;2根电源线。 通过gpio readall 查看树莓派各个端口的信息选择26 27 28 29 作为信号端口 编程可能会遇到的一些问题 1、通过键盘输入指令的时候&#xff0c;如果用scanf 会有bug&#xff0c;导…

JavaScript高级程序设计读书记录(一):语言基础,语法,变量,数据类型

1. 语法 很大程度上借鉴了 C 语言和其他类 C 语言&#xff0c;如 Java 和 Perl. 1.1 区分大小写 1.2 标识符 第一个字符必须是一个字母、下划线&#xff08;_&#xff09;或美元符号&#xff08;$&#xff09;&#xff1b; 剩下的其他字符可以是字母、下划线、美元符号或数…

排除启动类故障----三大实验

目录 一、模拟破坏mbr和分区表然后修复 二、修复grub引导故障 三、遗忘root用户密码 一、模拟破坏mbr和分区表然后修复 1、mbr处于第一块磁盘的第一个物理扇区&#xff0c;总共512个字节&#xff0c;前446个字节是grub程序&#xff0c;后面64个字节是分区表 2、故障原因&a…

Linux 进程(九) 进程等待

子进程退出&#xff0c;父进程如果不管不顾&#xff0c;就可能造成‘僵尸进程’的问题&#xff0c;进而造成内存泄漏&#xff0c;所以父进程回收子进程是必然要做的。 另外&#xff0c;进程一旦变成僵尸状态&#xff0c;那就刀枪不入&#xff0c;“杀人不眨眼”的kill …