js算法基础二 快速排序和计数排序

59 阅读3分钟

快速排序

顾名思义,他很快速,但也有其缺点,快速排序不能保证排序的稳定性,即当重复元素存在时,原数组中排在前面的元素,在排序后依然排在前面

快速排序是怎么做的

选取原数组中任意一个值,我这里取第一个元素,将比这个元素大的元素存放到bigger数组,比他小的存放到less数组,然后在分别对bigger和less数组做同样的操作,最后返回[...less + 选取元素 + ...bigger]

代码示例

    // 快速排序
    function quickSort(arr) {
      if(arr.length < 2) return arr
      const length = arr.length
      const origin = arr[0]
      const less = []
      const bigger = []
      for (let i = 1; i < length; i++) {
        const item = arr[i]
        item > origin ? bigger.push(item) : less.push(item)
      }
      const lessArr = quickSort(less)
      const biggerArr = quickSort(bigger)
      return lessArr.concat([origin]).concat(biggerArr)
    }

计数排序

计数排序是用空间换时间的典型,比所有靠比较来排序的算法都快,且排序具有稳定性,但当需要排序的数组的最大值和最小值差距过大时,会非常损耗内存

计数排序是怎么做的

我们都知道数组的角标是升序的整数,那么创建一个新数组,原素组的元素作为角标,在那上之上元素的值,是该元素在原数组出现的次数,那么自然就会按照升序排列了

代码示例

    function countingSort(arr) {
      if (arr.length < 2) {
          return arr
      }
      const countArr = []
      // 计数
      arr.forEach(item => {
        // 注意,数组元素默认为 empty, 直接++ 会变成NaN
        if(!countArr[item]) countArr[item] = 0
        countArr[item]++
      })

      // 将计数的数组展开
      let res = []
      countArr.forEach((item, index) => {
        if(item) {
            let i = item
            while(i > 0) {
                res.push(index)
                i--
            }
        }
      })
      return res
    }

在这之上,我们可以做一些优化
首先是解决元素组最小值之前的位置全部为空,浪费空间的问题;
然后是使用累计数组,使排序具有稳定性

代码示例

// 计数排序
    function countingSort(arr) {
      let min = arr[0]
      let max = arr[0]
      // 获取最大最小值
      arr.forEach(item => {
        if(item > max) max = item
        if(item < min) min = item
      })
      // 为了节省空间,小于最小值的空间没用,可以切掉
      // 从 countArr 中取值要记得加 min
      const countArr = new Array(max - min + 1).fill(0)
      // 计数
      arr.forEach(item => {
        const index = item - min
        countArr[index]++
      })

      // 使用累计数组来使排序稳定,
      // 累计数组通过计数数组映射而来,它的元素值代表它在这个角标下,之前有多少个原数组的元素
      // 我们知道了某个值前面有多少个比他小的值,就能知道他在最终数组中的角标
      countArr.forEach((item, index) => {
        if(index === 0) item = countArr[0]
        else {
            item += countArr[index - 1]
            countArr[index] = item
        }
      })

      // 获取结果时,需要注意如果原数组有相同元素,累计数组的值将是这些相同元素中最后一个的角标+1
      // 而我们通过 countArr[index]-- 来循环添加,就会导致先来的元素进入到靠后的位置
      // 所以要么靠前一个角标的值能查找第一个重复元素的位置(太麻烦),或者遍历时从后向前
      const res = new Array(arr.length)
      for(let i = arr.length - 1; i >= 0; i--) {
        const item = arr[i]
        res[--countArr[item - min]] = item
      }
      console.log('countArr', countArr);
      return res
    }