【问题标题】:Java - Big O of bit manipulation?Java - 位操作的大 O?
【发布时间】:2017-11-12 01:48:09
【问题描述】:

这段代码的大 O 是多少?我知道所有的行都是 O(1),除了递归部分。我不确定递归的大 O 是多少,我感觉它仍然是 O(1),因为我们没有比 O(1) 更糟糕的行,但通常递归是 O(n)。

代码:

public int getSum(int a, int b) {

        if (b == 0){
            return a;
        }

        if (a == 0){
            return b;
        }

        int add = a ^ b;
        int carry = (a&b) << 1;

        return getSum(add, carry);
    }

编辑:顺便说一句,这不是作业,是为面试做准备。

【问题讨论】:

  • 我认为递归调用最多 32 次,因为肯定比carry == 0,因为位移位你用完了位。可以这么说。因此 O(1)。
  • @jerrybibo - 不必如此。您也可以定义大 O 以获得最佳和平均运行时间。
  • O(1) 是一个恒定复杂度,在这种情况下怎么可能是@martijnn2008,因为最后有一个递归方法,不是每次都调用!我认为这是一个 线性复杂度 O(n)!例如 1 次调用:1 秒 10 次:10 秒 等等。
  • @GhostCat 是的,我意识到这些问题不能在这里问,谢谢你让我注意到这一点。我删除了这个问题。
  • 感谢您的反馈以及您的积极反应。不是每个人都会在这样的庄园里做出反应......

标签: java recursion big-o


【解决方案1】:

这个函数实际上是O(n) 最坏的情况。正如上面的 cmets 中所讨论的,大 O 表示法引用了函数的渐近上限。在最坏的情况下,此函数的上限是您的每个输入数字都是max int。这意味着该函数被调用的次数等于整数中的位数。如果您的整数有32 bits,则函数运行32 次​​em>,每次执行一系列常量操作。这使得函数O(n),其中n 是您的整数的大小

【讨论】:

  • 我喜欢你的回答。但是,n 不能无限增长。也许使用 bitarray 之类的问题会更清楚。
  • 正在讨论一种算法,这是理论上的。 OP 选择人为地限制输入大小的事实并没有改变算法随输入线性增长的事实。想象一下,您需要对数组进行排序,但数组的大小始终相同。然后你就不会称排序为常数时间算法了。
  • 正如 cmets 中所讨论的,大 O 表示法不一定指最坏情况分析。
  • @olivercharlesworth,我认为您将大 O 表示法与 Bachmann-Landau 表示法的其他元素混淆了。虽然其中一些术语有多个或有争议的定义(例如 Omega),但我使用这些符号的 CLRS 定义,它将大 O 分类为函数的渐近上限。为简洁起见,我将“渐近上限”与“最坏情况”互换使用。
  • @OliverCharlesworth 我明白你在说什么。然而,我仍然相信,上面描述的函数是 O(n)。函数的渐近上限是基于输入大小的,即使对于任何大小的某些输入,我们可能会得到一个恒定的运行时间。我将更新我的答案以说明我们正在研究大 O 最坏情况这一事实,但我认为说这个函数通常是 O(1) 是违反这种分析的精神的。例如,插入排序的大 O 最佳情况是 n,但没有人认为插入排序是 O(n) 算法。
【解决方案2】:

如果我们分析您的函数,我们会发现以下内容:

  • 如果传递给它的两个整数之一是 -> 它将由 return 语句调用 一次开始。
  • 如果传递的整数是相似,即使它们不是零 -> 它将被调用一次。这是因为XOR^ 要求两个对应的位不同。例如:

    (decimal)    (binary)              (decimal)    (binary)
        5     =    101                     1     =    001
        6     =    110                     1     =    001
    **********************   Whereas    *********************
        3     =    011                     0     =    000
    
  • 如果传递的整数是 not 零并且 not 相似 -> 这会将程序带到 carry 部分,这里我们需要考虑以下几点:

    1. 如果两个整数有没有相似的对应位->(a&amp;b)将等于,因此该方法将被调用一次。例如:

      (decimal)    (binary)               (decimal)    (binary)
          1     =    001                      1     =    001
          2     =    010                      3     =    011
       **********************   Whereas   *********************
          0     =    000                      1     =    001
      
    2. 如果 (a&amp;b) 结果不为零 -> 我们来到 shift 部分 &lt;&lt; 这是情节。如果 ALL 上述条件通过 -> 那么我们最多有 32 个位移,并假设在每个 位移 递归发生 -> 我们总是有最多 32 个递归但不是。这意味着它可能并非每次都如此(有时不会像我上面展示的那样递归!)。


