排序算法

78 阅读2分钟

回顾下冒泡算法、插排算法、快排算法等算法的实现思想。 首先来看看for循环的执行过程:

    let array1 = [1, 2, 32, 3, 44, 5, 0]
    let array2 = [1, 2, 32, 3, 44, 5, 0]

    let arr = []
    let reverseArr = []

    for (let j = 0; j < array1.length; j++) {
      console.log(array1[j]);
      arr[j] = array1[j]
    }
    // for循环的本质:先j=0 => j < array.length => console.log(array[j])= > j++ => j < array.length =>  console.log(array[j])等

    for (let i = array2.length - 1; i >= 0; i--) {
      console.log(array2[i]); // 倒序版
      reverseArr[i] = array2[i]
    }
    // 循环关系:index = length - 1

    console.log(arr); // [1, 2, 32, 3, 44, 5, 0]
    console.log(reverseArr); // [1, 2, 32, 3, 44, 5, 0]

基本排序类

1.冒泡算法

应用场景类似于:气泡轻的一直往上冒泡。

    let array = [1, 2, 32, 3, 44, 5, 1]
    let temp = null
    // 外层循环i控制比较的轮数
    for (let i = 0; i < array.length - 1; i++) {
      // 内层循环控制j每一轮比较的次数
      for (let j = 0; j < array.length - 1 - i; j++) {
        if (array[j] > array[j + 1]) { // 大于号执行交换操作,即完成升序效果
          // temp = array[j]
          // array[j] = array[j + 1]
          // array[j + 1] = temp
          [array[j], array[j + 1]] = [array[j + 1], array[j]]
        }
      }
    }
    console.log(array); // [1, 1, 2, 3, 5, 32, 44]

总结:数组位置,两两比较,大的放在后面。

2.选排算法

应用场景类似于:不断的从一组数据中选择极值的过程。

    let array = [1, 2, 32, 3, 44, 5, 1]
    for (let i = 0; i < array.length; i++) {
      let minIndex = i //保存最小数字的索引
      for (let j = i + 1; j < array.length; j++) {
        if (array[j] < array[minIndex]) {
          minIndex = j //数字两两比较,保存小的那个数的索引
        }
      }
      [array[i], array[minIndex]] = [array[minIndex], array[i]];
    }
    console.log(array); //  [1, 1, 2, 3, 5, 32, 44]

总结:这个算法跟冒泡容易碰瓷。冒泡排序是左右两个数相比较,而选择排序是用后面的数和每一轮的第一个数相比较。

3.插排算法

应用场景类似于:拿扑克牌到手中排序的这个过程。

    let array = [1, 2, 32, 3, 44, 5, 1]
    let handle = [];
    handle.push(array[0]);
    for (let i = 1; i < array.length; i++) {
      //A是新抓的牌
      let newGetOne = array[i];
      //用新抓的牌和手里的牌依次比较(从后往前比较)
      for (let j = handle.length + 1; j >= 0; j--) { // handle.length -1 + 2 = handle.length + 1
        //B是手里的牌
        let handleOne = handle[j];
        //如果新牌比旧牌大,就放在旧牌的后面,停止循环
        if (newGetOne > handleOne) {
          handle.splice(j + 1, 0, newGetOne); // 往后插入
          break; //插入之后,比较结束,进行下一次比较
        }
        //如果已经比到第一项,就直接把手里的牌放到最前面
        if (j == 0) {
          handle.splice(0, 0, newGetOne); // 往前插入
          //同 handle.unshift(newGetOne);
        }
      }
    }
    console.log(handle); // [1, 1, 2, 3, 5, 32, 44]

总结:如同现实生活中打扑克或者打麻将的时候,把牌排列好,把一手牌给分好我们常用的就是拿出没有排序的部分的牌移动到排好序的牌中,位置定好,让它插入。

高级排序类

1.快排算法

应用场景类似于:二分法查找的一种实现,一直查找中间的数值进行排序。

    let array = [1, 2, 32, 3, 44, 5, 1]
    let quickSort = function (arr) {
      // 结束递归
      if (arr.length <= 1) {
        return arr;
      }
      // 找到数组的中间项,并在原有的数组中把它移除
      var middleIndex = Math.floor(arr.length / 2);
      var middleVal = arr.splice(middleIndex, 1)[0];
      // 准备左右两个数组,循环剩下数组,把数值小的放到left数组中,反之放到right数组中
      var left = [];
      var right = [];
      for (var i = 0; i < arr.length; i++) {
        if (arr[i] < middleVal) {
          left.push(arr[i]);
        } else {
          right.push(arr[i]);
        }
      }
      return [...quickSort(left), middleVal, ...quickSort(right)]
      // return quickSort(left).concat([middleVal], quickSort(right));
    };
    let res = quickSort(array)
    console.log(res); //  [1, 1, 2, 3, 5, 32, 44]

总结:二分法查找的一种实现,一直查找中间的数值进行排序。

2.希尔算法

应用场景类似于:分组间隔不断缩小,把分组对应下标的人进行排序。

    let array = [1, 2, 32, 3, 44, 5, 1]
    let len = array.length;
    // 处理步长
    for (let gap = Math.floor(len / 2); gap > 0; gap = Math.floor(gap / 2)) { //gap: 3 , 1  是数组中间元素的index
      // 分组插入排序
      for (let i = gap; i < len; i++) {
        // console.log(i); // 3 4 5 6 1 2 3 4 5 6 ,j的值因为i的值变化
        
        // let j = i
        // let current = array[i]
        // while (j - gap >= 0 && current < array[j - gap]) {
        //   array[j] = array[j - gap];
        //   j = j - gap;
        // }
        // array[j] = current;
        
        for (let j = i; j >= gap && array[j] < array[j - gap]; j = j - gap) {
          [array[j - gap], array[j]] = [array[j], array[j - gap]]
        }
      }
    }
    console.log(array);//  [1, 1, 2, 3, 5, 32, 44]

总结:分组间隔不断缩小,把分组对应下标的人进行排序。

3.归并算法

应用场景类似于:将两个已经排序的序列合并成一个序列的操作,采用“分治法”。

    let array = [1, 2, 32, 3, 44, 5, 1]
    function mergeSort(arr) {
      // 序列长度为1时退出
      if (arr.length < 2) {
        return arr
      }
      // 将序列分为两个子序列,这一块起到“分治法”中的“分割”
      const middle = Math.floor(arr.length / 2)
      const left = arr.slice(0, middle)
      const right = arr.slice(middle)
      // 递归,这一块起到“分治法”中的“集成”
      return merge(mergeSort(left), mergeSort(right))
    }

    function merge(left, right) {
      const result = []
      // 两个子序列进行比较,从小到大放入新的序列result中
      while (left.length > 0 && right.length > 0) {
        // 将较小的放入result,并改变left或者right的长度,灵活使用shift方法
        if (left[0] < right[0]) {
          result.push(left.shift())
        } else {
          result.push(right.shift())
        }
      }
      // 先将小的元素放入result中,直到left或者right为空,剩余的一个数组肯定是大于result的有序序列,所以直接通过concat进行合并返回
      return result.concat(left, right)
    }

    let res = mergeSort(array)
    console.log(res); //  [1, 1, 2, 3, 5, 32, 44]

总结:将两个已经排序的序列合并成一个序列的操作,采用“分治法”。

可以学习:B站某up主手撕常见的算法,上面六个基本的算法,里面的构思很有值得研究的地方,后续会持续关注并补充!!!