最好使用什么样的结构,哈希表,树(红黑树,Splay,...)还是只是一个无序列表(可能是StringArray?)?
(额外的说明:也欢迎提供一个好的C#实现链接)
HashSet<T>
似乎是理想的选择。(如果你需要键,则当然可以使用 Dictionary<T>
)。HashSet<T>
不同操作的时间复杂度摘要,其中一部分基于该类型使用数组作为支撑数据结构的事实。
O(1)
,但如果需要调整数组大小,则可能为 O(n)
。O(1)
O(1)
(假设理想的哈希表桶)HashSet在快速插入和搜索方面非常出色。Add、Remove和Contains的时间复杂度都是O(1)。
需要注意的是,如果数组不需要调整大小,则Add操作的时间复杂度为O(n),正如Noldorin所说。
我最近参与了一个VB 6(我没有写)升级到.NET 3.5项目,在该项目中,我遍历了一个具有子项的集合,每个子项都可以出现在多个父项中。该应用程序处理了一个要发送到API的项目列表,每次调用API都会收取很高的费用。
我基本上使用HashSet来跟踪已经发送过的项目,以防止我们产生不必要的费用。由于该过程被多次调用(它基本上是一个批处理作业,带有多个命令),因此我在调用之间对HashSet进行了序列化。这个方法效果非常好- 我需要尽可能多地重用现有的代码,因为这些代码已经经过了充分的测试。HashSet的性能表现非常快速。
HashSet<T>
,如果您的比较只是“这个字符串是否存在于集合中”。您甚至可以使用不同的IEqualityComparer<string>
实现(可能从StringComparer
中选择),用于区分大小写等。List<T>
提供了BinarySearch方法;我不知道为什么SortedList
和SortedDictionary
没有,因为两者都能够很容易地进行搜索。诚然,SortedDictionary
搜索不会完全与普通二进制搜索相同,但我认为它通常具有类似的特性。)HashSet<T>
是您的朋友。我只是想提出其他情况 :)如果你需要知道“如果它实际上是一个有序列表,这个字符串将出现在集合中的哪里”(如Jon Skeet的答案中所述),你可以考虑使用trie。这种解决方案只能用于某些类型的“类似字符串”的数据,并且如果“字母表”与字符串数量相比较大,则可能会迅速失去其优势。缓存局部性也可能成为问题。
然而,对于仅包含N = 30,000个事先计算的东西的集合来说,这可能过于工程化了。你甚至可以更好地分配一个k * N Optional数组,并通过跳过每个实际事物之间的k个空格来填充它(从而减少你的稀有插入需要重新分配的概率,仍然让你拥有二分搜索的变体,并保持你的项目按排序顺序排列。如果你需要精确“这个字符串将出现在集合中的哪里”,这种方法不起作用,因为你需要O(n)时间来检查每个空间,然后检查它是否为空白或者在插入时更新每个插槽中的“在我之前真正有多少项”的计数器需要O(n)时间。但它可以为你提供非常快速的不精确索引,并且这些索引在插入/删除之间是稳定的。