Doug McIlroy 的解决方案的时间复杂度为 O(T log T),其中 T 是单词的总数。这是由于第一个sort
.
为了比较,以下是相同问题的四种更快的解决方案:
这是一个具有上限时间复杂度 O((T + N) log N) 的 C++ 实现,但实际上 - 几乎是线性的,接近 O(T + N log N)。
下面是一个快速的 Python 实现。在内部,它使用时间复杂度 O(T + N log Q) 的哈希字典和堆,其中 Q 是唯一词的数量:
import collections, re, sys
filename = sys.argv[1]
k = int(sys.argv[2]) if len(sys.argv)>2 else 10
reg = re.compile('[a-z]+')
counts = collections.Counter()
for line in open(filename):
counts.update(reg.findall(line.lower()))
for i, w in counts.most_common(k):
print(i, w)
另一个使用 AWK 的 Unix shell 解决方案。它的时间复杂度为 O(T + Q log Q):
awk -v FS="[^a-zA-Z]+" '
{
for (i=1; i<=NF; i++)
freq[tolower($i)]++;
}
END {
for (word in freq)
print(freq[word] " " word)
}
' | sort -rn | head -10
这是Anders Kaseorg 在 Rust中的一个非常快速的解决方案。
CPU时间比较(以秒为单位):
bible32 bible256 Asymptotical
Rust (prefix tree) 0.632 5.284 O(?)
C++ (prefix tree + heap) 4.838 38.587 O((T + N) log N)
Python (Counter) 14.366 115.855 O(T + N log Q)
AWK + sort 21.548 176.411 O(T + Q log Q)
McIlroy (tr + sort + uniq) 60.531 690.906 O(T log T)
笔记:
- T >= Q,通常 Q >> N(N 是一个小常数)
- bible32 是圣经连接 32 次 (135 MB), bible256 – 256 次 (1.1 GB)
- AWK 时间可能会因您使用的 AWK 版本(gawk、nawk 或 mawk)而有很大差异
如您所见,McIlroy 的解决方案的运行速度比已知最快的程序慢大约 100 倍!但是,他的解决方案仍然非常优雅,易于调试,而且毕竟它的性能也没有那么糟糕,除非您开始将它用于千兆字节文件。C/C++ 或 Haskell 中更复杂算法的错误实现很容易比他的管道运行得慢得多(我已经见证了这一点)。