计算整数列表的均值,最小值,最大值,总和 - 复杂度是多少?

时间:2017-11-03 19:01:46

标签: javascript haskell time-complexity computer-science complexity-theory

这是一个愚蠢的问题,一直困扰着我。

我有一个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)

对于每个统计信息averageDistancemaxDistance等,时间复杂度显然是 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计算。

一种解决方案真的比另一种快吗?空间复杂性的差异(不及时)?如果两者都没有,为什么还要使用传感器或融合?

1 个答案:

答案 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_AStats_B做更多的工作,但不是渐进式更多的工作。我们可以让:

  1. 内存访问(读/写)需要时间a
  2. 分别为
  3. +, - ,*,/花时间bcde
  4. 比较(&lt;,&gt;,=)需要时间f
  5. 现在我们可以计算更详细的运行时表达式:

    此功能:

    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%。