有效的方法来乘以一大组小数

时间:2014-04-15 17:50:13

标签: algorithm multiplication

在接受采访时提到了这个问题。

你有一个小整数数组。你必须倍增所有这些。你不必担心溢出,你有足够的支持。你能做些什么来加速机器上的乘法运算?

在这种情况下,多次添加会更好吗?

我建议使用分而治之的方法进行乘法,但面试官并没有留下深刻的印象。什么是最好的解决方案?

5 个答案:

答案 0 :(得分:4)

以下是一些想法:

使用多线程进行分治:将输入拆分为n个不同的大小为b的块,并递归地将每个块中的所有数字相乘。然后,递归地将所有n / b块再次相乘。如果你有多个内核并且可以并行运行部分内核,那么你可以节省大量的时间。

字级并行:我们假设你的数字都是从上面被一些数字U限制,这恰好是2的幂。现在,假设您想要将a,b,c和d相乘。通过计算开始(4U 2 a + b)×(4U 2 c + d)= 16U 4 ac + 4U 2 ad + 4U 2 bc + bd。现在,请注意这个表达式mod U 2 只是bd。 (由于bd< U 2 ,我们不需要担心mod U 2 步骤弄乱了它)。这意味着,如果我们计算这个产品并采用mod U 2 ,我们就会回到bd。由于U 2 是2的幂,因此可以使用位掩码来完成。

接下来,请注意

  

4U 2 ad + 4U 2 bc + bd< 4U 4 + 4U 4 + U 2 < 9U 4 < 16U 4

这意味着如果我们将整个表达式除以16U 4 并向下舍入,我们最终会收回广告。这种除法可以通过比特移位完成,因为16U 4 是2的幂。

因此,通过一次乘法,您可以通过应用后续的bitshift和bitmask来获取ac和bd的值。一旦你有了ac和bd,你可以直接将它们相乘以获得abcd的值。假设位掩码和位移比乘法更快,这会将所需的乘法次数减少33%(这里有两次而不是三次)。

希望这有帮助!

答案 1 :(得分:2)

你的分而治之的建议是一个好的开始。它只需要更多解释来留下深刻印象。

使用快速乘法算法来乘以大数(大整数),乘以类似大小的被乘数比一系列不匹配的大小更有效。

这是Clojure中的一个例子

