[面试向]JS简单实现常见排序算法

为了面试, 几种常见算法简单好理解的写法, 不考虑空间复杂度之类的, 以升序为例, 概念来自百度 边界问题, 注意两点即可

  1. 数组起始index为0
  2. 数组长度为<=1即为有序
var a = [9,4,7,1,8,5,3,2,6,2]
function swap(arr, x, y) {
  [arr[x], arr[y]] = [arr[y], arr[x]]
}
复制代码

1. 冒泡排序

它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素已经排序完成。

一. 遍历, 前后比较, 前边的大, 就交换 (由于相同的值不会交换位置, 稳定的排序)

二. 对前n-1项重复一 (每次都将未排序部分的最大值换到了最后)

const bubbleSort = array => {
  const len = array.length
  for (let t = 0; t < len-1; t++){ // 前len-t项是无序区, 后t项为有序区
    for (let i = 0; i < len-t; i++) { //遍历无序区, 将最大值依次交换至无序区末尾
      if (array[i] > array[i+1]) {
        swap(array, i, i+1)
      }
    }
  }
  return array
}
复制代码

2. 选择排序

它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。 以此类推,直到全部待排序的数据元素排完。

一. 遍历, 前后比较, 记下较大值的index, 遍历结束即获得(选出)最大值的index, 交换至无序区最后(如例子中的两个2, 先碰到第一个2放到了无序区最后, 交换了位置, 不稳定排序)

二. 对前n-1项重复一

const selectSort = array => {
  const len = array.length
  for (let t = 0; t < len-1; t++) { // 前len-t项是无序区, 后t项为有序区
    var maxValueIndex = 0
    for (let i = 0; i < len-t; i++){ //遍历无序区, 获得最大值的index, 交换至无序区末尾
      if (array[i] > array[maxValueIndex]) {
        maxValueIndex = i
      }
    }
    swap(array, maxValueIndex, len-t-1)
  }
  return array
}
复制代码

3. 插入排序

每步将一个待排序的记录,按其关键码值的大小插入前面已经排序的文件中适当位置上,直到全部插入完为止。

emmm, 这里其实是写了个"反着"的冒泡排序, 前t项为有序区, 后len-t为无序区, 每次选无序区的开头, 往前交换(依次从无序区取值, 插入有序区, 稳定排序)

const insertSort = array => {
  const len = array.length
  for (let t = 1; t < len; t++) { //起始, 第0项已经为有序区, 遍历无序区往前交换
    for (let i = t; i >= 0; i--){ //前t项为无序区, 将较小值从后往前交换
      if (array[i-1] > array[i]) {
        swap(array, i-1, i)
      }
    }
  }
  return array
}
复制代码

4. 快速排序

通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小, 然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

一 选一个pivot(标志), 比p小的放左边, 比p大的放右边

二 对分成的两组重复操作一

三 concat起来

const quickSort = array => {
  if (array.length <= 1) { // 有序了, 这部分结束
    return array
  }
  const len = array.length,
        pivot = array.splice(len-1, 1), // 随便选一个p, 并剔除数组, splice选最后一个快一点..., 选最后一个(这里已经可以看出, 不稳定...)
        l = [], r = []
  array.forEach(item => {
    item < pivot ? l.push(item) : r.push(item) //小的放左边, 大的放右边
  });
  return quickSort(l).concat(pivot).concat(quickSort(r)) //从小到大concat起来
}
复制代码

5. 堆排序

指利用堆这种数据结构所设计的一种排序算法 完全二叉树, 大顶堆(每个父节点都比子节点打, 那根节点就是最大的), 请百度一下看看图, 方便理解

感觉这个还是冒泡排序的思路, 首先, 还是分为无序区和有序区, 区别就是每次把无序区搞成大顶堆, 而不是通过依次交换来得到最大值 既然无序区是大顶堆, 每次把根节点放在有序区之前就行了

一. 初始化一个大顶堆, 得到最大值(根节点, array[0]), 将根节点与最后一个值交换, array[len-1]变成了有序区

二. 继续把前len-1项搞成大顶堆, 得到最大值, 即重复操作一

所以... 我们需要一个"搞出大顶堆"的函数, 然后交换值就完事儿了

const shiftDown = (array, i, len) => { //将节点i为根的树, 调整为大顶堆, len就是无序区的长度
  const p = array[i] //父节点
  for (let j = 2*i+1; j<len; j = 2*j+1) {
    if (j+1<len && array[j] < array[j+1]){
      j++ //取两个子节点中较大的
    }
    if (array[j] > p) { //如果父节点值小, 就交换, 换完不能保证该子树为大顶堆, 继续; 否则跳出
      swap(array, i, j) //(不稳定排序)
      i = j //交换后, 父节点下移
    } else {
      break
    }
  }
}
const heapSort = array => {
  const len = array.length
  //1.初始化大顶堆
  //从最后一个非叶子节点(len/2-1)开始(毕竟叶子结点已经是个大顶堆了), 从下往上, 将每个子树调整为大顶堆
  for (let i = Math.floor(len/2-1); i>=0; i--) {
    shiftDown(array, i, len)
  }
  for (let i = len-1; i>0; i--) {
    swap(array, 0, i) // 将根节点(最大值)交换到最后
    shiftDown(array, 0, i) // 无序区搞成大顶堆, 以获得最大值
  }
  return array
}
复制代码

总结

冒泡和选择差不太多, 都是从无序区获得最大值, 放到有序区的开头, 选出最大值的方法不一样

插入排序, 反过来, 依次从无序区取值, 插入到有序区正确的位置

堆排序, 高级点的冒泡...

快排, 分治, 递归的去划分数组

上边的写法也都不是最优的写法, 不过面试应该够了, 学有余力, 可以优化

比如遍历中+个flag提前停止, 快排不要新建数组(多占用了内存)之类的

但是感觉面试的时候, 手写本来就不如键盘码, 还要处理边界, 很容易写错...

最后, 最近在准备面试找工作, 两年半经验的前端, 比较熟悉vue, 有人要嘛...

转载于:https://juejin.im/post/5c4f2e055188250f6f0cf862

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值