likes
comments
collection
share

浅聊js中的基础排序

作者站长头像
站长
· 阅读数 7

前言

当谈到对数据进行排序时,冒泡排序、选择排序和插入排序是最简单和常见的几种算法。它们在实现简单和可读性方面优秀。下面我们将对这三种排序算法进行详细介绍:

冒泡排序

众所周知,冒泡排序是排序算法中最基础的算法。它通过重复遍历待排序数组,每次比较相邻元素,并根据大小顺序交换它们。这个过程像气泡从水底升至水面一样,较大的元素逐渐“浮”到数组末尾。

下面是冒泡排序的一个示例:

let arr = [2, 4, 5, 1, 3]
function bubbleSort(arr) {// 冒泡排序
  const res = []
  while (arr.length) {
    let min = Math.min(...arr)// 找到最小值
    res.push(min)
    let index = arr.indexOf(min)// 找到最小值的索引,并删除,继续循环,直到arr为空
    arr.splice(index,1)
  }
  return res
  }
console.log(bubbleSort(arr));// [1, 2, 3, 4, 5]

以上代码是一种使用循环迭代来实现的简单排序算法。然而,这并不是典型的冒泡排序,它更类似于选择排序的实现。

这段代码的逻辑如下:

  1. 创建了一个空数组 res 用于存放排序后的结果。
  2. 使用 while 循环,判断原始数组 arr 是否还有剩余元素。
  3. 在每一轮循环中,找到 arr 中的最小值,使用 Math.min(...arr) 来找到最小值。
  4. 将最小值加入结果数组 res 中,并通过 indexOf 找到最小值在 arr 中的索引。
  5. 使用 splice 方法将最小值从原数组 arr 中移除。
  6. 循环继续,直到原数组 arr 中的所有元素都被移至 res 数组,最终返回排序好的结果数组。

虽然这段代码可以实现排序功能,但其时间复杂度较高。因为在每次循环中都会进行查找和删除操作,这些操作会导致算法的效率较低,尤其在处理大量数据时。对于较小的数据量,这种实现或许足够,但面对大量数据时,更适合使用高效的排序算法。

优化

上面的方法时间复杂度较高,效率不高。因此可以进行优化。

优化后的代码:

let arr = [2, 4, 5, 1, 3]
 function bubbleSort(arr) {// 冒泡排序
  for (let i = 0; i < arr.length; i++) {
    for (let j = i + 1; j <arr.length; j++) {
      if (arr[i] > arr[j]) {
        [arr[i], arr[j]] = [arr[j], arr[i]]
      }
    }
  }
  return arr
}
console.log(bubbleSort(arr));// [1, 2, 3, 4, 5]

这段代码的逻辑是:

  1. 使用嵌套的循环,外部循环 i 用于遍历数组的每个元素,内部循环 j 用于比较相邻元素的大小。
  2. 比较相邻的两个元素,如果前一个元素大于后一个元素,就交换它们的位置,确保较大的元素向数组的末端移动。
  3. 外部循环每一轮结束,都会使未排序部分的最大元素沉降到数组末尾。
  4. 随着外部循环的进行,整个数组逐渐完成排序。

相较于之前的版本,这段代码实现了真正的冒泡排序算法。

选择排序

选择排序的核心思想是找到未排序部分的最小值,然后与未排序部分的第一个元素交换。通过遍历数组,不断选择最小的元素放到已排序部分的末尾。

以下是选择排序的示例:

let arr = [2, 4, 5, 1, 3]

// 找出(选择)原数组中的最小值,方舟当前数组的最前方

function selectSort(arr) { //收缩区间
  const len = arr.length
  let minIndex;
  for (let i = 0; i < len; i++){
    minIndex = i
    for (let j = i; j < len;j++){//找最小值
      if(arr[minIndex]>arr[j]){
        minIndex = j
      }
    }
    // 交换位置
    [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]
  }
  return arr
}
console.log(selectSort(arr));// [1, 2, 3, 4, 5]

这段代码的逻辑是:

  1. selectSort 函数定义了一个选择排序算法,接受一个数组 arr 作为参数。
  2. const len = arr.length 获取数组的长度,以便于后续循环使用。
  3. 外部循环 for (let i = 0; i < len; i++) 用于遍历数组,每次循环将数组中的第 i 个元素标记为当前未排序部分的起始点。
  4. 在内部循环 for (let j = i; j < len; j++) 中,从当前未排序的部分开始,遍历数组找到未排序部分中最小的元素,并记录其索引为 minIndex
  5. 如果找到的最小值的索引 minIndex 不等于当前外部循环索引 i,则说明找到了比当前未排序部分起始元素更小的值,需要进行交换。交换的目的是将最小值放到未排序部分的最前面。
  6. [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]] 这行代码使用了解构赋值来交换数组中的两个元素,将当前外部循环索引 i 的元素与找到的最小值 minIndex 进行交换位置。
  7. 最后,函数返回排好序的数组。

