高阶函数的计算复杂度?
Computational Complexity of Higher Order Functions?
Map 和 filter 似乎是线性 O(n) 因为它们只需要遍历一次列表,但是它们的复杂度是否受传递函数的影响?例如下面的两个例子是同一个顺序吗?
map (+) list
map (complex_function) list
您似乎有一个(常见的)误解,认为复杂性是 n
的函数。
什么是 n
?
n
只是衡量您的输入的一个参数。它可能不足以(甚至是必要的)统计数据来描述您的输入——您可能需要其他变量来准确描述您输入的复杂性。
所以,map
和filter
在n
中是线性的。它们对其他变量的依赖取决于您传递的函数,但它们对 n
的依赖通常不会。
(脚注:是的,您 可以 将一个函数传递给 map
和 filter
,当它处理更多元素时实际上执行更多工作,但那是无趣而且离题了。)
在几乎所有情况下,当高阶函数的文档声明其复杂度为 O(f(n))
时,这是假设高阶函数具有常数时间复杂度 O(1)
。此外,n
的确切含义可能会有所不同,但如果没有明确说明,从上下文中应该很清楚:例如列表的长度,elements/associations 在 set/map 中的数量,等等。
假设我们有一个称为 g h ...
的高阶函数 g
,其中 h
是一个函数,...
是一阶数据。没有关于高阶函数 g
的任何其他信息,如果文档指出它是 O(f(n))
,您可以获得更现实的 O(f(n)) * CostH
的最坏情况界限,其中 CostH
表示调用 H
一次的成本。
当然,CostH
也将取决于哪些参数开始传递给 h
:在这里,我们所知道的是这些参数是在 O(f(n))
时间内构建的。很难对 h
的参数大小给出一个有用的一般界限,因为例如,如果 h
将树作为输入,那么您可以在短时间内构建一个非常大的树:
foo :: Int -> Tree ()
foo 0 = Tree []
foo m = Tree [t,t] where t = foo (m-1)
上面构建的树在 m
时间内有 2^m
个叶子(感谢分享)。这可以适应 3^m
或 b^m
微不足道地保持 b*m
复杂性。
但是,可能有一些方法可以利用参数化来恢复更有用的边界。例如,如果我们的订单函数有 type
g :: (a -> Int) -> [a] -> Int
然后我们知道 g h [...]
只能使用从列表中获取的参数调用h
。同样,库函数调用 sortBy h [...]
只能使用 h
来比较所提供列表中的两个元素。
但是我不知道如何形式化和证明上面的草图声明。很可能有一些关于该主题的研究论文。
有关复杂性和高阶函数的一些背景知识,例如,参见
霍夫曼,马丁。范畴论语义学在复杂性表征中的应用 类 使用高阶函数代数。
牛。符号逻辑 3 (1997),没有。 4、469--486。 https://doi.org/10.2307/421100
Map 和 filter 似乎是线性 O(n) 因为它们只需要遍历一次列表,但是它们的复杂度是否受传递函数的影响?例如下面的两个例子是同一个顺序吗?
map (+) list
map (complex_function) list
您似乎有一个(常见的)误解,认为复杂性是 n
的函数。
什么是 n
?
n
只是衡量您的输入的一个参数。它可能不足以(甚至是必要的)统计数据来描述您的输入——您可能需要其他变量来准确描述您输入的复杂性。
所以,map
和filter
在n
中是线性的。它们对其他变量的依赖取决于您传递的函数,但它们对 n
的依赖通常不会。
(脚注:是的,您 可以 将一个函数传递给 map
和 filter
,当它处理更多元素时实际上执行更多工作,但那是无趣而且离题了。)
在几乎所有情况下,当高阶函数的文档声明其复杂度为 O(f(n))
时,这是假设高阶函数具有常数时间复杂度 O(1)
。此外,n
的确切含义可能会有所不同,但如果没有明确说明,从上下文中应该很清楚:例如列表的长度,elements/associations 在 set/map 中的数量,等等。
假设我们有一个称为 g h ...
的高阶函数 g
,其中 h
是一个函数,...
是一阶数据。没有关于高阶函数 g
的任何其他信息,如果文档指出它是 O(f(n))
,您可以获得更现实的 O(f(n)) * CostH
的最坏情况界限,其中 CostH
表示调用 H
一次的成本。
当然,CostH
也将取决于哪些参数开始传递给 h
:在这里,我们所知道的是这些参数是在 O(f(n))
时间内构建的。很难对 h
的参数大小给出一个有用的一般界限,因为例如,如果 h
将树作为输入,那么您可以在短时间内构建一个非常大的树:
foo :: Int -> Tree ()
foo 0 = Tree []
foo m = Tree [t,t] where t = foo (m-1)
上面构建的树在 m
时间内有 2^m
个叶子(感谢分享)。这可以适应 3^m
或 b^m
微不足道地保持 b*m
复杂性。
但是,可能有一些方法可以利用参数化来恢复更有用的边界。例如,如果我们的订单函数有 type
g :: (a -> Int) -> [a] -> Int
然后我们知道 g h [...]
只能使用从列表中获取的参数调用h
。同样,库函数调用 sortBy h [...]
只能使用 h
来比较所提供列表中的两个元素。
但是我不知道如何形式化和证明上面的草图声明。很可能有一些关于该主题的研究论文。
有关复杂性和高阶函数的一些背景知识,例如,参见
霍夫曼,马丁。范畴论语义学在复杂性表征中的应用 类 使用高阶函数代数。
牛。符号逻辑 3 (1997),没有。 4、469--486。 https://doi.org/10.2307/421100