所以,我认为是O(1)O(n),如果我们分析这两种情况,我们会发现:

  • O(1) 表示恒定复杂度,例如:

    1 invocation  : 1 second
    10 invocation : 1 second
    100 invocation: 1 second
    And so on..
    

    但我上面展示的情况并非如此,因为每个输入可能会导致递归,而可能不会!而且递归也不一样,虽然最大值是32,但是可以在0到32之间

  • O(n)表示线性复杂度,例如:

    1 invocation  : 1 second
    10 invocation : 10 seconds
    100 invocation: 100 seconds
    And so on..
    

得出结论递归发生的次数越多,花费的时间就越多

不过,我仍然愿意纠正。

【讨论】:

  • 我有一个与你建议的不同的界限。你能解释一下为什么它显然是 O(1) 或 O(n) 吗?
  • @templatetypedef 我同意,答案是 O(log n)
  • 在 big-O 表示法中忽略比例因子。 N 是输入的长度,而不是它的值。答案不正确。
【解决方案3】:

当您尝试分析递归算法的复杂性时,通常有助于找到输入“大小”随时间下降的某种方式。例如,如果您正在分析递归阶乘实现,那么您可能会注意到输入的大小在每次调用时都会下降 1,然后用它来表示总共有 O(n) 个调用,因此 O (n) 全部工作都完成了。

这个递归函数分析起来有点棘手,因为根本不清楚什么数量会随着时间的推移而减少。第一个和第二个参数的值都可以随着函数的运行而增加,这在使用递归时通常不是一件好事!

但是,这里有一个有用的观察。查看递归函数的第二个参数的值。每次都重新计算为

b = (a & b) << 1

这有一个非常有趣的效果:在每个函数调用中,b 的二进制表示末尾的 0 位的数量至少增加了一位。要看到这一点,假设 b 以 k 0 位结尾。计算 a &amp; b 会产生一个最后 k 位为零的数,然后将结果右移一位将使最右边的 0 的数量增加 1。

同时,当 b 的二进制表示中最右边的 0 的数量超过 a 的二进制表示中最左边的 1 的位置时,算法将终止。要了解原因,请注意我们保持 b 与 a 的 AND 运算,一旦 b 中的 1 位高于 a 中的 1 位,AND 的计算结果为 0 并且基本情况触发。

综上所述,我们可以看到,递归调用的数量受数字 a 中最高 1 位的位置的限制,因为每次迭代都会将 b 中的 1 位越来越高,并且当那些1s 超过 a 中的 1s。

所以现在的问题是从大 O 的角度来看这意味着什么。如果您有一个数字 n 并以二进制形式写入 n,则 n 中最高 1 位的位置为 O(log n),因为该位的含义随时间呈指数增长。因此,这里的运行时间为 O(log n),其中 n 是两个输入中的最大值。

【讨论】:

    【解决方案4】:

    首先让我引用Wikipedia

    Big O 表示法是一种数学表示法,它描述了当参数趋于特定值或无穷大时函数的限制行为。

    在计算机科学中,大 O 表示法用于根据算法的运行时间或空间需求如何随着输入大小的增长而增长。

    你问性能是否O(n)。好吧,在我们回答这个问题之前,n 是什么?

    正如您在上面看到的,n 通常定义为输入的大小。虽然这通常是指输入的数量(计数),但在这种情况下,可以定义为ab数量,所以问题是:处理时间(即递归次数)是否随着a 的增加和/或b 的增加而增加?

    答案是否定的。 ab幅度 与递归次数之间没有相关性。当然,递归的数量会有所不同,但它与 size 无关。

    无论na 还是b,您都有Omin(1)、Oavg(6.24) 和Omax (33).


    更新

    但是,ab 的低值无法获得 33 次迭代。迭代次数受较大输入的位长限制。位长为 log2(n),给出的答案是:

    O(log n) 其中n = max(a, b)

    【讨论】:

    • 这是迄今为止唯一正确的答案。这一切都归结为 N. 的定义,即输入的长度。不是它的价值,
    • O&lt;sub&gt;max&lt;/sub&gt;(33) 等价于 O(1),因为这种表示法忽略了常量。
    【解决方案5】:

    我会将n 解释为值b 左侧(包括)最右侧1 位的位数。随着n 的增长,可能的递归迭代次数也在增长。鉴于此,复杂度为 O(n)。

    【讨论】:

      猜你喜欢
      • 2014-04-21
      • 2012-04-19
      • 1970-01-01
      • 1970-01-01
      • 2011-12-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-04
      相关资源
      最近更新 更多