选择排序是一种简单但低效的排序算法,其基本思想是不断地从未排序部分选择最小的元素放到已排序部分的末尾。

第二种方法(双指针)

let arr = [2, 4, 5, 1, 3]
// 双指针
function selectSort(arr) {
  let i = 0, j = arr.length - 1;
  while (i<j) {
    let minIndex = between(i, j);
    [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
    i++;
  }
  function between(left,right) {
    // 返回左右区间中的最小值
    let minIndex = left;
    for (let i = left; i <= right; i++) {
      if (arr[minIndex] > arr[i]) {
        minIndex = i;
      }
    }
    return minIndex;
  }
  return arr
}
console.log(selectSort(arr));

这段代码的逻辑是:

  1. selectSort 函数:该函数实现了排序算法。它使用了双指针 ij,分别位于数组的头部和尾部。
  2. while 循环:当 i 小于 j 时,执行循环。这是选择排序中未排序部分的范围。
  3. between 函数:该函数用于在 leftright 区间中找到最小值的索引,并返回该索引。它是遍历 left 和 right 区间来找到最小值的简单方法。
  4. 主循环:在主循环中,对 i 所在位置进行了查找最小值的操作。然后,找到最小值的索引并与 i 所在位置的元素进行了交换。

插入排序

插入排序将数组分成已排序和未排序两部分,然后逐步将未排序部分的元素插入到已排序部分的正确位置。

以下是插入排序算法的示例:

et arr = [2, 4, 5, 1, 3]

// 插入排序
// 认定第一个元素是有序的,从第二个元素开始,去找自己在前面已经有序的区间中的位置
function insertSort(arr) {
  const len = arr.length
  let temp;
  for (let i = 1; i < len; i++){
    let j = i;
    temp=arr[i]
    while (j > 0 && temp < arr[j - 1]) { //找到temp能插队的位置
      arr[j] = arr[j - 1]//后移
      j--
    }
    arr[j] = temp
  }
  return arr
}
console.log(insertSort(arr));

这段代码的逻辑是:

  1. insertSort 函数:这是实现插入排序的主要函数。

  2. for 循环:它遍历数组中的每一个元素,从第二个元素开始,因为第一个元素被认为是已排序的。

  3. temp 变量:用于保存当前正在比较的元素的临时变量。

  4. 内部 while 循环

    • 这个循环用于在已排序的区间中找到插入位置。
    • j 指向当前元素,temp 保存当前需要插入的元素。
    • 如果 temp 小于已排序区间的某个元素,则将该元素向后移动,给 temp 腾出位置。
    • 循环会持续直到找到 temp 的正确位置或者 temp 已经是最小的元素,或者 j 已经达到了已排序区间的起始位置。
  5. 最后,将 temp 插入到找到的位置。

  6. 返回已排序的数组。

插入排序的基本思路是从第二个元素开始,逐个将其插入到已排序的部分,直到整个数组都排序完成。

结语

对比与总结

冒泡排序:

  • 思想:通过相邻元素的比较和交换,让较大的元素逐渐“浮”到数组的顶部。
  • 工作原理:重复地遍历数组,依次比较相邻的元素并进行交换,直到整个数组排序完成。
  • 优点:简单直观,代码易于实现。
  • 缺点:效率低下,在大型数据集上性能不佳,时间复杂度为 O(n^2)。
  • 适用场景:适用于小型数据集或者基础算法学习。

选择排序:

  • 思想:从未排序的数据中选择最小的元素,放到已排序区间的末尾。
  • 工作原理:在未排序部分找到最小的元素,然后将其与未排序部分的第一个元素交换,依次循环。
  • 优点:实现简单,空间复杂度低。
  • 缺点:时间复杂度仍然为 O(n^2),不适用于大型数据量的计算。
  • 适用场景:适用于小型数据集,尤其是在空间有限的情况下。

插入排序:

  • 思想:通过构建有序序列,对未排序的数据逐个进行插入,直到整个序列有序。
  • 工作原理:从第二个元素开始,逐个将当前元素插入到已排序区间的合适位置。
  • 优点:对于基本有序的数据效果好,对小型数据集、数据近乎有序的情况性能较好。
  • 缺点:在大型数据集上时间复杂度较高(O(n^2)),尤其在数据集大且无序时效率低下。
  • 适用场景:适用于基本有序的数据或者小型的数据。

总结:

  • 冒泡排序和选择排序的时间复杂度都为 O(n^2),适用于小型数据集。
  • 插入排序在某些情况下效率更高,尤其是对基本有序的数据集。
  • 以上三种排序算法都是基础算法,对于大型数据集不太适用,更高效的排序算法如快速排序、归并排序等适合大规模数据的排序。