在VBA中使用这两种循环方式的时间复杂度有什么区别?

时间:2011-01-28 12:00:36

标签: excel vba excel-vba time-complexity big-o

我有一个理论问题,如果你在这里建议,我将不胜感激。

说,我们有这两段代码。 第一个:

For Each cell In rng1
    collectionOfValues.Add (cell.Value)
Next

For Each cell In rng2
   collectionOfAddresses.Add (cell.Address)
Next

For i = 1 To collectionOfAddresses.Count
   Range(collectionOfAddresses.Item(i)) = collectionOfValues.Item(i)
Next i

这里我们将地址从一个范围添加到某个集合,将值从另一个范围添加到第二个集合,然后用这些值填充这些地址上的单元格。

这是第二个代码,它是相同的:

For i = 1 To rng1.Rows.Count
  For j = 1 To rng1.Columns.Count
       rng2.Cells(i, j) = rng1.Cells(i, j)
  Next j
Next i

所以,问题是 - 两种情况下执行的时间是多少?我的意思是,很明显第二种情况是O(n ^ 2)(为了使我们更容易假设范围是正方形)。

第一个怎么样? For Each被认为是嵌套循环吗?

如果是这样,是否意味着第一个代码的时间是O(n ^ 2)+ O(n ^ 2)+ O(n ^ 2)= 3 * O(n ^ 2)这使得漂亮与第二个代码时间相同?

一般来说,这两个代码是否与第一个代码在创建集合时需要额外内存的事实不同?

提前多多感谢。

3 个答案:

答案 0 :(得分:5)

实际上,你的第一个例子是 O(n ^ 4)

这可能听起来令人惊讶,但这是因为索引到VBA集合中具有线性而非常量的复杂性。 VBA Collection本质上具有列表的性能特征 - 通过索引获取元素N 需要与N成比例的时间。迭代整个事物按索引需要一个时间比例到N ^ 2。 (我在你的情况下切换区分N,即数据结构中元素的数量,从你的n,一个方块单元格一侧的单元格数。所以这里N = n ^ 2.)

这就是为什么VBA具有For ...每个用于迭代集合的符号的原因之一。当你使用For ... Each时,VBA在幕后使用迭代器,因此遍历整个Collection是O(N)而不是O(N ^ 2)。

因此,切换回你的n,你的前两个循环使用For ...每个在一个范围内有n ^ 2个单元格,所以它们都是O(n ^ 2)。你的第三个循环是使用For ... Next而不是具有n ^ 2个元素的Collection,因此它是O(n ^ 4)。

我实际上并不确定你的最后一个循环,因为我不确切知道Range的Cells属性是如何工作的 - 那里可能存在一些额外的隐藏复杂性。但我认为Cell将具有数组的性能特征,因此O(1)用于通过索引进行随机访问,这将使最后一个循环为O(n ^ 2)。

这是Joel Spolsky所说的“Shlemiel the painter's algorithm”的一个很好的例子:

  

画家必须有一个Shlemiel   在那里的算法。每当   似乎应该有的东西   线性表现,但似乎   有n平方的表现,寻找   隐藏的Shlemiels。他们经常   被你的图书馆隐藏。

(在stackoverflow成立之前查看此文章:http://www.joelonsoftware.com/articles/fog0000000319.html

关于VBA表现的更多信息可以在Doug Jenkins的网站上找到:

http://newtonexcelbach.wordpress.com/2010/03/07/the-speed-of-loops/

http://newtonexcelbach.wordpress.com/2010/01/15/good-practice-best-practice-or-just-practice/

(我还要说明,如果这是一个“真正的”程序而不仅仅是一个学习练习,那么cyberkiwi所说的不会通过Ranges来复制单元格内容。)

答案 1 :(得分:0)

你是对的,第一个是3 x O(n ^ 2),但请记住O符号并不关心常量,因此就复杂性而言,它仍然是O(n^2) algorithm

第一个不被认为是嵌套循环,即使它与第二个循环的工作大小相同。它只是Excel中N项目范围的直接迭代。 N ^ 2的原因在于你将N定义为边的长度,即行/列的数量(正方形)。

只是一个Excel VBA注释,你不应该循环遍历单元格,也不应该存储地址。这两种方法都不是最佳的。但我认为它们可以用来说明你的问题以理解O符号。

rng1.Copy
rng2.Cells(1).PasteSpecial xlValues
Application.CutCopyMode = False

答案 2 :(得分:0)

请记住,不要将您的代码的复杂性与后台Excel函数的复杂性混淆。在两种情况下,完成的所有工作量都是N ^ 2。但是,在您的第一个示例中 - 您的代码实际上只有3N(三个循环中的每一个都为N)。 Excel中的单个语句可以填充多个值这一事实不会改变编写代码的复杂性。 foreach循环与for循环相同 - N复杂度本身。嵌套循环时只能得到N ^ 2.

要回答关于哪个更好的问题 - 通常最好尽可能使用内置函数。应该假设内部Excel的运行效率比您自己编写的效率高。但是(知道MS) - 如果性能优先,请确保始终检查该假设。