我需要对大量任意长度的文本字符串进行排序。我想基数排序是这里最好的选择。列表真的很大,所以将字符串填充到相同的长度是完全不可能的。
这个任务是否有现成的实现,最好是在 C# 中?
5 回答
根据您的需要,您可能会发现将所有字符串插入某种形式的 Trie 是最佳解决方案。即使是基本的三元搜索 Trie 也将比字符串数组/列表具有更小的内存占用,并将按排序顺序存储字符串。
插入、查找和删除都是字母表O(k * log(a))
的a
大小(一个字符的可能值的数量)。由于a
是恒定的,log(a)
所以你最终得到了一个O(n * k)
排序算法。
编辑:如果您不熟悉 Tries,它们基本上是 n 叉树,其中每条边代表键的单个字符。插入时,您检查根节点是否包含与键的第一个字符匹配的边(或子节点,等等)。如果是这样,您按照该路径并重复第二个字符,依此类推。如果没有,则添加新边缘。在三元搜索树中,边/子节点存储在二叉树中,因此字符按排序顺序排列并且可以及时搜索log(a)
。如果你想浪费内存,你可以将边/子元素存储在一个大小数组中,a
这样你就可以在每个步骤中不断查找。
多少是多少,一百万?
内置的List<string>.Sort()
平均需要 O(n * log(n))。
log2(10^6) ~=20,对于 10^6 个元素,这并不比 O(n) 慢很多。如果您的字符串长度超过 20 个字符,则基数排序 O(n * k) 将“更慢”。
我怀疑基数排序会比内置排序快得多。但是测量和比较会很有趣。
编辑:我之前所做的这些陈述是有道理的,但总体而言,这一点是错误的。
基数排序是对大量字符串使用的错误排序。对于像这样的事情
I really like squirrels. Yay, yay, yay!
I really like blue jays. Yay, yay, yay!
I really like registers. Yay, yay, yay!
您将有一堆条目落在同一个桶中。你可以通过散列来避免这种情况,但是对散列码进行排序有什么用呢?
使用快速排序或合并排序等。(快速排序通常性能更好,占用的内存更少,但许多示例的最坏情况性能为 O(N^2),这在实践中几乎从未发生过;合并排序的性能不太好,但通常实现为稳定,而且它是容易做部分在内存和部分在磁盘上。)即使用内置的排序功能。
编辑:好吧,事实证明,至少在非常大的文件中,开始时重复很长(例如源代码)并且许多行完全相同(实际上是 100 次重复),基数排序确实开始与快速排序竞争。我很惊讶!但是,无论如何,这是我用来实现基数排序的代码。它是在 Scala 中,而不是 C# 中,但我已经以相当迭代的风格编写了它,所以它应该是相当明显的事情是如何工作的。唯一的两个棘手的位是(a(i)(ch) & 0xFF)
从字节数组(字节有符号)中提取一个 0-255 字节,这counts.scanLeft(0)(_ + _)
形成计数的累积和,从零开始(然后indices.clone.take(257)
接受除最后一个之外的所有参数),并且 Scala 允许多个参数列表(因此我将始终提供的参数与具有递归使用的默认值的参数分开)。这里是:
def radixSort(a: Array[Array[Byte]])(i0: Int = 0, i1: Int = a.length, ch: Int = 0) {
val counts = new Array[Int](257)
var i = i0
while (i < i1) {
if (a(i).length <= ch) counts(0) += 1
else { counts((a(i)(ch)&0xFF)+1) += 1 }
i += 1
}
val indices = counts.scanLeft(0)(_ + _)
val starts = indices.clone.take(257)
i = i0
while (i < i1) {
val bucket = if (a(i).length <= ch) 0 else (a(i)(ch)&0xFF)+1
if (starts(bucket)+i0 <= i && i < starts(bucket)+i0+counts(bucket)) {
if (indices(bucket) <= i) indices(bucket) = i+1
i += 1
}
else {
val temp = a(indices(bucket)+i0)
a(indices(bucket)+i0) = a(i)
a(i) = temp
indices(bucket) += 1
}
}
i = 1
while (i < counts.length) {
if (counts(i)>1) {
radixSort(a)(i0+starts(i),i0+starts(i)+counts(i),ch+1)
}
i += 1
}
}
时间是 7M 行源代码(70k 行的 100 倍重复),基数排序与内置库排序并列,然后获胜。
String.Compare() 重载正在使用这种字符串比较。看看你需要的是把它提供给你的排序算法。
更新
这是实现:
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern int nativeCompareString(int lcid, string string1, int offset1, int length1, string string2, int offset2, int length2, int flags);
用您自己的实现很难击败这个原生的非托管实现。