Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

<漫画算法>常见排序算法整理 #28

Open
jiangtao opened this issue Mar 29, 2020 · 0 comments
Open

<漫画算法>常见排序算法整理 #28

jiangtao opened this issue Mar 29, 2020 · 0 comments

Comments

@jiangtao
Copy link
Owner


整理一下之前看《漫画算法》上的一些基础排序算法。部分资料引用wiki自己做过回顾,已经掌握好排序的童鞋,可以忽略。

排序算法

排序算法常见要求:

  1. 时间复杂度 (最好和最坏情况,什么时候最好,什么时候最坏)
  2. 空间复杂度 (最好和最坏情况,什么时候最好,什么时候最坏)
  3. 稳定性 (元素相等的情况下, 排序之后位置是否发生变化)

快速排序 o(n*logn)

  1. 使用条件 普通的数组
  2. 交换排序 (不稳定排序)

具体实现

  1. 挑选基准值:从数列中挑出一个元素,称为“基准”(pivot),
  2. 分割:重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(与基准值相等的数可以到任何一边)。在这个分割结束之后,对基准值的排序就已经完成,
  3. 递归排序子序列:递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。

额外空间实现

这种空间复杂度高,代码简洁。

function quickSort2(A) {
  if (A.length < 2) return A
  let pivotIndex = A.length >> 1
  let pivotValue = A.splice(pivotIndex, 1)[0]
  let left = [], right = []
  for(let i = 0; i < A.length; i++) {
    (A[i] > pivotValue ? right : left).push(A[i])
  }
  return quickSort2(left).concat(pivotValue, quickSort2(right))
}

原地排序实现

function swap (A, i, j ) {
  let t = A[j]
  A[j] = A[i]
  A[i] = t
} 
/**
 * 双边循环来做, 好理解
 * 双指针 left与小的比较找到最前面的大的, right找大的比较,找到最后面的小的, 然后交换 
 * 小的就在前面, 大的就在后面咯
 * @param {*} A 
 * @param {*} left 
 * @param {*} right 
 */
function partition (A, left, right) {
  let pivotIndex = left
  let pivot = A[left]
  while(left !== right) {
    while (left < right && A[right] > pivot) {
      right--
    }
    while(left < right && A[left] <= pivot) {
      left++
    }
    if (left < right) {
      swap(A, left, right)
    }
  }
  swap(A, pivotIndex, left)
  return left
}
/**
  * 1. 挑选基准值:从数列中挑出一个元素,称为“基准”(pivot),
  * 2. 分割:重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(与基准值相等的数可以到任何一边)。在这个分割结束之后,对基准值的排序就已经完成,
  * 3. 递归排序子序列:递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。
  **/
function quickSort (A, left = 0, right = A.length - 1) {
  
  if (left < right) {
    const pivot = partition (A, left, right)
    quickSort(A, left, pivot - 1)
    quickSort(A, pivot + 1, right)
  }
  return A
}

冒泡排序 o(n*n)

  1. 使用条件 普通的数组
  2. 交换排序 (稳定排序)
i∈[0,N-1)               //循环N-1遍
   j∈[0,N-1-i)           //每遍循环要处理的无序部分
     swap(j,j+1)          //两两排序(升序/降序)

分为有序区和无序区, 想象就跟水泡一下,沉的在下面 (有序区), 轻的在上面 (无序区)..

  • 有序区不用做比较
  • 有序区放在末端,当判定已经拍完序之后就不用再比较了。
function swap(A, i, j) {
  let t = A[i]
  A[i] = A[j]
  A[j] = t
}
function bubbleSort(A) {
  const len = A.length
  for(let i = 0; i < len; i++) {
    // 假定无序区已排序完, 若本次无序区已排序完,则代表排序已经完成,则退出比较,防止多次交换检测
    let sorted = true
    for(let j = 0; j < len - i - 1;j++) {
      if (A[j] > A[j+1]) {
        swap(A, j, j + 1)
        sorted = false
      }
    }
    if (sorted) break
  }
  return A
}

计数排序 o(n+k)

计数排序主要利用了数组索引自带排序特性

  1. 使用条件 值最大最小值确定, 且范围差不大的情况下(MaxValue - minValue + 1 <= 100)

注: 最大值和最小值差太大, 会造成创建的数组过大, 内存过大;
当数组值不是整数 的时候 不适合用这种方法

  1. 稳定排序

代码实现

// 计数排序适合分布比较均匀的排序
function coutSort(A) {
  const max = Math.max.apply(null, A)
  const min = Math.min.apply(null, A)
  const len = max - min + 1
  const L = new Array(len)
  let R = []
  // 统计数字出现的次数,避免造成不必要undefine的空间浪费, 存储 index (value - min)
  for(let i = 0; i < A.length; i++) {
    let v = A[i] - min
    L[v] >= 1 ? L[v]++ : (L[v] = 1)
  }
  // 利用数组的索引自带排序特性
  for(let i = 0; i < len; i++) {
    while(L[i]) {
      R.push(i + min)
      L[i]--
    }
  }
  return R
}

桶排序

样例数据 [1,2,4,100,50]

原理

  1. 桶排序是对计数排序的补充
  2. 桶的数量 N <= Array.length, 分成 N个桶, 分桶是根据 值所在的区间来分, 比如d = 最大值-最小值, value - 最小值 / d 就是桶的范围比 可求出对应的桶的索引值
  3. 在进行每个桶的排序, 可以是桶自己的排序 也可以其他排序

说明

  1. 桶排序弥补了 计数排序的一些缺陷, 数值必须是整数;

代码实现

function bucketSort (A, bucketLen = A.length) {
  const max = Math.max.apply(null, A)
  const min = Math.min.apply(null, A)
  const d = max - min
  const bucketList = Array.apply(null, {length: bucketLen}).map(_ => [])
  for(let i = 0; i < A.length; i++) {
    // 索引结果: idx = len * d / s 
    let idx = parseInt((A[i] - min) * (bucketLen - 1) / d)
    bucketList[idx].push(A[i])
  }
  for(let i = 0; i < bucketLen; i++) {
    bucketList[i].sort((a, b) => a - b)
  }
  return bucketList.reduce((l, p) => (l = l.concat(p)), [])
}

堆排序

后续补充

归并排序

后续补充

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant