3 个版本 (重大更改)
使用旧 Rust 2015
0.3.0 | 2016年8月22日 |
---|---|
0.2.0 | 2016年8月18日 |
0.1.0 | 2016年8月12日 |
#557 在 模板引擎
每月 26 次下载
用于 crate-race
26KB
520 行
rdxsort-rs
Rust 中的快速基数排序
版权 (c) 2016 Marco Neumann
lib.rs
:
RdxSort
这个 crate 实现了对不同数据类型的切片进行基数排序,可以直接或通过利用其他数据类型的实现。
实现另一种排序算法的主要原因是因为大多数排序算法都是比较方法。为了排序数据,它们依赖于一个比较数据元素的函数。可以证明这会导致平均时间复杂度为 O(n*log(n))
,在最坏情况下为 O(n^2)
。相比之下,基数排序利用了这样一个事实,即许多数据类型有一个可能值的有限范围,并且在这个范围内有一个有限的分辨率(这也适用于浮点数)。关于详细解释,请参阅维基百科文章。这种特殊处理的结果是降低了并且是恒定的复杂度 O(n*k)
,其中 k
是对特定数据类型进行排序所需的固定轮数。
支持的数据类型
目前支持以下数据类型
- bool: 简单分为 2 部分
- char: 类似于
u32
- 无符号整数:原生实现,取决于宽度
- 有符号整数:将其拆分为正负两部分并使用无符号实现
- 浮点数:将数据拆分为
-∞
,(-∞,-0)
,-0
,+0
,(+0,+∞)
,+∞
,并将这两个范围视为无符号整数值。不支持 非规格数 和NaN
! - 数组,元组:使用内部数据类型的实现
- 自定义数据类型...:填写提供的模板特质
示例
use rdxsort::*;
fn main() {
let mut data = vec![2, 10, 0, 1];
data.rdxsort();
assert!(data == vec![0, 1, 2, 10]);
}
性能
当然,基数排序较低的运行时间复杂度在排序特定数据类型时显示出其力量。优势取决于类型的大小和复杂性。虽然短的无符号整数受益最大,但长类型并不显示出巨大的改进。以下列表显示了不同大小数据集排序所需的运行时间(ns)。数据集使用均匀分布进行采样。以下标记了最佳的算法
请注意,结果可能因硬件、编译器版本、操作系统版本和配置以及天气而异。
小型(1,000个元素)
对于小型数据集,基数排序可以是无符号整数大小为32位的数据类型的优势。对于64位,应首选标准库排序。
类型 | 快速排序 | rdxsort | std |
---|---|---|---|
bool |
4,070 |
2,246 |
26,068 |
char |
31,121 |
20,204 |
34,051 |
f32 |
79,714 |
25,825 |
77,774 |
f64 |
80,954 |
52,262 |
79,431 |
i16 |
32,896 |
12,496 |
31,167 |
i32 |
32,854 |
22,009 |
30,713 |
i64 |
33,064 |
53,366 |
31,669 |
i8 |
24,819 |
8,190 |
46,281 |
u16 |
35,252 |
9,937 |
33,946 |
u32 |
33,002 |
19,202 |
33,627 |
u64 |
32,986 |
47,739 |
33,204 |
u8 |
25,425 |
5,170 |
47,369 |
中型(10,000个元素)
对于中型数据集,由于64位类型的劣势相当小,因此可以盲目地使用基数排序。
类型 | 快速排序 | rdxsort | std |
---|---|---|---|
bool |
52,211 |
22,083 |
400,111 |
char |
655,553 |
192,328 |
508,557 |
f32 |
1,086,882 |
230,670 |
1,117,565 |
f64 |
1,095,529 |
417,104 |
1,152,340 |
i16 |
665,131 |
108,128 |
455,047 |
i32 |
650,501 |
202,533 |
460,097 |
i64 |
669,643 |
378,480 |
470,572 |
i8 |
383,545 |
65,521 |
617,405 |
u16 |
675,060 |
78,424 |
508,264 |
u32 |
670,348 |
177,068 |
511,134 |
u64 |
664,549 |
342,935 |
517,176 |
u8 |
386,572 |
37,012 |
655,377 |
大型(100,000个元素)
对于大型数据集,基数排序对所有数据类型都是很好的。
类型 | 快速排序 | rdxsort | std |
---|---|---|---|
bool |
815,653 |
216,809 |
4,900,426 |
char |
8,131,402 |
2,538,064 |
6,333,865 |
f32 |
13,554,291 |
3,264,657 |
14,340,082 |
f64 |
13,746,190 |
7,122,334 |
15,563,206 |
i16 |
8,235,642 |
1,248,289 |
5,575,196 |
i32 |
8,184,902 |
2,494,882 |
5,852,913 |
i64 |
8,222,482 |
5,446,507 |
6,561,292 |
i8 |
3,664,532 |
703,288 |
7,118,816 |
u16 |
8,272,903 |
866,833 |
6,291,101 |
u32 |
8,193,408 |
2,051,413 |
6,395,163 |
u64 |
8,179,078 |
4,393,579 |
7,216,868 |
u8 |
3,681,361 |
367,240 |
7,816,775 |
实现新类型
此软件包通过实现 RdxSortTemplate
允许您添加对新类型的支持。它描述了数据如何排序到桶中以及安排了多少轮排序。
use rdxsort::*;
// `Clone` is required for `RdxSort`
// `PartialEq` is only required for the equality assert, not for the actual sorting
#[derive(Clone, PartialEq)]
struct Foo {
a: u8,
b: u8,
}
impl RdxSortTemplate for Foo {
// using `#[inline]` is generally recommended since it helps
// the compiler to optimize the sorting algorithm
#[inline]
fn cfg_nbuckets() -> usize {
// usually too high, but works as a simple demonstration
// `256 = 2^8`
256
}
#[inline]
fn cfg_nrounds() -> usize {
// one per sub-type
2
}
#[inline]
fn get_bucket(&self, round: usize) -> usize {
// return the least significant digit first
if round == 0 {
self.b as usize
} else {
self.a as usize
}
}
#[inline]
fn reverse(_round: usize, _bucket: usize) -> bool {
// not required in our case
false
}
}
fn main() {
let mut data = vec![
Foo{a: 5, b: 2},
Foo{a: 0, b: 1},
Foo{a: 5, b: 1},
Foo{a: 0, b: 2},
];
data.rdxsort();
let reference = vec![
Foo{a: 0, b: 1},
Foo{a: 0, b: 2},
Foo{a: 5, b: 1},
Foo{a: 5, b: 2},
];
assert!(data == reference);
}