js算法基础一 冒泡/选择/插入/归并排序

56 阅读4分钟

冒泡排序

为了方便,本文所有的排序皆为升序
冒泡排序是指:

  1. 将数组中相邻的元素两两比较,若不符合条件,则交换位置,例如升序排序,如果arr[4] > arr[5],则交换位置
  2. 交换位置之后,应当继续将上一次交换中较小的元素与前一个元素比较,重复上面的过程,直到符合要求

代码实现:

// 冒泡排序
    function bubbleSort(arr) {
      const length = arr.length
      // i < num 即i执行num次,而冒泡总是两两对比,最后一个数是不需要再往后对比的,因此对比length-1次
      for (let i = 0; i < length - 1; i++) {
        // 因为本方法是让最大值冒泡到最后,即冒泡外层循环将最大值排在末尾,
        // 所以每次循环从头开始,而处理过的末尾固定为正确,不需要再比较,通过控制j的最大值掐掉这段末尾
        for (let j = 0; j < length - 1 - i; j++) {
          swap(arr, j, j + 1)
        }
      }
    }

选择排序

选择排序是指:

  1. 将数组中的元素与第一个元素比较,若不符合条件,则交换位置,例如升序排序,第一个元素应当是最小值,则对比中,比第一个元素小的其他元素,需要和第一个元素交换位置
  2. 将最小值确定位置之后,接下来是第二小,以此类推,将所有元素固定到应该的位置上

代码实现:

// 选择排序
    function selectionSort(arr) {
      const length = arr.length
      // 每个都是跟最小值比较,而最小值不跟自己比较,最小值在内层循环中不变,且i从0开始,且小于length-1
      for (let i = 0; i < length - 1; i++) {
        // 从最小值位置+1开始,遍历到最后一个元素
        for (let j = i + 1; j < length; j++) {
          if (arr[j] < arr[i]) {
            swap(arr, j, i)
          }
        }
      }
    }

插入排序

插入排序是指:

  1. 遍历数组,将每个元素(origin)和他之前的元素(arr[j])比较,如果之前的元素比他大,则将之前的元素的值赋值给他后面的那个元素(arr[j+1] = arr[j])
  2. 如果比他小,则将origin赋值给arr[j+1]

光看文字比较难理解,这里推荐一个网站,算法可视化

代码实现:

// 插入排序
    function insertSort(arr) {
      const length = arr.length
      // 根据插入算法规则,第一次对比是第二个元素对比第一个,
      // 也就是说,循环从第二个开始,应当let i = 1
      for (let i = 1; i < length; i++) {
        // 分两种情况
        // 1.子循环未结束,origin就找到位置,origin会插入到比他小的元素的后面,
        // 在代码中,它会在循环的下一次开始时赋值,并跳出
        let origin = arr[i]
        for(let j = i - 1; j >= 0; j--) {
            if(arr[j] < origin) {
                arr[j + 1] = origin
                break
            } else {
                arr[j + 1] = arr[j]
                // 2.子循环已结束,origin未找到位置,这种情况,是因为origin需要排在第一个,没有比他小的,
                // 而循环将原第一个元素赋值给第二个之后,j-- == -1,循环结束了,必须作出处理
                if(j === 0) arr[0] = origin
            }
        }
      }
    }

归并排序

归并排序是指:

  1. 将长度为n的数组拆解成两个数组,分别为left和right,然后再讲left和right拆解,直到将原数组的每个元素都拆为单独的一个数组
  2. 将这些单独的数组两两对比,返回两者合并的升序的数组,然后再将这些合并的数组,按照升序将其中的元素排序并合并,直到返回完成的一个数组

代码实现:

    // 归并排序
    // 整体从拆散到合并
    function megerSort(arr) {
      if (arr.length > 1) {
        let middle = Math.floor(arr.length / 2)
        let left = arr.splice(0, middle)
        let right = arr
        // 有点类似于二叉树的后序遍历
        left = megerSort(left)
        right = megerSort(right)
        return meger(left, right)
      } return arr
    }

    //合并两组升序数组
    function meger(left, right) {
      let leftIndex = 0
      let rightIndex = 0
      const arr = []
      // 当合并的是分解到最后一层,也就是左右都只有一个元素时
      if (left.length === 1 && right.length === 1) {
        left[0] < right[0] ?
          arr.push(left[0], right[0]) :
          arr.push(right[0], left[0])
        return arr
      }

      // 当合并的并非是最后一层,我们要通过不断地将左右中最小的值取出,排到从左到右位置上
      // 使用arr记录顺序,arr的长度应该小于 left.length + right.length - 1

      while (arr.length < (left.length + right.length - 1)) {
        // 这块用于判断是否有一边走完,处理另一边
        // 因为leftright都是升序数组,所以走完的数组的最大值一定小于没走位的数组的走到的那个值
        if(leftIndex === left.length - 1 && right[leftIndex] > left[left.length - 1]) {
            arr.push(left[leftIndex])
            return arr.concat(right.slice(rightIndex))
        }
        if(rightIndex === right.length - 1 && left[leftIndex] > right[right.length - 1]) {
            arr.push(right[rightIndex])
            return arr.concat(left.slice(leftIndex))
        }
        // 普通情况处理
        if (left[leftIndex] < right[rightIndex]) {
          arr.push(left[leftIndex])
          leftIndex++
        } else {
          arr.push(right[rightIndex])
          rightIndex++
        }
      }
      return arr
    }