绝对记得住的各种排序算法(上)

627 阅读4分钟

「这是我参与2022首次更文挑战的第29天,活动详情查看:2022首次更文挑战

前言

今天我们复习一下基础排序算法

  • 冒泡排序
  • 插入排序
  • 选择排序

一步一步进行深入,然后会讲一下面试官会如何询问

排序算法

冒泡排序

步骤

  • 1.从第一个元素开始,比较每两个相邻元素,如果前者大,就交换位置
  • 2.每次遍历结束,能够找到该次遍历过的元素中的最大值
  • 3.如果还有没排序过的元素,继续1

2022年5月26日思考 为什么要两层循环

  • 第一层循环是告诉要开启新一轮冒泡,并把i传给第二层循环
  • 第二层循环的意思就是这次循环要对比几个length-i个数

动画演示

基础写法

function bubbleSort(nums: number[]) {
  const length = nums.length;
   // 第一层循环可以使每次把最大的值,放到数组的最后面
  for (let i = 0; i < length; i++){
   // 第二层值只需要遍历len-i次,因为最大的值已经到数组最后面了
   // 第二层循环是用来交换的
    for (let j = 1; j < length - i; j++){
      if (nums[j-1] > nums[j]) {
        [nums[j-1], nums[j]] = [nums[j], nums[j-1]];
      }
    }
  }
}
  • 时间复杂度:O(n^2),两个for循环,并且最多都能执行n次,所以复杂度是O(n^2)
  • 空间复杂度:O(1),没有把那个变量的空间进行扩展,所以空间复杂度为O(1)

我们考虑一下,如果题目给的数组就是有序的,那么我们还要循环两遍是不是有点多余,我们可以加一个标志位,来判断是不是这个数组本身就是有序的,以此来减少时间复杂度

进阶写法

增加标志位也叫哨兵法

function betterBubbleSort(arr) {
    const len = arr.length  
    for(let i=0;i<len;i++) {
        // 区别在这里,我们加了一个标志位
        let flag = false
        for(let j=0;j<len-1-i;j++) {
            if(arr[j] > arr[j+1]) {
                [arr[j], arr[j+1]] = [arr[j+1], arr[j]]
                // 只要发生了一次交换,就修改标志位
                flag = true
            }
        }
        // 若一次交换也没发生,则说明数组有序,直接放过
        if(flag == false) return arr;
    }
    return arr
}
  • 此时最好情况的时间复杂度变为O(n)

选择排序

步骤

  • 1.取出未排序部分的第一个元素,遍历该元素之后的部分并比较大小。对于第一次遍历,就是取出第一个元素
  • 2.如果有更小的,与该元素交换位置
  • 3.每次遍历都能找出剩余元素中的最小值并放在已排序部分的最后

动画演示

写法

const selectSort = (arr) => {
    let length = arr.length;
    //没有运算符“<”不能应用于 JavaScript 中的类型“未定义”错误,就像您在其他类型语言中发现的那样。因此,JavaScript 将带有运算符的不兼容类型评估为 false。
    for (let i = 0; i < length; i++) {
        // 初始化最小值为i
        let min = i;
        // 寻找未排序部分的最小值坐标
        for (let j = i+1; j < length; j++) {
             if (arr[min] > arr[j]) {
                min = j;
             }
        }
        // 最小值坐标如果不是原坐标的话,那么交换位置
        if (min !== i) {
            [arr[i], arr[min]] = [arr[min], arr[i]];
        }
    }
    return arr;
};
  • 时间复杂度:O(n^2),两个for循环,时间复杂度最大为O(n^2)

  • 空间复杂度:O(1)

  • 和冒泡排序差别:冒泡排序是把最大的冒泡排序到后面,选择排序是把最小的选择放到最前面


插入排序

步骤

  • 1.将一组待排序的数据,分成2段,一段是“已经排序”了的数据,另一段是“未排序”的数据。
  • 2.每次都从“未排序”的数据中取出一个元素,将这个元素插入到“已经排序”数据中的正确的位置(可能会涉及到原有元素的移动),那么插入后,“已经排序”区段中的数据依然是有序的,
  • 3.只要这样不停的循环,直到所有的“未排序”的数据都已取完,则整个排序完成。

动画演示

写法

export const insertSort = (arr) => {
    // 时间复杂度为O(n^2),所以两个循环
    // 此处为什么i为1,因为要分成两段,未排序的从1开始,留出一个给已排序的
    for (let i = 1; i < arr.length; i++) {
        // 已排序部分倒着排序
        // 如果当前的比前一个大,那么进行交换
        // 此处要想明白第一次交换的是哪两个,是已排序的最后一个,和未排序的第一个进行交换
        for (let j = i - 1; j >= 0; j--) {
            // 每次都要用前面的和后面的对比
            if (arr[j] > arr[j + 1]) {
                [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
            }
        }
    }
    return arr;
};
  • 时间复杂度:O(n^2),外面的循环最大为n,里面的循环最大也为n,那么时间复杂度依然是O(n^2)
  • 空间复杂度:O(1),这没啥好说的

面试如何提问

使用那种排序最快

  • 如果数据规模小(n<50)的话,那么选择排序,插入排序都很快
  • 如果有序的话,那么冒泡排序很适合
  • 如果数据规模大的话,那么快速排序,归并排序更适合

总结

今天我们讲了冒泡排序,选择排序,插入排序,大家一定要认真学习,学习东西的时候一定要谨记,如何在学习的时候,让你百日之后依然能记得今天所学的内容,所以一定要学透,不要求快