经试验, 桶排序 > 快速排序 > 归并排序/堆排序 > …
复杂度概览(copy过来的)
https://blog.csdn.net/zhc_24/article/details/82153471
另, 桶排序 N + N ( l o g 2 N − l o g 2 M ) N+N(log^N_2 - log^M_2) N+N(log2N−log2M) .
环境:
Idea编辑器
处理器 Intel® Core™ i7-8550U CPU @ 1.80GHz,2001 Mhz,4 个内核,8 个逻辑处理器
已安装的物理内存(RAM) 16.0 GB
工程: E:\Work\JAVA\DemoSet\hahacat\demo-mianshi
Yoga
处理器 Intel® Core™ i7-7500U CPU @ 2.70GHz,2901 Mhz,2 个内核,4 个逻辑处理器
已安装的物理内存(RAM) 8.00 GB
小结: 桶排序 > 快排 > 归并/堆排
归并排序
复杂度分析
[外链图片转存失败(img-BHCk1ME3-1568536386777)(assets/1568258590697.png)]
假设分解到每个叶子节点, 复杂度为常数 c , 树的高度为 l o g ( n + 1 ) log(n+1) log(n+1) , 则总复杂度: c ∗ n ∗ l o g ( n + 1 ) c*n*log(n+1) c∗n∗log(n+1) , 即 O ( n l o g n ) O(nlog^n) O(nlogn) .
测试
数值范围: 1亿
1000W: 7460ms, 6781ms
规模 | 耗时 | 备注 |
---|---|---|
10W | 195, 254, 253 | |
100W | 636, 717, 686 | |
1000W | 9967ms, 9830ms, Yoga: 9056ms, 9017ms, 消除了装箱拆箱: 6757ms | |
10000W | 太慢, 等不及结果出来 |
改为基本类型int后:
规模 | 耗时 | 备注 |
---|---|---|
1000W | 2972ms, 2947ms, 2888ms | |
10000W | 25194ms |
桶排序
复杂度分析
N个数据划分为M个桶, 求最大值最小值O(N), 每个桶内排序复杂度是 ( N / M ) l o g ( N / M ) (N/M)log(N/M) (N/M)log(N/M).
N + M ∗ ( N / M ) l o g 2 N / M = N + N ( l o g 2 N − l o g 2 M ) N+M*(N/M)log^{N/M}_2 = N+N(log^N_2 - log^M_2) N+M∗(N/M)log2N/M=N+N(log2N−log2M)
最坏情况下, M=1, 会比一般的多出 O(N) 的损耗。但只要M达到2, 就会和一般的一样了, 随着M的增大, 复杂度越来越小. 当M=N时, 为 N .
补充说明三点(https://www.cnblogs.com/kkun/archive/2011/11/23/bucket_sort.html)
1,桶排序是稳定的
2,桶排序是常见排序里最快的一种,比快排还要快…大多数情况下
3,桶排序非常快,但是同时也非常耗空间,基本上是最耗空间的一种排序算法
桶数量问题
假如对数组nums进行桶排序,nums的长度为L,最小元素为A,最大元素为B。
则gap为(B-A)/L+1,桶的个数为(B-A)/gap+1。
另外一个重要的性质是,同一个桶中的元素相差最多为gap-1。
对nums中的元素nums[i],确定放入哪个桶的公式为:(nums[i]-A)/gap
https://blog.csdn.net/wusecaiyun/article/details/48048901
<java核心知识点.PDF> 中, 桶数量公式: ( m a x − m i n ) / l e n g t h + 1 (max-min)/length+1 (max−min)/length+1 . 这是有缺陷的.
- 假设, [10, 100, 10000]. 得 3331 , 桶过多.
- [1, …, 10000]. 连续. 得 1 , 桶过少.
而换成, 先确定gap, 在确定桶数. 情形1, 桶数 3; 情形2, 桶数 10001 .
故, 暂推荐, 桶数算法:(并不好)
m
x
m
i
=
m
a
x
−
m
i
n
g
a
p
=
m
x
m
i
/
l
e
n
g
t
h
+
1
b
u
c
k
e
t
N
u
m
=
m
x
m
i
/
g
a
p
+
1
mxmi = max-min \\ gap = mxmi / length +1 \\ bucketNum = mxmi / gap + 1
mxmi=max−mingap=mxmi/length+1bucketNum=mxmi/gap+1
测试(继续上面桶数算法的)
规模 | 耗时 | 备注 |
---|---|---|
1000W | 18411ms, 18302ms, 桶内部排序改用java内置排序方法后: 13339ms | |
没有理论分析的那么快. 但当桶数量合适时, 明显快于归并.
1000W 不同桶数量时.
桶数 | 耗时(ms) |
---|---|
1 | 7008 |
100 | 5109 |
1000 | 4376 |
10000 | 1888 |
100000 | 2204 |
1000000 | 2096 |
10000000 | 3790 |
可见要发挥桶排序的优势, 适当的桶数量很重要.
基数排序
基数排序用的就是桶排序思想.
对于整数, 从低位到高位, 趟. 按数字大小, 定位到对应的桶, 遍历完后, 将所有桶的数倒回源数组. 然后, 走下一个位(高一位). 位数由最大值决定. 不够的高位补0.
基数排序的另一个应用就是, 对字符串数组排序.
实验
对字符串数组, 用基于比较的排序方法和基数排序方法对比.
字符串最大长度: 100
规模 | 基数 | 内置 | 归并 | |
---|---|---|---|---|
10W | 973 | 242 | ||
100W | 11302, 11809, 10842 | 995, 948, 876 | 931, 871 |
注: 基数和归并用的是我自己写的.
字符串最大长度: 2
规模 | 基数 | 内置 | 归并 | |
---|---|---|---|---|
100W | 198 | 1142 | 1135 |
字符串最大长度设为10时, 三者差不多, 在800+ms.
堆排序
堆 堆是一个完全二叉树, 但又有区别, 所谓’堆’, 顾名思义, 就是一堆, 金字塔一样, 堆尖是最小的, 而且每个小堆, 也是一样的堆尖是最小的. 满足堆序性质.
上滤 新增元素时, 依次和父节点比较, 小于父节点, 就要网上冒一层.
下滤 删除时涉及. 镂空的节点下沉.
- 建立最大堆. 假设初始乱序的数组就是一个不满足堆序的堆. 从最后一个非叶子节点开始, 自下而上, 将逐个小堆堆序化(下滤搞定). 遍历完成后, 堆就建立好了. 自上而下不行, 因为局部有序了, 在全局中并不是有序的. 而自下而上, 局部有序, 在更大范围中, 即使无序, 可以用下滤解决.
- 不停的删除堆顶, 删除后, 下滤, 保持堆序性. 然后将被删除的堆顶, 堆末尾空出来的位置.
- 删除完毕后, 数组自然就是升序了.
[外链图片转存失败(img-jdYUgKMc-1568536386778)(assets/1568454018519.png)]
实验
数值范围: 1亿
规模 | 耗时 | 备注 |
---|---|---|
10W | 86, 105, 90 | 略快于归并 |
100W | 1038, 848, 882 | 略慢于归并 |
1000W | 16843, 14908 | |
10000W | 太慢, 等不及结果出来 |
快速排序
- 选取枢纽元(pivot)
- 分割策略: 尽量保持两边平衡. 对于相等元素, 宁愿做无谓的交换.
- 小数组时, 用插入排序或其他.
规模 | 耗时 | 备注 |
---|---|---|
10W | 106, 96 | |
100W | 423, 463 | |
1000W | 4102, 4273, 4075 | |
5000W | 29796, 29645, 28791 |
小数组改为插入排序后, 貌似好一点点.
规模 | 耗时 | 备注 |
---|---|---|
10W | ||
100W | ||
1000W | 4218, 4225, 4187 | |
5000W | 28629, 30305, 28260 |
插入排序
规模 | 耗时 | 备注 |
---|---|---|
10W | 20860, 24290 |