5种排序
大致上,平时需要用到的算法有如下几种:
- 冒泡排序
- 选择排序
- 计数排序
- 快速排序
- 插入排序
- 归并排序
冒泡排序
这个排序的算法很简单,其实就是比较相邻两个元素的大小,然后交换,代码如下
/** |
其中需要注意的地方就是 isSwapped
这个标识位,没有这个标识位的话,在一些情况下会做重复两两判断的操作,这里实际上就是一个优化,以下是其动图演示
选择排序
每次选择这个数组的已经排好序的最后一个作为最大/小值(默认第一个是已经排好序的), 然后拿这个值依次与后面的值进行比较,比它大/小就记下它当时的下标,并且把这个值更新 为最大/小值,轮完一圈之后利用当时存的下标就可以与已经排好序的最后一个数交换,完成一轮排序操作,后面的以此类推
/** |
算法动态显示如下
计数排序
本质上这个就是用一个数组,将输入的数字作为这个数组的下标,然后当遇到一个相同数字的时候,对应这个下标的值就自增,最后再对这个已经按照顺序存放好数据的数组进行排序
/** |
算法动态显示如下
快速排序
首先需要从这个数列中挑选出一个基准数,然后对这个数列做如下的排列
- 所有大于该基准数的数都放在这个数列的右边
- 所有小于该基准数的数都放在这个数列的左边
完成这样的分割之后,再递归的重复上述操作,直到排列好的数列的长度小于 2 就返回(return)
还有一个就是随机快速排序的改进版本,其实区别就在于挑选基准数这个规则上,它是随机的
/** |
算法动态显示如下
插入排序
这个跟打牌的道理是一样的,它需要经历以下步骤
- 默认取数列的第一个为已经为排好序的序列
- 然后取剩下的没有排好序的序列的第一个数,从已经排好序的序列的最后一位开始比较
- 如果 没有排好序列的第一个数 比较小,就移动数组将它插入到前面去
- 反复比较,最后插入
/** |
算法动态显示如下
归并排序
这种归并的算法就用到了”分治”的思想,就是指将两个已经排序的序列合并成一个序列的操作,该操作需要以下步骤
- 先把长度为 n 的数组分成两半
- 然后把调用排序函数递归排序(merge sort)
- 最后将数组的左边和右边进行合并(merge)
/** |
算法动态显示如下
算法复杂度
见下表
排序方法 | 时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最优) | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n^2) | O(n^2) | O(n) | O(1) | 稳定 |
选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
计数排序 | O(n+k) | O(n+k) | O(n+k) | O(n+k) | 稳定 |
快速排序 | O(nlogn) | O(n^2) | O(nlogn) | O(nlogn) | 不稳定 |
插入排序 | O(n^2) | O(n^2) | O(n) | O(1) | 稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
其他一些常用算法
二分查找
二分查找是建立在被查找的数组是一个有序序列的基础上的,想法也是很朴素的,即找一个中间的数,若被查找的数小于这个中间的数,那么必然往数组更小的方向去找;若被查找的数大于这个中间的数,那么必然往数组更大的方向去找,然后在下一次找的时候取一个要查找范围的中间数,以此类推,这里尝试用递归的思路实现
/** |
翻转二叉树
经常被人吐槽说若去面试 google,连这道面试题都答不出来的就可以滚了,2333。一个简单的实现就是递归翻转这个二叉树,然后将得到的这个递归出来的节点的左边指向它的右边,将得到的这个递归出来的节点的右边指向它的左边,代码如下
/** |
打印二叉树
用递归
function printTree(prefix, node, isLeft) { |
桶排序
桶排序是计数排序的升级版,比如像这样的数组
[0, 56, 1, 1, 3, 67] |
桶排序算法相对于计数排序升级的地方主要在于其 hash 的存储方式,如下
hash: { |
其实可以看出来,就像是一个数组里面又存放了一个数组,很像二维数组 的存储方式
其本质上就是将数组中重复的数放到这个有限数量的桶里面,然后再次对这个桶里面的数组进行排序,具体实现在 代码链接
基数排序
就是根据 [基数] 来排序,比如有如下数组
23 202 103 566 32 617 37 |
hash 的存储情况如下
hash: { |
思路就是,第一次比较个位,第二次比较十位,第三次比较百位,第四次比较千位…,然后把排列好的序列从桶里面抽出来,放到原来的数组中去,然后再次重复上述操作,这里需要注意的是每个桶都是一个队列,可以利用队列先进先出的特性来排。所以基数排序是在在桶排序的基础上优化而来的,目的是解决桶排序中待排序数字跨度过大,需要使用很多桶空间的问题。桶排序很简单,但是很浪费桶资源,但是对于基数排序,桶的个数是固定的。
具体实现在 代码链接
堆排序
堆可以视为一颗完全的二叉树,完全二叉树的一个性质是,除了最底层之外,每一层都是满的,这使得堆可以用数组来表示,特点就是最大的数在最上面
这里有个关键的地方,叫做 最大堆调整
具体实现在 代码链接
算法特征归纳
- 输入: 一个算法必须有零个或以上输入量
- 输出: 一个算法应有一个或以上输出量,输出量是算法计算的结果
- 明确性: 算法的描述必须无歧义,以保证算法的实际执行结果是精确地匹配要求或期望,通常要求实际运行结果是确定的
- 有限性: 一个算法是能够被任何图灵完备系统模拟的一串运算,而图灵机只有有限个状态,有限个输入符号和有限个转移函数(指令)
- 有效性: 可行性,算法中描述的操作都可以通过已经实现的基本运算执行有限次来实现
参考网站
网站 排序可视化