Java - 位运算的大O表示法是什么?

9

这段代码的时间复杂度是多少?我知道除了递归部分以外,所有行的时间复杂度都是O(1)。我不确定递归的时间复杂度是多少,我有一种感觉是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);
    }
编辑:顺便说一下,这不是作业,而是为了准备面试。

1
我认为递归调用最多32次,因为此时carry==0确保了位移,使其不会再有更多位可用。这就是所谓的O(1)。 - martijnn2008
1
@jerrybibo - 不一定非得这样。你同样可以定义最佳和平均运行时间的大O表示法。 - Oliver Charlesworth
1
O(1)是一种常数复杂度,但在这种情况下@martijnn2008,因为存在一个递归方法,在最后并不总是被调用!我认为它是线性复杂度 O(n)!例如1次调用:1秒 10次:10秒等等。 - Yahya
1
@GhostCat 是的,我意识到这些问题不能在这里提问,谢谢你提醒我。我已经删除了这个问题。 - data_pi
1
我感谢您的反馈 - 以及您的积极反应。并不是每个人都会有这样的反应... - GhostCat
显示剩余4条评论
5个回答

3

这个函数的最坏情况复杂度实际上是O(n)。如上面评论所讨论的那样,大O符号指的是函数的渐近上界。该函数在最坏情况下的上界是每个输入数字都是max int。这意味着该函数被调用的次数等于一个整数中的位数。如果您的整数有32位,则该函数运行32次,每次执行一系列常量操作。这使得函数的复杂度为O(n),其中n是您的整数的大小。


我喜欢你的回答。然而,n 不能无限增长。也许问题更清晰一些,使用类似位数组的东西。 - martijnn2008
1
我们正在讨论一个理论上的算法。事实上,OP选择人为地限制输入的大小并不改变该算法随着输入的增加呈线性增长的事实。想象一下,如果你需要对一个数组进行排序,但是这个数组的大小总是相同的,那么你就不能称其为一个常数时间算法。 - Jayson Boubin
正如评论中所讨论的那样,大O符号并不一定指最坏情况分析。 - Oliver Charlesworth
1
@OliverCharlesworth 我明白你的意思。然而,我仍然相信上述描述的函数是O(n)。该函数的渐近上界基于输入大小,即使对于某些任意大小的输入,我们可能会得到恒定的运行时间。我将更新我的答案以涉及我们正在查看的大O最坏情况,但我认为说这个函数通常是O(1)违背了这种类型分析的精神。例如,插入排序的大O最佳情况是n,但没有人认为插入排序是一个O(n)算法。 - Jayson Boubin
@JaysonBoubin "函数基于输入大小而定"指的是什么大小?n又是什么?由于大O描述了“n”与时间(在这种情况下)之间的关系,那么输入a和/或b与递归次数之间的关联是什么?没有关联 - Andreas
显示剩余4条评论

2
当你试图分析递归算法的复杂性时,通常有助于找到某种方式使输入的“大小”随时间降低。例如,如果你正在分析递归阶乘实现,则可能会注意到每次调用时输入的大小减少了一个,然后使用它来说总共有O(n)个调用,因此总共进行了O(n)的工作。
这个递归函数有点棘手,因为不清楚哪个数量会随时间而减少。第一个和第二个参数的值都可以随着函数运行而增加,这通常在使用递归时不是好事情!
然而,这里有一个有用的观察结果。看看递归函数的第二个参数的值。它每次重新计算为:
b = (a & b) << 1

该函数有一个非常有趣的效果:二进制表示中末尾的0位数在每次函数调用时至少增加1。为了看清这一点,假设b以k个0位结尾。计算 a & b 将产生一个最后k位为零的数字,然后将结果向右移动一步将使最右边的0的数量增加1。
同时,当二进制表示中b的最右边的0的数量超过a的二进制表示中最左边的1的位置时,该算法将终止。要理解这一点,请注意我们不断将b与a进行AND运算,只要b中的1位变得高于a中的1位,AND运算就会评估为0并触发基本情况。
将这些放在一起,我们可以看到递归调用的数量受到数字a中最高1位位置的限制,因为每次迭代都将b中的1位向更高位置移动,当这些1位移动到a中的1位之后,递归停止。
因此,现在的问题是从大O的角度来看这意味着什么。如果您有一个数字n并将其写成二进制形式,则n中最高1位的位置是O(log n),因为该位的含义随时间呈指数增长。因此,这里的运行时间为O(log n),其中n是两个输入中的最大值。

1
如果我们分析您的函数,我们会发现以下内容:
  • If one of the two integers passed to it is a zero -> it will be invoked one time only by the return statement at the beginning.
  • If the integers passed are similar even if they are not zeros -> it will be invoked one time only. That is because of the XOR or ^ which requires the two corresponding bits to be different. For example:

    (decimal)    (binary)              (decimal)    (binary)
        5     =    101                     1     =    001
        6     =    110                     1     =    001
    **********************   Whereas    *********************
        3     =    011                     0     =    000
    
  • If the integers passed are not zeros and not similar -> that will take the program to the carry part and here we need to consider the following:

    1. If the two integers have no similar corresponding bits -> (a&b) will equals zero, thus the method will be invoked only one time. For example:

      (decimal)    (binary)               (decimal)    (binary)
          1     =    001                      1     =    001
          2     =    010                      3     =    011
       **********************   Whereas   *********************
          0     =    000                      1     =    001
      
    2. If (a&b) results with no zero -> here we come to the shift part << and here is the plot. If ALL the above conditions pass -> then we have a maximum of 32 bit shift and supposing that after each shift the recursion happens -> we have a maximum of 32 recursion BUT NOT always. That means it could be not the case every time (sometimes there would be no recursion as I showed above!).


因此,我认为它要么是O(1),要么是O(n),如果我们分析这两种情况,我们会发现:

  • O(1) means Constant complexity, for example:

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

    But that is not the case as I showed above, because every input may cause a recursion and may not! And the recursion is not the same, although the maximum is 32 but it can be between 0 to 32.

  • O(n) means a Linear complexity, for example:

    1 invocation  : 1 second
    10 invocation : 10 seconds
    100 invocation: 100 seconds
    And so on..
    
我得出结论:递归发生的次数越多,所需时间就越长。虽然,我仍然愿意接受更正。

我的边界与您提出的不同。您能解释一下为什么它显然是O(1)或O(n)吗? - templatetypedef
@templatetypedef 我同意,答案是 *O(log n)*。 - Andreas
1
在大O符号中,缩放因子被忽略。N是输入的长度,而不是它的值。答案不正确。 - user207421

1

首先让我引用Wikipedia的话:

大O符号是一种数学符号,描述当参数趋近于特定值或无穷大时函数的极限行为。

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

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

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

答案是否定的。a或b的大小与递归次数之间没有相关性。当然,递归次数可能会有所不同,但与大小无关。

无论是a还是b,您都具有Omin(1),Oavg(6.24)和Omax(33)。


更新

然而,当ab的值较低时,您无法获得33次迭代。迭代次数受限于更大输入的位长度。位长度将是log2(n),这给出了答案:

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


1
到目前为止,这是唯一正确的答案。这完全取决于N的定义。它是输入的长度,而不是其值。 - user207421
1
O<sub>max</sub>(33) 相当于 O(1),因为这种表示法忽略常数。 - SpaceTrucker

0

我会将n解释为值b中最右边的1位及其左侧的位数。随着n的增加,递归迭代的可能次数也会增加。因此,复杂度为O(n)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接