我必须将已经排序的各种集合合并到一个已排序的集合中。这个操作需要针对非常大的集合(大约5个元素)和不多的源(可能大约10个)进行,但输出应该快速计算(非阻塞服务器)。
对于两个源集合,它非常简单,但是当源集合的数量增加时,需要考虑不同的策略(n
源集合,每个集合都有m
元素:
O(n*m*log(n*m))
复杂度(快速排序n*m
元素)。O(n*n*m)
复杂度(扫描n
头n*m
次,即元素总数。)O(log(n)*n*m)
复杂度(做log(n)
合并阶段,每个阶段都包含所有n*m
元素。)O(log(n)*n*m)
(堆删除和插入操作是log(n)
,它们完成了n*m
次。因此,进行后续二进制合并的计算复杂度(如果我没有犯任何错误)和每次更复杂的基于堆的min元素选择似乎同样好。在内存方面,堆可能更加垃圾收集器友好,因为它不需要那么多的中间临时集合。
我是否错过了一些错误(错误计算复杂性或错过任何替代方法)?我的实现最有可能在Javascript或Scala中完成,因此欢迎任何可能适用于这些执行环境的运行时问题!
BTW这是不相关:Most efficient way to merge collections preserving order?
答案 0 :(得分:3)
由于您的数据集非常小,您的渐近考虑因素几乎无关紧要(尽管正确),因为更重要的因素是微优化。我建议你至少比较以下几个选项,以增加实现它们所需的工作量:
O(k)
。对于5个大小为10的列表,k
的最坏情况是1000.但实际上它会少得多。你甚至可以防止最坏的情况,例如通过按照增加第一个元素的顺序预先排序列表或者只是随机化顺序。这些是一般性建议。根据您的数据集,可能会有一个更好的定制选项。例如,如果输入的数字足够小,则使用计数排序。也许使用基数排序来获得更大的数字,但这可能不会比插入排序提供更多的速度。如果您在输入中有任何模式,请利用它。
答案 1 :(得分:2)
<强> TL;博士强>
合并。
完整版
没有什么比测试更好的了。
假设您的基础集合是List
。我们会将它们存储在Array
。
val test = Array(
List("alpha", "beta", "gamma", "delta", "epsilon"),
List("one", "two", "three", "four", "five", "six"),
List("baby", "child", "adult", "senior"),
List("red", "yellow", "green"),
List("red", "orange", "yellow", "green", "blue", "indigo", "violet"),
List("tabby", "siamese", "manx", "persian"),
List("collie", "boxer", "bulldog", "rottweiler", "poodle", "terrier"),
List("budgie", "cockatiel", "macaw", "galah", "cockatoo"),
List("Alabama", "California", "Georgia", "Maine", "Texas", "Vermont", "Wyoming"),
List("I", "have", "to", "merge", "into")
).map(_.sorted)
然后你最初的想法可能只是扁平化和排序。
scala> val ans = th.pbench{ test.flatten.sorted.toList }
Benchmark (20460 calls in 183.2 ms)
Time: 8.246 us 95% CI 8.141 us - 8.351 us (n=18)
Garbage: 390.6 ns (n=1 sweeps measured)
ans: List[String] = List(Alabama, California, Georgia, I, Maine, ...)
或者您可以实现自定义展平和排序:
def flat(ss: Array[List[String]], i0: Int, i1: Int): Array[String] = {
var n = 0
var i = i0
while (i < i1) {
n += ss(i).length
i += 1
}
val a = new Array[String](n)
var j = 0
i = i0
while (i < i1) {
var s = ss(i)
while (s ne Nil) {
a(j) = s.head
j += 1
s = s.tail
}
i += 1
}
a
}
def mrg(ss: Array[List[String]]): List[String] = {
val a = flat(ss, 0, ss.length)
java.util.Arrays.sort(a, new java.util.Comparator[String]{
def compare(x: String, y: String) = x.compare(y)
})
a.toList
}
scala> val ans = th.pbench{ mrg(test) }
Benchmark (20460 calls in 151.7 ms)
Time: 6.883 us 95% CI 6.850 us - 6.915 us (n=18)
Garbage: 293.0 ns (n=1 sweeps measured)
ans: List[String] = List(Alabama, California, Georgia, I, Maine, ...)
或自定义成对合并
def mer(s1: List[String], s2: List[String]): List[String] = {
var s3 = List.newBuilder[String]
var i1 = s1
var i2 = s2
while (true) {
if (i2.head < i1.head) {
s3 += i2.head
i2 = i2.tail
if (i2 eq Nil) {
do {
s3 += i1.head
i1 = i1.tail
} while (i1 ne Nil)
return s3.result
}
}
else {
s3 += i1.head
i1 = i1.tail
if (i1 eq Nil) {
do {
s3 += i2.head
i2 = i2.tail
} while (i2 ne Nil)
return s3.result
}
}
}
Nil // Should never get here
}
然后是分而治之的策略
def mge(ss: Array[List[String]]): List[String] = {
var n = ss.length
val a = java.util.Arrays.copyOf(ss, ss.length)
while (n > 1) {
var i,j = 0
while (i < n) {
a(j) = if (i+1 < n) mer(a(i), a(i+1)) else a(i)
i += 2
j += 1
}
n = j
}
a(0)
}
然后你看
scala> val ans = th.pbench{ mge(test) }
Benchmark (40940 calls in 141.1 ms)
Time: 2.806 us 95% CI 2.731 us - 2.882 us (n=19)
Garbage: 146.5 ns (n=1 sweeps measured)
ans: List[String] = List(Alabama, California, Georgia, I, Maine, ...)
所以,你去吧。对于您指定的大小的数据,以及使用列表(非常干净地合并),一个好的赌注确实是分而治之的合并。 (堆可能不会更好,可能会更糟,因为维护堆的额外复杂性;出于同样的原因,heapsort往往比mergesort慢。)
(注意:th.pbench
是对我的微基准测试实用程序Thyme
的调用。)
其他一些建议涉及插入排序:
def inst(xs: Array[String]): Array[String] = {
var i = 1
while (i < xs.length) {
val x = xs(i)
var j = i
while (j > 0 && xs(j-1) > x) {
xs(j) = xs(j-1)
j -= 1
}
xs(j) = x
i += 1
}
xs
}
但是这些与合并排序没有竞争力,只需一次扫描:
scala> val ans = th.pbench( inst(flat(test, 0, test.length)).toList )
Benchmark (20460 calls in 139.2 ms)
Time: 6.601 us 95% CI 6.414 us - 6.788 us (n=19)
Garbage: 293.0 ns (n=1 sweeps measured)
ans: List[String] = List(Alabama, California, Georgia, I, Maine, ...)
或两个:
scala> th.pbench( mer(inst(flat(test, 0, test.length/2)).toList,
inst(flat(test, test.length/2,test.length)).toList) )
Benchmark (20460 calls in 119.3 ms)
Time: 5.407 us 95% CI 5.244 us - 5.570 us (n=20)
Garbage: 390.6 ns (n=1 sweeps measured)
res25: List[String] = List(Alabama, California, Georgia, I, Maine,