• JavaScript算法之旅:简易桶排序、冒泡排序、快速排序


    简易桶排序

    例子

    小哼的班上只有 5 个同学,这 5 个同学分别考了 5 分、3 分、5 分、2 分和 8 分,哎考得真是惨不忍睹(满分是 10 分)。接下来将分数进行从大到小排序,排序后是 8 5 5 3 2。你有没有什么好方法编写一段程序,让计算机随机读入 5 个数然后将这 5 个数从大到小输出?

    桶排序思路

    1. 新建一个数组 arr,数组长度为 11(包括 0-11),值都是 0
    2. 依次读取分数,有 8 分,就把数组 arr[8]++,其他一样
    3. 循环 arr,每一项值是多少就打印多少次

    js 实现代码

    const numList = [5, 3, 5, 8, 2];
    const arr = Array(11).fill(0);
    
    for (let i = 0; i < numList.length; i++) {
      arr[numList[i]]++;
    }
    
    for (let i = 0; i < arr.length; i++) {
      for (let j = 0; j < arr[i]; j++) {
        console.log(i);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    复杂度

    代码中第 4 行的循环一共循环了 m 次(m 为桶的个数),第 8 行和第 9 行一共循环了 m+n 次。所以整个排序算法一共执行了 m+m+n 次。我们用大写字母 O 来表示时间复杂度,最终桶排序的时间复杂度为 O(2m+n)。还有一点,在表示时间复杂度的时候,n 和 m 通常用大写字母即 O(M+N)。

    冒泡排序

    例子

    将 8, 100, 50, 22, 15, 6, 1, 1000, 999, 0 十个数从小到大排列

    冒泡排序思路

    1. 循环数组,将第一个值和第二个值比较,大的移动到后面,第二个和第三个比较,依次内推
    2. 通过第一组循环之后,可以把最大的值移动到数组末尾,所以可以开始第二组循环,将第二大的值移动到末尾

    js 实现代码

    const arr = [8, 100, 50, 22, 15, 6, 1, 1000, 999, 0];
    
    function sort(arr) {
      for (let j = 0; j < arr.length - 1; j++) {
        for (let i = 0; i < arr.length - j; i++) {
          if (arr[i] > arr[i + 1]) {
            [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]];
          }
        }
      }
      return arr;
    }
    
    console.log(sort(arr));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    复杂度

    冒泡排序的核心部分是双重嵌套循环。不难看出冒泡排序的时间复杂度是 O(N 2)。这是一个非常高的时间复杂度。冒泡排序早在 1956 年就有人开始研究,之后有很多人都尝试过对冒泡排序进行改进,但结果却令人失望。如 Donald E. Knuth(中文名为高德纳,1974 年图灵奖获得者)所说:“冒泡排序除了它迷人的名字和导致了某些有趣的理论问题这一事实之外,似乎没有什么值得推荐的。”你可能要问:那还有没有更好的排序算法呢?不要走开,请看下节——快速排序。

    快速排序

    例子

    将 8, 100, 50, 22, 15, 6, 1, 1000, 999, 0 十个数从小到大排列

    快速排序思路

    1. 找到数组中的某一个值作为基准值,将数组循环,比基准值小的放到基准值左边,比基准值大的放到基准值右边
    2. 在将数组遍历过一次之后,基准值左边和右边再分别去找一个基准值,重复上述操作

    js 实现代码

    const arr = [8, 100, 50, 22, 15, 6, 1, 1000, 999, 0];
    
    function quickSort(arr) {
      if (arr.length <= 1) return arr;
      // 基准值下标
      const numIndex = ~~(arr.length / 2);
      // 基准值
      const num = arr[numIndex];
      // 基准值左侧
      const left = [];
      // 基准值右侧
      const right = [];
    
      arr.forEach((it, idx) => {
        if (idx == numIndex) return;
        it < num ? left.push(it) : right.push(it);
      });
      return [...quickSort(left), num, ...quickSort(right)];
    }
    
    console.log(quickSort(arr));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    复杂度

    快速排序之所以比较快,是因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序一样只能在相邻的数之间进行交换,交换的距离就大得多了。因此总的比较和交换次数就少了,速度自然就提高了。当然在最坏的情况下,仍可能是相邻的两个数进行了交换。因此快速排序的最差时间复杂度和冒泡排序是一样的,都是 O(N2),它的平均时间复杂度为 O (NlogN)。其实快速排序是基于一种叫做“二分”的思想。

  • 相关阅读:
    Spring Boot - Junit4 / Junit5 / Spring Boot / IDEA 关系梳理
    Redis 持久化
    Apache Hop Pipeline Transforms【持续完善中】
    剑指 Offer 27. 二叉树的镜像
    洛谷P1406 方格填数 题解
    视频SDK,高效视频解决方案
    docker 映射端口穿透内置防火墙
    谈谈WorkManager线程池的设计
    大数据题目的解题技巧
    为什么使用KT6368A蓝牙芯片用app连接,基本都在5分钟左右后断开
  • 原文地址:https://blog.csdn.net/Big_YiFeng/article/details/125994018