【问题标题】:Big O notation with recursion带递归的大 O 表示法
【发布时间】:2017-10-02 02:03:26
【问题描述】:

我对此代码的大 O 表示法有疑问:

int strange_sumA(int[] arr) {
     if (arr.length == 1) {
        return arr[0];
        } else {
             int newlen = arr.length/2;
             int[] arrLeft = new int[newlen];
             int[] arrRight = new int[newlen];
         for (int i=0; i<newlen; i++) {
             arrLeft[i] = arr[i];
         }
         for (int i=newlen; i<arr.length-1; i++) {
             arrRight[i-newlen] = arr[i];
         }
         return strange_sumA(arrLeft) + strange_sumA(arrRight);
    }
}

据我了解,第一个for循环是O(n/2),第二个for循环是O(n/2),使得整个第一次运行O(n)。然后,在第一次递归之后,接下来的两次递归的大 o 仍然是 O(n),因为 2[n/2] = n,下一个也是因为 4[n/4] = n。那么,这个算法的整个大 O 表示法会是 O(n^2) 吗?我认为代码会运行 N 次但我不确定

【问题讨论】:

  • 警告:对于奇数长度的输入数组,这将无法正常工作。新的“半数组”总共将有 一个 元素,第二个for 循环将超出arrRight 数组的边界
  • 请记住 O(n / 2) 和 O(n) 的含义完全相同。说 O(n / 2) 在技术上并没有错,但它有点不寻常。

标签: java big-o


【解决方案1】:

在进行运行时分析时,重要的是要考虑您要测量的是什么。例如...看起来您正在对数组中的所有数字求和。但是,您不是在迭代地执行它 - 您是在递归地执行它。因此,如果您最“昂贵”的操作(花费最多时间的步骤)是函数调用......那么您可以选择表达在函数调用中测量的运行时间。

由于您每次都将数组分成两半,因此它是对数的。

O(log n)

现在,如果您还想考虑每个数组操作。

arrLeft[i] = arr[i];

对于每个函数调用,您执行此 O(n/2) 操作两次,因此 O(n)。所以每个函数调用都有O(n)个数组操作。

O(n)

对于整体数组操作,我们必须将每个函数调用的数组操作数乘以函数调用总数。

O(n * log n) 

你也可以通过master theorm证明这一点

【讨论】:

    【解决方案2】:

    令 T(n) 为长度为 n 的数组的算法的时间复杂度。所以我们得到:

    T(n) = 2T(n/2) + O(n)

    T(n) = 2(2T(n/4) + n/2) + O(n)

    ....

    T(n) = 2^(log_2(n))T(1) + log_2(n) * O(n) = O(n) + O(n) * log_2(n) = O(n log n)

    【讨论】:

    • 我很困惑您从哪里获取日志。根据这个来源,树不是平衡的,因此应该是 O(n^2)?cs.cornell.edu/courses/cs3110/2012sp/lectures/lec20-master/…
    • @AlexJustiniano 我展示了上述所有步骤,以了解日志的来源。基本上是雪陶然做的,中间的东西都一样
    • @AlexJustiniano 它是一个平衡二叉树。对于每个级别,时间复杂度为 O(n),树的高度为 log n。因此总 T(n) 为 O(n log n)。如果您对证明感到困惑,我认为这将是理解树的更好方法。
    【解决方案3】:

    从 T(n) = 2T(n/2) + n 开始

    T(n/2) = 2T(n/4) + n/2
    

    把这个替换成原来的

    T(n) = 2[2T(n/4) + n/2] + n
    T(n) = 2^2T(n/4) + 2n
    

    再做一次

    T(n/4) = 2T(n/8) + n/4
    

    再次替换

    T(n) = 2^2[2T(n/8) + n/4] + 2n
    T(n) = 2^3T(n/8) + 3n
    

    你可以随心所欲地重复,但你最终会看到模式

    T(n) = 2^kT(n/2^k) + kn
    

    我们想要达到 T(1),所以设置 n/2^k = 1 并求解 k

    n/2^k = 1 then,
    lgn = k
    

    现在用一般形式将 lgn 替换为 k

    T(n) = 2^lgnT(1) + lgn * n
    
    T(n) = n + nlgn   
    

    nlgn 的增长速度比 n 快,因此它是主导项。因此 O(nlgn)

    【讨论】: