递归乘法的时间复杂度

huangapple go评论52阅读模式
英文:

Time complexity of recursion of multiplication

问题

这个函数的最坏情况时间复杂度(大O表示法)为O(2^n)。

你的观点:
这个函数在任何情况下都会递归调用b次,因此复杂度为O(b) = O(n)。

你朋友的观点:
由于有n位,a/b的值不会超过(2^n)-1,因此最大的调用次数将为O(2^n)。

英文:

What is the worst case time complexity (Big O notation) of the following function for positive integers?

def rec_mul(a:int, b:int) -> int:
        if b == 1:
            return a
        
        if a == 1:
            return b
        
        else:
            return a + rec_mul(a, b-1)

I think it's O(n) but my friend claims it's O(2^n)

My argument:
The function recurs at any case b times, therefor the complexity is O(b) = O(n)

His argument:
since there are n bits, a\b value can be no more than (2^n)-1,
therefor the max number of calls will be O(2^n)

答案1

得分: 2

你们两个都是对的。

如果我们不考虑加法的时间复杂度(你们可以讨论是否有理由这样做),只计算迭代的次数,那么你们两个都是对的,因为你定义:

n = b

而你的朋友定义:

n = log_2(b)

所以复杂度是 O(b) = O(2^log_2(b))

这两个定义都是有效的,都可以实际使用。你关注输入值,而你的朋友关注输入的 位数

这是一个很好的示例,说明如果不定义在这些表达式中使用的变量,那么大O表示就毫无意义。

英文:

You are both right.

If we disregard the time complexity of addition (and you might discuss whether you have reason to do so or not) and count only the number of iterations, then you are both right because you define:

n = b

and your friend defines

n = log_2(b)

so the complexity is O(b) = O(2^log_2(b)).

Both definitions are valid and both can be practical. You look at the input values, your friend at the lengths of the input, in bits.

This is a good demonstration why big-O expressions mean nothing if you don't define the variables used in those expressions.

答案2

得分: 1

你和你的朋友都可能是对的,这取决于n的具体含义。另一种说法是,你和你的朋友都是错的,因为你们都忘了具体指定n是什么。

你的函数接受两个变量ab作为输入。这些变量都是数字。如果我们将复杂性表示为这些数字的函数,它实际上是O(b log(ab)),因为它包括b次迭代,每次迭代需要将大小最多为ab的数字相加,这需要log(ab)次操作。

现在,你们两个选择将复杂性表示为n而不是ab的函数。这是可以的;我们经常这样做;但一个重要的问题是:n是什么?

有时我们认为n是“显而易见”的,所以我们忘了具体说明它。

  • 如果你选择n = max(a, b)n = a + b,那么你是对的,复杂性是O(n)
  • 如果你选择n输入的长度,那么n是表示两个数字ab所需的位数。换句话说,n = log(a) + log(b)。在这种情况下,你的朋友是对的,复杂性是O(2^n)

由于n的含义存在歧义,我会主张在没有明确说明n是什么的情况下,将复杂性表示为n的函数是没有意义的。因此,你和你的朋友都是错的。

英文:

Your friend and you can both be right, depending on what is n. Another way to say this is that your friend and you are both wrong, since you both forgot to specify what was n.

Your function takes an input that consists in two variables, a and b. These variables are numbers. If we express the complexity as a function of these numbers, it is really O(b log(ab)), because it consists in b iterations, and each iteration requires an addition of numbers of size up to ab, which takes log(ab) operations.

Now, you both chose to express the complexity in function of n rather than a or b. This is okay; we often do this; but an important question is: what is n?

Sometimes we think it's "obvious" what is n, so we forget to say it.

  • If you choose n = max(a, b) or n = a + b, then you are right, the complexity is O(n).
  • If you choose n to be the length of the input, then n is the number of bits needed to represent the two numbers a and b. In other words, n = log(a) + log(b). In that case, your friend is right, the complexity is O(2^n).

Since there is an ambiguity in the meaning of n, I would argue that it's meaningless to express the complexity as a function of n without specifying what n is. So, your friend and you are both wrong.

答案3

得分: 1

背景

一个一元编码使用大小为1的字母表:可以想象成是记数符号。如果输入是数字 a,你需要 O(a) 位。

一个二元编码使用大小为2的字母表:你会得到0和1。如果数字是 a,你需要 O(log_2 a) 位。

