1. 冒泡排序
时间复杂度: $O(N^2)$
平均空间复杂度: 常数
最快: 正序
最慢: 倒序
1 | // 冒泡排序 |
2. 选择排序
不断找到最小的元素与对应的位置进行交换.
平均时间复杂度: $O(N^2)$
空间复杂度: 常数
最快: 正序
最慢: 倒序
1 | // 选择排序 |
3. 插入排序
类似于插扑克牌, 假设前n-1个是排好序的, 将第n个元素插入前面的有序数列中, 使得这n个数也是排好顺序的.
平均时间复杂度: $O(N^2)$
空间复杂度: 常数
最快: 正序 $O(N)$
最慢: 倒序
1 | // 插入排序 |
4. 希尔排序
对于基本有序的数据序列, 使用插入排序会更高效.
5. 归并排序
分治思想, 将多个已有序的子序列合并, 得到完全有序的序列(多路归并).
自上而下的递归/自下而上的迭代.(递归均可用迭代重写)
平均时间复杂度: $Ο(NlogN)$
1 | // 归并排序 |
6. 快速排序
分治思想, 是处理大数据最快的排序算法之一.
步骤:
- 从数组中选中一个基准
- 比基准小的放在基准左边, 比基准大的放在基准右边. 所有元素结束之后, 该基准处于数列中间位置, called partition (分区操作).
- 递归地将基准左边与基准右边的子数列再进行排序.
平均时间复杂度: $Ο(nlogn)$
空间复杂度: $Ο(logn)$
最慢: $O(N^2)$
1 | // 快速排序 |
7. 堆排序
使用桶
8. 计数排序
非比较排序, 一个桶放一个数字.
速度快于任何比较算法. 核心在于将数字放在对应的桶中, 需要知道元素的大小范围.
每个桶里放一个元素.
时间复杂度: $O(N+K)$. 对于一个大小范围是$0-K$的长度为$N$的数组, 需要遍历两个数组.
空间复杂度: $O(K)$. 需要长度为$K+1$的数组放置这$N$个元素
限制: 只能对整数进行排序, 在最大值与最小值之间差距较大时, 比较浪费空间.
1 | var countingSort = (arr, maxValue) => { |
9. 桶排序
将数组中的元素分到数量有限的桶中, 每个桶中再单独进行排序(可能使用别的排序算法), 即每个桶是一个桶区间.
计算桶区间时, 使用 (max - min)/(桶数 - 1), 除数减一的原因是为了使得最大值也能有桶装, 否则只有最大值刚好被排除在外.
假设 N 为待排序的元素个数, M 为桶个数.
时间复杂度: 将所有元素放入桶中, 数组遍历一遍, 桶遍历一遍, $O(M+N)$
空间复杂度: $O(M)$
优点: 简单/速度快
限制: 空间利用率不高
适用场景: 数据分布比较均匀/数据跨度不是很大, 实际使用桶排序会使用散列表, 提高空间利用率和时间复杂度.
1 | var bucketSort = (arr) => { |
10. 基数排序
非比较排序, 一个桶放对应数位相同的所有数字.
每个桶里放对应位数相同的元素, 比如第一轮放置时, 64, 24, 34一个桶, 第二轮放置时, 63, 65, 567一个桶.
时间复杂度:
假设最大值的位数是 K, 则一共需要遍历数组 K 遍. 每次遍历后将元素放入桶中, 随后还要将元素从桶中拿出来, 需要N的时间.
时间复杂度为: $O(K (N + N)) = O(KN)$.
空间复杂度: 需要桶来存储元素, 每对元素进行除法, 都需要桶来记录当前的结果.
1 | // 基数排序 |