4 个版本

0.2.0 2022年2月14日
0.1.6 2022年2月12日
0.1.5 2022年2月10日
0.1.1 2022年2月10日
0.1.0 2022年2月10日

数据结构 中排名 1200


clocks 中使用

MIT 许可协议

45KB
496

priq

使用原始二叉堆实现的优先队列(最小/最大堆)。

PriorityQueue 使用原始数组进行构建,以提高性能。

以下两点使这个 PriorityQueue 与其他现有的二叉堆实现不同

1 - 允许使用 PartialOrd 对数据进行排序。 - 其他所有最小-最大堆都需要对分数进行 完全排序(例如,应实现 Ord 特性)。这可能会成为一个问题,例如,当你想要根据浮点分数对项目进行排序时,这些分数没有实现 Ord 特性。 - 由于部分排序,不可比较的值被放在队列的末尾。只有在所有可比较的元素都被 pop 后,才会看到不可比较的值。 - 你可以在这里了解更多关于 Rust 实现 OrdPartialOrd 以及它们之间区别的信息 这里

2 - 将分数和要存储的项目分开。 - 这使得关联项目实现任何排序变得更容易。 - 使评估项目的顺序变得更加容易。

3 - 具有相同分数的项目存储在第一个可用的空闲空间中。 - 这为大量条目提供了性能提升。

4 - 易于使用!

你可以在我的博客 阅读更多关于这个软件包的信息

实现

具有为 score 和相关 item 定义的参数的最小-最大堆!

Default 实现是一个最小堆,其中顶节点(根)是得分最低的元素

                    10
                   /  \
                58      70
               /  \    /  \
             80   92  97   99

父节点的值小于其子节点。

每个父节点,包括顶节点(根节点),都小于或等于其右子节点。

PriorityQueue 允许重复的分数/项目值。当你使用具有相似分数的项目放入队列时,新条目将存储在内存中的第一个空位置。这提供了增量性能提升(而不是通过使用关联项目作为优先级评估的次要工具来解决)。此外,这种实现形式不强制要求元素 T 实现任何排序。这保证了顶节点始终是值最小的。

您可以初始化一个空的 PriorityQueue,然后添加项目

use priq::PriorityQueue;

// create queue with `usize` key and `String` elements
let pq: PriorityQueue<usize, String> = PriorityQueue::new();

或者您可以对 Vec 和/或 slice 进行 堆化

use priq::PriorityQueue;

let pq_from_vec = PriorityQueue::from(vec![(5, 55), (1, 11), (4, 44)]);
let pq_from_slice = PriorityQueue::from([(5, 55), (1, 11), (4, 44)]);

部分排序

因为 priq 允许只实现 PartialOrdscore 参数,所以无法比较的元素将被评估并放在队列的后面

use priq::PriorityQueue;

let mut pq: PriorityQueue<f32, isize> = PriorityQueue::new();

pq.put(1.1, 10);
pq.put(f32::NAN, -1);
pq.put(2.2, 20);
pq.put(3.3, 30);
pq.put(f32::NAN, -3);
pq.put(4.4, 40);

(1..=4).for_each(|i| assert_eq!(i * 10, pq.pop().unwrap().1));

// NAN scores will not have deterministic order
// they are just stored after all the comparable scores
assert!(0 > pq.pop().unwrap().1);
assert!(0 > pq.pop().unwrap().1);

时间复杂度

此数据结构的标准用法是将元素 put 到队列中,并 pop 以删除队列顶部的元素,并检查队列顶部的元素。存储的元素结构是通过使用具有连续内存位置的数组实现的平衡树。这允许维护放入元素之间的正确父子关系。

大O符号的时间复杂度

方法 时间复杂度
put O(log(n))
pop O(log(n))
peek O(1)

您也可以使用循环遍历元素,但返回的切片将不会正确排序,因为每次插入和删除后堆都会重新平衡。如果您想以正确的优先级获取项目,请在循环中调用 pop 直到它返回 None

自定义 struct

如果您想要自定义 struct 而不是有一个独立的和特定的分数,您可以传递 struct 的克隆作为 score 和相关值,但在这种情况下,我建议使用 BinaryHeap,因为它更适合目的。

最小堆

如果您想要最大堆而不是最小堆,其中最高分的元素在顶部,您可以使用 Reverse 或自定义 [Ord] 实现来具有自定义的优先级逻辑。

示例

use priq::PriorityQueue;
use std::cmp::Reverse;

let mut pq: PriorityQueue<Reverse<u8>, String> = PriorityQueue::new();

pq.put(Reverse(26), "Z".to_string());
pq.put(Reverse(1), "A".to_string());

assert_eq!(pq.pop().unwrap().1, "Z");

合并和组合

您可以将另一个优先级队列合并到这个队列中。右侧的优先级队列将排空到左侧的优先级队列中。

示例

use priq::PriorityQueue;

let mut pq1 = PriorityQueue::from([(5, 55), (6, 66), (3, 33), (2, 22)]);
let mut pq2 = PriorityQueue::from([(4, 44), (1, 11)]);

pq1.merge(&mut pq2);
// at this point `pq2` is empty

assert_eq!(6, pq1.len());
assert_eq!(11, pq1.peek().unwrap().1);

您还可以使用 + 运算符合并两个优先级队列。操作数将保持不变。新优先级队列将通过克隆和合并它们来构建。

示例

use priq::PriorityQueue;

let pq1 = PriorityQueue::from([(5, 55), (1, 11), (4, 44), (2, 22)]);
let pq2 = PriorityQueue::from([(8, 44), (1, 22)]);

let res = pq1 + pq2;

assert_eq!(6, res.len());
assert_eq!(11, res.peek().unwrap().1);

性能

这是 priq::PriorityQueue 的基准测试结果

priq 基准测试 中位数 纳秒 标准差
pq_pop_100 146 ns/iter (+/- 1)
pq_pop_100k 291,818 ns/iter (+/- 5,686)
pq_pop_10k 14,129 ns/iter (+/- 39)
pq_pop_1k 1,646 ns/iter (+/- 32)
pq_pop_1mil 16,517,047 ns/iter (+/- 569,128
pq_put_100 488 ns/iter (+/- 21)
pq_put_100k 758,422 ns/iter (+/- 13,961)
pq_put_100k_wcap 748,824 ns/iter (+/- 7,926)
pq_put_10k 80,668 ns/iter (+/- 1,324)
pq_put_1k 8,769 ns/iter (+/- 78)
pq_put_1mil 6,728,203 ns/iter (+/- 76,416)
pq_put_1mil_wcap 6,622,341 ns/iter (+/- 77,162)

std::collections::BinaryHeap 的比较

BinaryHeap 基准测试 中位数 纳秒 标准差
bh_pop_100 272 ns/iter (+/- 90)
bh_pop_100k 171,071 ns/iter (+/- 6,131)
bh_pop_10k 13,904 ns/iter (+/- 130)
bh_pop_1k 1,847 ns/iter (+/- 6)
bh_pop_1mil 8,772,066 ns/iter (+/- 611,613)
bh_push_100 857 ns/iter (+/- 50)
bh_push_100k 943,465 ns/iter (+/- 108,698)
bh_push_10k 92,807 ns/iter (+/- 7,930)
bh_push_1k 8,606 ns/iter (+/- 639)
bh_push_1mil 12,946,815 ns/iter (+/- 900,347)

项目是在 MIT 许可下分发的。有关更多信息,请参阅 LICENSE

依赖项

~310KB