这个问题的灵感来自我昨天正在处理的答案。
假设我们有N个输入,评估为真或假, 什么是确定这些输入中的X是否最有效的方法 真正?
注意事项:
- 输入不在数组中,因此如果将它们转换为数组,请考虑任何管理费用。
- “效率最高”我指的是最佳平均情况(尽管我也希望看到最佳和最差情况统计数据)。
醇>
以下是我昨天遇到的两种方法。
1)将变量视为电路的布尔输入,并使用K-map减少它们
起初我认为这将是最有效的手段,因为它遵循电路逻辑,但我肯定有第二个想法。随着输入数量的增加,比较次数呈指数增长
2 inputs:
1 of 2: if(1 OR 2)
2 of 2: if(1 AND 2)
3 inputs:
1 of 3: if(1 OR 2 OR 3)
2 of 3: if((1 AND 2) OR (1 AND 3) OR (2 AND 3))
3 of 3: if(1 AND 2 AND 3)
4 inputs:
1 of 4: if(1 OR 2 OR 3 OR 4)
2 of 4: if((1 AND 2) OR (1 AND 3) OR (1 AND 4) OR (2 AND 3) OR (2 AND 4) OR (3 AND 4))
3 of 4: if((1 AND 2 AND 3) OR (1 AND 2 AND 4) OR (1 AND 3 AND 4) OR (2 AND 3 AND 4))
4 of 4: if(1 AND 2 AND 3 AND 4)
... etc. ...
最好的情况很好(O(1)
),但最糟糕的情况远不如......
2)计数器和顺序if语句
这总是在O(n)
时间内完成,这没关系,但我希望有更好的最佳案例。
counter = 0
if(input 1)
counter++
if(input 2)
counter++
if(input 3)
counter++
... etc. ...
if(counter >= X)
// true
什么是比其中任何一种更有效的解决方案?
答案 0 :(得分:15)
关于问题的复杂性
由于请求完全计数(而不是询问至少 x输入是否已开启),问题非常清楚O(n)
:
我们当然可以实现次优算法,例如,在处理每个输入时[不必要地]访问所有其他输入,使其成为O(n ^ 2)实现,但这当然是愚蠢的。 / p>
这被断言,因此问题可能是......左右
可以加快实施速度的技巧
应该注意的是,虽然可能存在这样的技巧,但算法/问题的复杂性仍然固执地存在于O(n)。
技巧1:更好的输入存储
不幸的是,问题表明输入来自命名变量,并且为了评估算法的整体性能,必须考虑输入的任何转换的成本[为了允许更快的计数]。虽然这最终取决于底层语言,运行时等,但考虑到转换成本的需要很可能会使基于备用存储的任何算法都比使输入保持原样的解决方案更慢。
技巧2:使评估短路
我们的想法是尽快(或不久之后)将false
作为
这个技巧相对简单,但计算早期退出测试所需值的额外成本可能会抵消[静态]早期退出所带来的收益。
技巧3:使用反逻辑:计算关闭的输入数量,而不是这些输入数量。 (或统计两者) 这种方法的成本/收益取决于要测试的输入数量(问题的X)和我们可能对输入的统计先验(在给定时间相对均匀的输入数量)分散或者我们倾向于只有少量输入(或关闭))。
solution proposed by Chris Acheson提供了使用Trick 2和Trick 3的基线。假设我们可以对输入状态的分布做出一些假设,那么这个基线的额外性能改进就会被推动。 “priors”:在计算输入本身之前完成的一些快速启发式算法将决定我们应该计算哪个州(开启或关闭或两者),哪个限制我们应该测试等等并分支到相应的“版本”算法。
如果我们知道给定输入的开启或关闭的个别概率,也可以获得额外的增益,因为我们首先测试最多(或最少)可能的输入,以快速达到我们的“短路”值”。
关于这些优化算法的最佳情况/更糟糕的“复杂性”
假设
技巧#2和#3的组合平均可以是O(X/2)
(我需要做数学计算,但这似乎是正确的)。但是我觉得用number of operations
相对于X和/或n来说话更明智,而不是滥用O符号......
假设所有操作大致产生相同的成本
计算给定算法完成所需操作的总数更容易,也更准确,因此可以使用此类计数,用于各种最佳/更差/平均情况,以帮助确定特定算法。
为了说明这一点,一个简单的实现只是系统地计算所有on-input并且只比较最后的计数器,将具有O(n)复杂度并且在大约1 + 2 * n + 1个操作的所有情况下都是完整的。这样的算法可以证明是整体的,比一个更好的算法更好,在最佳,平均和更差的情况下,分别是O(X),O((X + n)/ 2)和O(n)。 ,在这些相同的情况下,可以使用X * 3,(X + n)* 1.5和n * 3操作。
答案 1 :(得分:11)
对于接近零或N的X值,此版本将有效:
true_counter = 0
false_counter = 0
max_false = N - X
if(input 1)
true_counter++
if(counter >= X)
return true
else
false_counter++
if(false_counter > max_false)
return false
// and so on
答案 2 :(得分:4)
计算真实是最快的方法。你自己的计数器将允许超过X为真,但问题暗示你想要一个特定的值 - 不是什么大不了,但如果你想要至少10(但更多是可以的)那么你可以在每次增加后检查计数器反击并早早中止。
另一方面,如果将标志打包成一个单词,则有更快的方法来计算1。最后,计算1是要走的路。 BT中的C False为零,True为1,所以你可以将它们加在一起计算真实数。
答案 3 :(得分:4)
在编程语言中,布尔值只是signed char
,值为1或0,我们可以这样做:
if (input1 + input2 + input3 + input4 + ... + inputX > n)
答案 4 :(得分:3)
对于一般情况,不存在次线性算法:您必须查看每个输入。这样计算就好了。
平均案例运行时间取决于您的平均案例。克里斯的想法是,一旦计数确定结果就会停止计数,这在很多情况下会有所帮助。
除此之外,它真正归结为适当的数据结构。您询问了位域:http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetNaive
答案 5 :(得分:2)
mjv给出的答案非常全面地介绍了顺序算法,但由于您对电路逻辑的引用,我觉得并行和物理算法也需要轻松解决。
如果您有数字电路(或单元处理器)的N个输入,您可以通过递归添加对并传播结果,在O(log n)时间内对它们进行计数:
[1, 1, 0, 1, 1, 1, 0, 1] -> [(1+1), (0+1), (1+1), (0+1)] -> [(2+1), (2+1)] -> [(3+3)]
这为我们添加了N个,但是它们可以并行化为log(2,N)代(假设你有足够的处理器/加法器来同时运行N / 2个操作......)
这种算法有一些变化可以利用阈值问题的要求,但除非预期阈值非常低(例如,14000个输入中有10个),否则它们大多数都不值得。
答案 6 :(得分:0)
import Data.List (foldl')
main :: IO ()
xoutofn :: Num a => (a1 -> Bool) -> [a1] -> a
xoutofn pred ns = foldl' test 0 (map pred ns) where
test x (True) = x+1
test x (False) = x
main = print $ xoutofn predicate [1 .. 1000] where
predicate x = x > 500
一旦找到x,就可以做上面的人做的短路,但是我喜欢这个版本的简单性。
运行时间为O(n),因为根据需要将列表中的每个项目转换为布尔值(因为懒惰),所以它只需要遍历列表一次。
$ time ./xoutofn
500
real 0m0.003s
user 0m0.000s
sys 0m0.000s