; Define a vector of 100K random integers between 2 and 100 inclusive
(def xs (vec (repeatedly 100000 #(+ 2 (rand-int 99)))))

; Naive multiplication accumulating linearly through the array
(time (def a1 (apply *' xs)))
"Elapsed time: 7116.960557 msecs"

; Simple Divide and conquer algorithm
(defn d-c [v] 
  (let [m (quot (count v) 2)] 
    (if (< m 3) 
      (reduce *' v)
      (*' (d-c (subvec v 0 m)) (d-c (subvec v m))))))

; Takes less than 1/10th the time.
(time (def a2 (d-c xs)))
"Elapsed time: 600.934268 msecs"

(= a1 a2) ;=> true (same result)

请注意,此改进不依赖于数组中整数大小的设置限制(100个任意选择并演示下一个算法),但只是它们的大小相似。这是一个非常简单划分的征服。随着数字变得越来越大且成本越来越高,将更多时间花在按类似大小迭代分组上是有意义的。在这里,我依赖于随机分布和大小将保持相似的机会,但即使在最坏的情况下,它仍然会比天真的方法明显更好。

正如Evgeny Kluev在评论中所建议的那样,对于整数,会有很多重复,因此有效的取幂也更好比天真的乘法。这取决于相对参数而不是分而治之,即数量必须足够小,相对于计数足够重复累积而烦恼,但肯定对这些参数表现良好(范围2-中的100K数字) 100)。

; Hopefully an efficient implementation
(defn pow [x n] (.pow (biginteger x) ^Integer n))

; Perform pow on duplications based on frequencies
(defn exp-reduce [v] (reduce *' (map (partial apply pow) (frequencies v))))

(time (def a3 (exp-reduce xs)))
"Elapsed time: 650.211789 msecs"

请注意,在这个试验中,非常简单的分而治之,只是表现得更好,但如果预计会有更少的重复,那就更好了。

当然我们也可以将两者结合起来:

(defn exp-d-c [v] (d-c (mapv (partial apply pow) (frequencies v))))

(time (def a4 (exp-d-c xs)))
"Elapsed time: 494.394641 msecs"

(= a1 a2 a3 a4) ;=> true (all equal)

注意有更好的方法来组合这两个,因为取幂步骤的结果将导致不同大小的被乘数。增加复杂度的值取决于输入中预期数量的不同数字。在这种情况下,很少有不同的数字,因此增加很多复杂性是不会付出代价的。

另请注意,如果有多个内核可以轻松并行化这两种内核。

答案 2 :(得分:1)

如果许多小整数出现多次,您可以从计算每个唯一整数开始。如果c(n)是整数n的出现次数,则可以将产品计算为

P = 2 ^ c(2) * 3 ^ c(3) * 4 ^ c(4) * ...

对于取幂步骤,你可以通过平方来使用取幂,这可以大大减少乘法次数。

答案 3 :(得分:1)

如果数字的数量与范围相比确实很大,那么我们已经看到了两种渐近解决方案,可以大大降低复杂度。一个是基于连续平方来计算每个数字c的O(log k)时间中的c ^ k,如果最大数字是C则给出O(C mean(log k))时间,并且k给出每个数字之间的指数1如果每个数字出现的次数相等,则均值(log k)项最大化,因此如果你有N个数,则复杂度变为O(C log(N / C)),这非常依赖于N.基本上只是O(C),其中C指定数字的范围。

我们看到的另一种方法是按照出现的次数对数字进行排序,并跟踪前导数字的乘积(从所有数字开始)并将其提高到一个幂,以便从中删除最不频繁的数字。数组,然后更新数组中剩余元素的指数并重复。如果所有数字出现相同的次数K,则得到O(C + log K),这是对O的改进(C log K)。但是,假设第k个数字出现2 ^ k次。然后,如果C> 1,这仍将给出O(C ^ 2 + C log(N / C))时间,其在技术上比先前的方法O(C log(N / C))更差。日志(N / C)。因此,如果您没有关于每个数字的出现均匀分布的良好信息,您应该采用第一种方法,只需通过使用连续平方,获取产品中出现的每个不同数字的适当功率,并取结果的产物。总时间O(C log(N / C))如果有C个不同的数字和N个总数。

答案 4 :(得分:0)

我有一个解决方案。让我们与其他解决方案讨论。

问题的关键部分是如何减少乘法次数。整数很小但设置很大。

我的解决方案:

  • 使用小数组记录每个数字出现的次数。
  • 从数组中删除数字1。你不需要数数。
  • 找出出现次数最少的数字n。然后乘以所有数字并得到结果K.然后计算K ^ n。
  • 删除此编号(例如,您可以使用最后一个数组切换它,并将数组的大小减小为1)。所以下次你再也不会考虑这个数字了。同时,其他数字的出现次数需要随着删除次数而减少。
  • 再次获得出现次数最少的数字。与第2步一样。
  • 反复执行步骤2-4并完成计数。
  

让我用一个例子来说明我们需要做多少乘法:假设   我们有5个数字[1,2,3,4,5]。数字1出现100次,数字   2出现150次,3次出现200次,4次出现300次   时间,数字5出现400次。

方法1:直接乘以或使用分而治之 我们需要100 + 150 + 200 + 300 + 400-1 = 1149乘以得到结果。

方法2:我们做(1 ^ 100)(2 ^ 150)(3 ^ 200)(4 ^ 300)(5 ^ 400) (100-1)+(150-1)+(200-1)+(300-1)+(400-1)+4 = 1149. [与方法1相同] 因为n ^ m将以m-1的形式乘以契约。另外,你需要时间来浏览所有数字,但这个时间很短。

这篇文章中的

方法: 首先,您需要时间来浏览所有数字。与乘法时间相比,它可以被丢弃。

您正在进行的实际计数是: ((2 * 3 * 4 * 5)^ 150)*((3 * 4 * 5)^ 50)*((4 * 5)^ 100)*(5 ^ 100)

然后你需要做3 + 149 + 2 + 49 + 1 + 99 + 99 + 3 = 405倍