这是一个愚蠢的问题,一直困扰着我。
我有一个100k数字列表,我正在计算一些统计数据。具体来说,我正在计算这些数字的平均值,最小值,最大值和总和。我是用折叠做的。在JavaScript中:
// define folding functions:
let mean = (a, b, index, array) => a + b / array.length
let max = (a, b) => Math.max(a, b)
let min = (a, b) => Math.min(a, b)
let sum = (a, b) => a + b
let fold = initial => f => data =>
Math.round(data.reduce(f, initial))
// functions we can consume:
let averageDistance = fold(0)(mean)
let maxDistance = fold(-Infinity)(max)
let minDistance = fold(Infinity)(min)
let totalDistance = fold(0)(sum)
// compute stats:
let data = [1, 2, 3, ...]
let a = averageDistance(data)
let b = maxDistance(data)
let c = minDistance(data)
let d = totalDistance(data)
对于每个统计信息averageDistance
,maxDistance
等,时间复杂度显然是 O(n)。对所有4个统计信息进行计算,复杂度为 O(4N)
现在,我可以在一个循环中计算所有4个统计数据,使用transducer(与Haskell的融合类似的优化),或者将eveything内联到for
循环:
let a = 0
let b = -Infinity
let c = Infinity
let d = 0
for (let i = 0; i < data.length; i++) {
a = mean(a, averageDistance(data[i]), i, data)
b = max(b, maxDistance(data[i]))
c = min(c, minDistance(data[i]))
d = sum(d, totalDistance(data[i]))
}
此解决方案只执行一个循环,因此直观地说它在 O(n)时间内完成(比以前 4n 的改进)。
但它仍然可以完成与以前相同的工作量:(100k整数)*(4次统计)= 400k计算。
一种解决方案真的比另一种快吗?空间复杂性的差异(不及时)?如果两者都没有,为什么还要使用传感器或融合?
答案 0 :(得分:1)
此功能:
Stats_A(array[1...n])
sum = 0
for i = 1 to n do
sum = sum + array[i]
avg = 0
for i = 1 to n do
avg = avg + array[i]
avg = avg / n
min = array[1]
for i = 1 to n do
if array[i] < min then
min = array[i]
max = array[1]
for i = 1 to n do
if array[i] > max then
max = array[i]
return (sum, avg, min, max)
这个功能:
Stats_B(array[1...n])
sum = 0
min = max = array[1]
for i = 1 to n do
sum = sum + array[i]
if array[i] < min then
min = array[i]
else if array[i] > max then
max = array[i]
return (sum, sum / n, min, max)
两者具有相同的线性时间复杂度O(n)
。我们可以为基本操作分配成本,并针对这些函数的时间复杂性计算出更多详细信息表达式,然后我们会发现Stats_A
比Stats_B
做更多的工作,但不是渐进式更多的工作。我们可以让:
a
b
,c
,d
,e
f
现在我们可以计算更详细的运行时表达式:
此功能:
Stats_A(array[1...n])
sum = 0 // a
for i = 1 to n do // a + n * (f + b + a)
sum = sum + array[i] // n * (2 * b + 3 * a)
// = (2 + 4 * n) * a
// + ( 3 * n) * b
// + ( 1 * n) * f
avg = 0 // a
for i = 1 to n do // a + n * (f + b + a)
avg = avg + array[i] // n * (2 * b + 3 * a)
avg = avg / n // 3 * a + e
// = (5 + 4 * n) * a
// + ( 3 * n) * b
// + ( 1 * n) * f
// + (1 ) * e
min = array[1] // 2 * a + b
for i = 1 to n do // a + n * (f + b + a)
if array[i] < min then // n * (2 * a + b + f)
min = array[i] // n * (2 * a + b)
// = (3 + 5 * n) * a
// + (1 + 3 * n) * b
// + ( 2 * n) * f
max = array[1] // 2 * a + b
for i = 1 to n do // a + n * (f + b + a)
if array[i] > max then // n * (2 * a + b + f)
max = array[i] // n * (2 * a + b)
// = (3 + 5 * n) * a
+ (1 + 3 * n) * b
+ ( 2 * n) * f
return (sum, avg, min, max) // = (5 ) * a
// total: (13 + 18 * n) * a
// + ( 2 + 12 * n) * b
// + ( 1 ) * e
// + ( 6 * n) * f
// = n * (18a + 12b + 6f) + (13a + 2b + e)
这个功能:
Stats_B(array[1...n])
sum = 0 // a
min = max = array[1] // 3a + b
for i = 1 to n do // a + n * (f + b + a)
sum = sum + array[i] // n * (3 * a + 2 * b)
if array[i] < min then // n * (b + 2 * a + f)
min = array[i] // n * (b + 2 * a)
else if array[i] > max then // n * (b + 2 * a + f)
max = array[i] // n * (b + 2 * a)
return (sum, sum / n, min, max) // 5a + e
// total: ( 9 + 12 * n) * a
+ ( 1 + 7 * n) * b
+ ( 1 ) * e
+ ( 3 * n) * f
= n * (12a + 7b + 3f) + (9a + b + e)
第一个函数比第二个函数严格要长;在极限情况下,其运行时间的比率接近其斜率的商:
(18a + 12b + 6f) / (12a + 7b + 3f)
我们可以观察到分母严格小于分子的2/3;因此,该比率严格大于3/2。我们希望,对于任何给定的输入,当Stats_A
输入大小不受约束地增加时,Stats_B
的接收量将比<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Virtual DrumPad</title>
<link rel="stylesheet" href="css\bootstrap.min.css">
<link rel="stylesheet" href="css\drumpad.css">
<link rel="prefetch" href="sounds\">
<script type="text/javascript" src="https://raw.githubusercontent.com/LeaVerou/prefixfree/gh-pages/plugins/prefixfree.jquery.js"></script>
</head>
<body>
<div class="container">
<div class="row middle">
<div class="header" style="">
<img src="images/logo.png" alt="Logo" width="300px" height="118px">
</div>
<div class="container col-xs-12 col-md-8 col-md-offset-2 buttonGroup">
<div class="row">
<div class="col-xs-12 col-sm-6 col-sm-offset-3 col-md-10 col-md-offset-1 big">
<button data-key="65" class="drumpad">A<br><sub>CLAP</sub></</button>
<button data-key="83" class="drumpad">S<br><sub>HIHAT</sub></</button>
<button data-key="68" class="drumpad">D<br><sub>KICK</sub></</button>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-6 col-sm-offset-3 col-md-10 col-md-offset-1 big">
<button data-key="70" class="drumpad">F<br><sub>OPENHAT</sub></</button>
<button data-key="71" class="drumpad">G<br><sub>BOOM</sub></</button>
<button data-key="72" class="drumpad">H<br><sub>RIDE</sub></button>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-6 col-sm-offset-3 col-md-10 col-md-offset-1 big">
<button data-key="74" class="drumpad">J<br><sub>SNARE</sub></</button>
<button data-key="75" class="drumpad">K<br><sub>TOM</sub></</button>
<button data-key="76" class="drumpad">L<br><sub>TINK</sub></button>
</div>
</div>
</div>
</div>
</div>
<script src="scripts\jquery-3.2.1.min.js"></script>
<script src="scripts\bootstrap.min.js"></script>
<script>
function playIt(e){
var keys = document.querySelector('[data-key="'+e.keyCode+'"]');
if(keys == null)return;
mainStuff(keys);
}
function mainStuff(keys){
keys.classList.add('playing');
var sname = keys.querySelector('sub').innerHTML.toLowerCase();
var audio = new Audio("sounds\\"+sname+".wav");
audio.play();
keys.addEventListener('transitionend',function(){
keys.classList.remove('playing');
});
}
document.addEventListener('keydown',playIt);
var all = document.querySelectorAll('.drumpad');
all.forEach(key => key.addEventListener('click', function(){
mainStuff(this);
}));
</script>
</body>
</html>
接近50%。