【问题标题】:What is the time complexity of this multiplication algorithm?这个乘法算法的时间复杂度是多少?
【发布时间】:2012-03-23 18:01:56
【问题描述】:

对于经典的面试问题“如何在没有乘法运算符的情况下执行整数乘法?”,最简单的答案当然是 C 中的以下线性时间算法:

int mult(int multiplicand, int multiplier)
{
    for (int i = 1; i < multiplier; i++)
    {
        multiplicand += multiplicand;
    }

    return multiplicand;
}

当然,还有更快的算法。如果我们利用向左移位相当于乘以 2 的移位次数的幂的性质,我们可以向上移位到最接近的 2 的幂,并使用我们之前的算法相加从那里。所以,我们的代码现在看起来像这样:

#include <math.h>

int log2( double n )
{
    return log(n) / log(2);
}

int mult(int multiplicand, int multiplier)
{
    int nearest_power = 2 ^ (floor(log2(multiplier)));
    multiplicand << nearest_power;
    for (int i = nearest_power; i < multiplier; i++)
    {
        multiplicand += multiplicand;
    }

    return multiplicand;
}

我无法确定该算法的时间复杂度。我不相信O(n - 2^(floor(log2(n)))) 是表达这一点的正确方式,尽管(我认为?)它在技术上是正确的。谁能提供一些关于这方面的见解?

【问题讨论】:

  • multiplicand += multiplicand;
  • 已修复 - 感谢您指出这一点。
  • 你的第一个算法不是乘法算法,但是结果是multiplicant * pow(2,multiplier),不是吗?
  • 啊,一个错误...感谢您指出,已修复。
  • @HerroRygar - 值得注意的是,这两个仅适用于正乘数(我的位移和加法也有这个问题)。也不是log2() 和移位乘法一样昂贵吗?

标签: c performance complexity-theory big-o


【解决方案1】:

mulitplier - nearest_power 可以大到multiplier 的一半,并且因为它趋向于无穷大,所以常量0.5 没有关系(更不用说我们去掉了 Big O 中的常量)。因此循环是O(multiplier)。我不确定位移位。

编辑:我对位移进行了更多的研究。正如 gbulmer 所说,它可以是O(n),其中n 是移位的位数。但是,在某些架构上也可以是O(1)。见:Is bit shifting O(1) or O(n)?

但是,在这种情况下没关系! n &gt; log2(n) 表示所有有效的 n。所以我们有O(n) + O(multiplier),它是O(2*multiplier)的子集,由于上述关系,因此整个算法是O(multiplier)

【讨论】:

  • 位移是 O(log2(multiplier)) 或 O(log2(min(multiplier,multiplicand))),看我的回答
  • 1 位的位移是 O(1)(在我知道的所有现代机器上),恕我直言,因为这个问题与 n 位移可能是 O(1) 无关。乘法需要 n 个 1 位移位,其中 n 实际上是最高设置位,为简单起见,它是乘法器中的位数。总结 移动任意数量的位置可能是 O(1) 并不重要,因为我们只需要 1 位移位,但我们需要其中的 n 个乘以 n 位乘数,因为我们需要能够做一个每次轮班后添加。
【解决方案2】:

找到最近的幂是为了让你的函数运行时可以接近运行时 O(1)。当 2^nearest_power 非常接近您的加法结果时,就会发生这种情况。

在幕后,整个“2 次方”都是通过位移来完成的。

因此,为了回答您的问题,您的代码的第二个版本仍然是更糟糕的线性时间:O(乘数)。
您的答案 O(n - 2^(floor(log2(n)))) 也不正确;它非常精确,可能很难在您的脑海中快速找到界限。

【讨论】:

  • 那么,我所看到的只是最佳情况下的常数时间、平均情况和最坏情况下的线性时间?
  • 您可以查看最佳、平均和最差情况;您还可以查看由大 O 等符号给出的渐近边界。这取决于您想看到的内容。在 wikipedia 上查找大 O 表示法、小 O、theta 表示法以及大小欧米茄。大O是最常用的:en.wikipedia.org/wiki/Big_O_notation
  • 问题中用于导出 2 的最接近幂的技术比 O(log2(n)) 差,因此 O(1) 被淹没了。然后是 O(n) 加法 - (x-1 和 x/2) 之间的距离是 x/4,仍然是 O(x),但具有更小的常数
【解决方案3】:

编辑

让我们看看第二个贴出的算法,从:

int nearest_power = 2 ^ (floor(log2(multiplier)));

我相信计算 log2 的结果是 O(log2(multiplier))

然后nearest_power 得到区间[multiplier/2 to multiplier],其大小为multiplier/2。这与查找正数的最高设置位相同。

所以for循环是O(multiplier/2),出来1/2的常数,所以是O(n)

平均而言,它是间隔的一半,即 O(multiplier/4)。但这只是常数1/4 * n,所以还是O(n),常数更小了还是O(n)。

更快的算法。

我们的直觉是我们可以在 n 步中乘以一个 n 位数字

在二进制中,这是使用 1 位移位、1 位测试和二进制加法来构建整个答案。这些操作中的每一个都是 O(1)。这是长乘法,一次一位数。

如果我们对 n 使用 O(1) 操作,一个 x 位数,它是 O(log2(n)) 或 O(x),其中 x 是数字中的位数

这是一个 O(log2(n)) 算法:

int mult(int multiplicand, int multiplier) {
    int product = 0;

    while (multiplier) {
        if (multiplier & 1) product += multiplicand;
        multiplicand <<= 1;
        multiplier >>= 1;
    }

    return product;
}

这基本上就是我们进行长乘法的方式。

当然,明智的做法是使用较小的数字作为乘数。 (我将把它作为练习留给读者:-)

这仅适用于正值,但通过测试和记住输入的符号,对正值进行操作,然后调整符号,它适用于所有数字。

【讨论】:

  • 你有这个算法示例的链接吗?
  • 当然,虽然我认为你可能会有所收获。 =)
  • @HerroRygar - 我已经发布了算法
猜你喜欢
  • 2015-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-03
  • 2016-10-30
相关资源
最近更新 更多