• 【算法与数据结构】JavaScript实现十大排序算法(二)


    关于排序算法

    在这里插入图片描述

    稳定排序: 在排序过程中具有相同键值的元素,在排序之后仍然保持相对的原始顺序。意思就是说,现在有两个元素a和b,a排在b的前面,且a==b,排序之后a仍然在b的前面,这就是稳定排序。

    非稳定排序: 在排序过程中具有相同键值的元素,在排序之后可能会改变它们的相对顺序。意思是说,现在有两个元素a和b,在原始序列中a排在b前面,排序之后a可能会出现在b后面,它们的相对位置可能会发生变化。

    原地排序: 在排序过程中不需要申请多余的存储空间,只利用原来存储待排数据的存储空间进行比较和交换的数据排序。这意味着在原地排序中,排序操作会直接修改原始数据,而不需要创建新的数据结构来存储排序后的结果。

    非原地排序: 在排序过程中需要申请额外的存储空间来存储临时数据或排序结果,而不直接在原始数据上进行修改。

    快速排序

    基本思路: 通过选取一个基准元素,将数组分为两个子数组,其中一个子数组的元素都小于基准元素,另一个子数组的元素都大于基准元素。然后对这两个子数组分别进行递归排序,最终将它们合并起来,就得到了有序数组。

    操作步骤:

    • 选择一个基准元素(通常是数组中的第一个元素),定义两个空数组(左数组和右数组);
    • 遍历数组,将小于基准元素的元素放到左边的数组,将大于基准元素的元素放到右边的数组;
    • 对左数组和右数组分别进行递归排序;
    • 将左数组、基准元素、右数组合并起来,得到有序数组。

    在这里插入图片描述

    例题:

    a=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48] 进行从小到大排序。

      <script>
        function QuickSort(arr) {
          if (arr.length <= 1) return arr
          // 选择第一个元素作为基准元素
          const pivot = arr[0];
          let left = [], right = []
          // 将数组中比基准元素小的元素放到 left 数组,比基准元素大的元素放到 right 数组
          for (let i = 1; i < arr.length; i++) {
            arr[i] < pivot ? left.push(arr[i]) : right.push(arr[i])
          }
          // 递归地对左右两个数组进行快速排序,然后将结果合并在一起,基准元素在中间
          return QuickSort(left).concat(pivot, QuickSort(right))
        }
        let a = [3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48]
        QuickSort(a)
      </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    总结: 不稳定排序,空间复杂度为O(log n),时间复杂度为O(nlog n)。

    堆排序

    基本思路: 利用堆这种数据结构来实现排序操作,堆必须是一个完全二叉树
    完全二叉树: 除了最底层的叶子节点外,每一层上的节点都有两个子节点(左子节点和右子节点),如果某一层的节点数没有达到最大值,那么这些节点必须从左向右连续存在,不能有空缺。将完全二叉树存储在一维数组中,若节点的下标为i,则左子节点下标为2i+1,右子节点下标为2i+2。

    操作步骤:

    • 建堆(Heapify):将待排序的数组构建成一个二叉堆(通常是最大堆或最小堆)。建堆过程可以从最后一个非叶子节点开始,依次向前对每个节点进行堆调整操作,确保每个子树都满足堆的性质;
    • 排序:建堆完成后,堆顶元素就是最大值或最小值(取决于是最大堆还是最小堆),将堆顶元素与最后一个元素交换,然后将堆的大小减一,再对堆顶元素进行堆调整,使其满足堆的性质。重复这个过程直到堆中只剩一个元素,排序完成。
      在这里插入图片描述
      例题:

    a=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48] 进行从小到大排序。

      <script>
        // 堆排序函数
        function heapSort(arr) {
          // 第一步:建立最大堆
          for (let i = Math.floor(arr.length / 2) - 1; i >= 0; i--) {
            heapify(arr, arr.length, i);
          }
          // 第二步:一个个从堆中取出元素,进行排序
          for (let i = arr.length - 1; i > 0; i--) {
            // 将当前最大的元素移到数组末尾
            [arr[0], arr[i]] = [arr[i], arr[0]];
            // 调整剩余元素为最大堆
            heapify(arr, i, 0);
          }
          return arr;
        }
        // 辅助函数:将以 i 为根的子树调整为最大堆
        function heapify(arr, n, i) {
          let largest = i;
          const left = 2 * i + 1;
          const right = 2 * i + 2;
          // 如果左子节点比根节点大,则标记左子节点
          if (left < n && arr[left] > arr[largest]) {
            largest = left;
          }
          // 如果右子节点比根节点大,则标记右子节点
          if (right < n && arr[right] > arr[largest]) {
            largest = right;
          }
          // 如果最大值不是当前节点,则交换它们,并递归调整下面的子树
          if (largest !== i) {
            [arr[i], arr[largest]] = [arr[largest], arr[i]];
            heapify(arr, n, largest);
          }
        }
        // 测试堆排序
        const arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48];
        heapSort(arr);
      </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    总结: 不稳定排序,原地排序算法,不需要额外的存储空间,空间复杂度为O(1),时间复杂度为O(nlog n)。
    注意: 堆排序的常数因子较大,因此在实际应用中,对于小规模数据或者数据已经近乎有序的情况,可能不如其他排序算法(如快速排序或归并排序)效率高。但对于大规模乱序数据,堆排序通常具有较好的性能。

    计数排序

    基本思路: 统计每个输入的相同元素的个数,然后根据这些统计信息,将元素放回其在输出数组中的正确位置。

    操作步骤:

    • 找到待排序数组中的最大值(max)和最小值(min),确定计数数组的大小范围;
    • 创建一个计数数组(countArray),其大小为max - min + 1,用于统计每个元素在原数组中出现的次数;
    • 遍历待排序数组,统计每个元素的出现次数并存储在计数数组中;
    • 将结果数组返回作为排序结果。
      在这里插入图片描述
      例题:

    a=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48] 进行从小到大排序。

      <script>
        function CountSort(arr) {
          // 找到数组中的最小值和最大值
          let min = Math.min(...arr);
          let max = Math.max(...arr);
          // 创建一个计数数组,初识元素值为0,用于记录每个元素出现的次数
          let countArr = new Array(max + 1).fill(0)
          // 遍历原始数组,统计每个元素出现的次数
          for (let i in arr) {
            countArr[arr[i]]++;
          }
          // 创建一个用于存放排序结果的数组
          let sortArr = []
          // 遍历计数数组,按照顺序将元素添加到排序结果数组中
          for (let i = min; i <= max; i++) {
            if (countArr[i] > 0) {
              sortArr.push(i)
              countArr[i]--
            }
          }
          return sortArr
        }
        const arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48];
        CountSort(arr)
      </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    总结: 稳定排序,空间复杂度为O(k)【k是非负整数的范围(即最大元素值减去最小元素值加1)】,时间复杂度为O(n+k)【n是排序元素的个数,k是非负整数的范围】。
    注意: 计数排序适用于非负整数的排序,如果待排序的元素是浮点数或负数,就不太适合使用计数排序。此外,计数排序在元素范围k较大时,会占用较大的内存空间,因此在元素范围较大的情况下,空间复杂度可能较高。

    桶排序

    基本思路: 将元素分布均匀到不同的桶中,然后对每个桶中的元素进行排序,最后按照桶的顺序将元素合并。

    操作步骤:

    • 确定桶的数量:首先确定要使用的桶的数量,通常桶的数量可以根据元素的范围和数量来确定;
    • 分配元素到桶中:遍历待排序的元素,根据元素的大小将每个元素分配到相应的桶中;
    • 对每个桶进行排序:对每个非空的桶中的元素进行排序,可以使用其他排序算法,例如插入排序或快速排序;
    • 合并桶中的元素:按照桶的顺序,将每个桶中的元素合并成一个有序序列。

    在这里插入图片描述

    例题:

    a=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48] 进行从小到大排序。

      <script>
        function BucketSort(arr) {
          // 找到数组中的最大值和最小值
          let max = Math.max(...arr)
          let min = Math.min(...arr)
          // 计算桶的数量,创建并初始化桶容器
          let bucketCount = Math.floor((max - min) / arr.length) + 1
          let buckets = new Array(bucketCount)
          for (let i = 0; i < buckets.length; i++) {
            buckets[i] = []
          }
          // 将元素分配到桶中
          for (let i = 0; i < arr.length; i++) {
            const bucketIndex = Math.floor((arr[i] - min) / arr.length)
            buckets[bucketIndex].push(arr[i])
          }
          // 对每个桶中的元素进行排序
          const sortArr = []
          for (let i = 0; i < buckets.length; i++) {
            InsertionSort(buckets[i])
            sortArr.push(...buckets[i])
          }
          return sortArr
        }
        // 插入排序
        function InsertionSort(arr) {
          for (let i = 1; i < arr.length; i++) {
            let temp = arr[i]
            let j;
            for (j = i - 1; j >= 0 && arr[j] > temp; j--) {
              arr[j + 1] = arr[j]
            }
            arr[j + 1] = temp
          }
        }
        const arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48];
        BucketSort(arr)
      </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    总结: 稳定排序,空间复杂度为O(n+k)【 n 为待排序元素的数量,k 为桶的数量】,时间复杂度为O(n+k)【 n 为待排序元素的数量,k 为桶的数量】。
    注意: 桶排序要求元素必须均匀分布在不同的桶中,否则可能导致不均匀的桶大小,影响排序性能。

    基数排序

    基本思路: 将所有元素统一为相同位数长度,位数长度不足的进行补零。接着从最低位开始,将每位相同的放到同一个容器里,依次进行排序。然后从最低位一直到最高位按照上述操作进行排序,就能得到有序序列。

    操作步骤:

    • 确定待排序的整数范围(通常是最小值和最大值);
    • 根据范围确定需要的容器数量;
    • 将待排序的整数按照个位、十位、百位等位数进行分配到相应的容器中(这里可以使用计数排序作为每个位数上的排序算法);
    • 按照容器的顺序依次收集每个容器中的元素,形成新的待排序序列;
    • 重复步骤3和步骤4,直到对所有位数都进行了排序。

    例题:

    a=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48] 进行从小到大排序。

    在这里插入图片描述

      <script>
        function RadixSort(arr) {
          // 获取数组中的最大值
          let max = Math.max(...arr)
          let maxDigit = getMaxDigits(max)
          for (let i = 0; i < maxDigit; i++) {
            // 创建并初始化容器
            const buckets = Array.from({ length: 10 }, () => [])
            for (let j = 0; j < arr.length; j++) {
              const digit = getDigit(arr[j], i)
              buckets[digit].push(arr[j])
            }
            arr = [].concat(...buckets)
          }
          return arr
        }
        // 获取数组中最大位数
        function getMaxDigits(max) {
          let digit = 0
          while (max > 0) {
            max = Math.floor(max / 10)
            digit++
          }
          return digit
        }
        // 获取元素的某位数上的数字
        function getDigit(num, place) {
          return Math.floor(Math.abs(num) / Math.pow(10, place)) % 10;
        }
        let arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48]
        let a = RadixSort(arr)
        console.log(a);
      </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    总结: 稳定排序,空间复杂度为O(n+k)【 n是数组的长度,k是桶的数量】,时间复杂度为O(d*(n+k))【d是最大数字的位数,n是数组的长度,k是每个桶的最大容量】。
    注意: 基数排序要求待排序的元素必须是非负整数,而且适用于整数排序。

  • 相关阅读:
    jpcap 分支tcpdump抓包文件遇到的问题以及解决情况
    IPV4和IPV6是什么?
    嵌入式分享合集24
    JoySSL:免费SSL证书的新选择
    AudioOptions
    [附源码]计算机毕业设计汽车租赁管理系统Springboot程序
    修改时间服务器-域控环境
    IPTABLES问题:DNAT下如何解决内网访问内部服务器问题
    论软件工程
    React的useLayoutEffect和useEffect执行时机有什么不同
  • 原文地址:https://blog.csdn.net/aDiaoYa_/article/details/133177008