1个不稳定版本

0.1.0 2020年4月1日

1750 / 算法

MPL-2.0许可证

28KB
436

Tinysort

Tinysort是一个用于尽可能减少内存占用进行数据排序的库。

示例

use tinysort::TinySort;

let mut sort = TinySort::new(8000, 1_000_000, 100_000_000).unwrap();

// create some values to sort between the indicated borders
let mut values: Vec<u32> = (0 .. 1_000_000).map(|i| (100_000_000.0 * (i as f64 * 0.4567895678).fract()) as u32).collect();
for value in values.iter().cloned() {
     sort.insert(value);
}

println!("TinySort using {} bytes of memory, normal sort using {} bytes of memory.", sort.used_space(), values.len() * std::mem::size_of::<u32>());
let sorted: Vec<u32> = sort.into_iter().collect();
values.sort_unstable();
assert!(sorted == values);

此示例将打印TinySort使用1043916字节内存,正常排序使用4000000字节内存.,展示了TinySort带来的近4倍的内存使用下降。

说明

对于排序算法,通常在速度和内存使用之间有一个权衡。对于经典排序算法,我们计算内存使用为算法的额外内存使用,即算法在存储原始值的基础上,需要存储其操作的数据的任何空间。

Tinysort在适用的情况下不需要任何额外内存。事实上,它存储所有排序值的内存比仅仅存储在连续数组中所需的内存还要少。它只需要足够的内存来存储包含的排序值的原始熵,并留出少量用于账簿目的的余地。

这意味着它可以在小于1MiB的内存中排序和存储一百万个介于0到1亿之间的数字。

复杂性

此算法的内存和时间复杂度取决于其输入的两个属性。第一个是它将处理的总值数(N),第二个是输入中包含的最大值(M

算法的内存使用量按最坏情况为 O(N*log(1+M/N) + M*log(1+N/M))(这确实值得一些荒谬的奖励)

算法的时间复杂度在最坏情况下为 O((N + M)*(Log(N)))

内部细节

该算法将排序后的数据以算术编码的比特流的形式存储在循环比特缓冲区中。它累积一定数量的给定值,使用常规排序算法对它们进行排序,然后将这些值与当前存储的排序值的流式解码合并,立即在循环缓冲区中写入新的排序流。要累积的值的数量是根据这些值压缩后缓冲区剩余空间来选择的。

为了以这种方式紧凑地存储所有数字,排序算法并不在压缩流中存储实际数字。它在压缩流中存储一个差分列表。然后通过算术编码对这些差分进行压缩。

选择的算术编码模型有两个字母的字母表。1表示向累加器加1,0表示输出当前数字。这意味着实际上,所有数字首先被转换为一个由n个1组成的一元表示,以一个终止的0结束。由于1的数量将等于已排序的最大数字,而0的数量将与完全排序的数字数量相匹配,这使得算法能够计算字母表的最优概率分布,并使用一个算术编码模型,该模型在理论上接近最大值,即使考虑到算法在固定精度下操作时的低效。

常见问题解答

问:这是如何可能的。

答:从一组值中选择一定数量的值所需的信息量,不考虑顺序,比存储这些值所需的顺序信息量要低得多。

问:这个算法很慢。

答:在内存限制下,它的速度已经是最快的了。

问:你为什么想使用这个。

答:我们必须这样做,因为我们可以。

使用方法

待办:目前该项目还不是完整的库,只需更改 main 中的值来查看其性能。不过,对于更大的值,请准备一些咖啡。

贡献

如果你足够疯狂,认为这是一个可以使用的好主意,你的贡献当然是受欢迎的。

许可证

MPL2.0

无运行时依赖