一个三元编码使用大小为3的字母表:你会得到0、1和2。如果数字是 a,你需要 O(log_3 a) 位。

一般来说,一个k-ary编码使用大小为k的字母表:你会得到0、1、2、... 直到 k-1。如果数字是 a,你需要 O(log_k a) 位。

这与复杂度有什么关系?

如你所知,我们在大O符号内部忽略乘法常数。n2n3n等都是O(n)

对于对数也是如此。log_2 n2 log_2 n3 log_2 n等都是O(log_2 n)

这里的关键观察是 比率 log_k1 n / log_k2 n 是一个常数,无论 k1k2 是什么... 只要它们大于 1。这意味着对所有 k1, k2 > 1,都有 f(log_k1 n) = O(log_k2 n)

这在比较算法时很重要。只要你使用一个“高效”的编码(即不是一元编码),你使用的基数并不重要:你可以简单地说 f(n) = O(lg n) 而不用指定基数。这使得我们能够比较算法的运行时,而不必担心你使用的确切编码方式。

所以 n = b(这意味着一元编码)通常是不会被使用的。二元编码是最简单的,与其他编码相比并没有提供非恒定的加速,所以我们通常只假设二元编码。

这意味着我们几乎总是假设 n = lg a + lg b 作为输入大小,而不是 n = a + b。一元编码是唯一建议线性增长而不是指数增长的编码方式,随着 ab 的值增加。


然而,有一个领域在那里一元编码被用来区分 NP完备性和 NP完备性。不深入理论,如果一个问题是NP完备的,我们不希望任何算法在使用高效编码时具有多项式运行时间,也就是说,当使用一种有效的编码时,运行时间受到 O(n**k)(其中 k 是常数)的限制。

但是一些算法如果我们允许一元编码,确实会变成多项式的。如果一个本来是NP完备的问题,在使用一元编码时变成了多项式,我们称之为一个 NP完备问题。它仍然很慢,但从某种意义上说比一个算法慢得多的地方是,数字的大小并不重要。

英文:

Background

A unary encoding of the input uses an alphabet of size 1: think tally marks. If the input is the number a, you need O(a) bits.

A binary encoding uses an alphabet of size 2: you get 0s and 1s. If the number is a, you need O(log_2 a) bits.

A trinary encoding uses an alphabet of size 3: you get 0s, 1s, and 2s. If the number is a, you need O(log_3 a) bits.

In general, a k-ary encoding uses an alphabet of size k: you get 0s, 1s, 2s, ..., and k-1s. If the number is a, you need O(log_k a) bits.

What does this have to do with complexity?

As you are aware, we ignore multiplicative constants inside big-oh notation. n, 2n, 3n, etc, are all O(n).

The same holds for logarithms. log_2 n, 2 log_2 n, 3 log_2 n, etc, are all O(log_2 n).

The key observation here is that the ratio log_k1 n / log_k2 n is a constant, no matter what k1 and k2 are... as long as they are greater than 1. That means f(log_k1 n) = O(log_k2 n) for all k1, k2 > 1.

This is important when comparing algorithms. As long as you use an "efficient" encoding (i.e., not a unary encoding), it doesn't matter what base you use: you can simply say f(n) = O(lg n) without specifying the base. This allows us to compare runtime of algorithms without worrying about the exact encoding you use.

So n = b (which implies a unary encoding) is typically never used. Binary encoding is simplest, and doesn't provide a non-constant speed-up over any other encoding, so we usually just assume binary encoding.

That means we almost always assume that n = lg a + lg b as the input size, not n = a + b. A unary encoding is the only one that suggests linear growth, rather than exponential growth, as the values of a and b increase.


One area, though, where unary encodings are used is in distinguishing between strong NP-completeness and weak NP-completeness. Without getting into the theory, if a problem is NP-complete, we don't expect any algorithm to have a polynomial running time, that is, one bounded by O(n**k) for some constant k when using an efficient encoring.

But some algorithms do become polynomial if we allow a unary encoding. If a problem that is otherwise NP-complete becomes polynomial when using an unary encoding, we call that a weakly NP-complete problem. It's still slow, but it is in some sense "faster" than an algorithm where the size of the numbers doesn't matter.

huangapple
  • 本文由 发表于 2023年2月14日 21:54:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/75448841.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定