Algorithm 为什么我的递归函数在R中如此缓慢?
下面的运行大约需要30秒,而我希望它几乎是即时的。我的代码有问题吗Algorithm 为什么我的递归函数在R中如此缓慢?,algorithm,r,optimization,recursion,fibonacci,Algorithm,R,Optimization,Recursion,Fibonacci,下面的运行大约需要30秒,而我希望它几乎是即时的。我的代码有问题吗 x <- fibonacci(35); fibonacci <- function(seq) { if (seq == 1) return(1); if (seq == 2) return(2); return (fibonacci(seq - 1) + fibonacci(seq - 2)); } x:-)因为你使用指数算法!!!因此,对于斐波那契数N,它必须调用函数2^N次,即2^35
x <- fibonacci(35);
fibonacci <- function(seq) {
if (seq == 1) return(1);
if (seq == 2) return(2);
return (fibonacci(seq - 1) + fibonacci(seq - 2));
}
x:-)因为你使用指数算法!!!因此,对于斐波那契数N,它必须调用函数2^N次,即2^35,这是一个非常好的数字….:-)
使用线性算法:
fib = function (x)
{
if (x == 0)
return (0)
n1 = 0
n2 = 1
for (i in 1:(x-1)) {
sum = n1 + n2
n1 = n2
n2 = sum
}
n2
}
抱歉,编辑:指数递归算法的复杂性不是O(2^N),而是O(fib(N)),因为:-)这是一个很好的注释:-),因为您使用的是其中一个
其复杂性为O(斐波那契(n))
=O((黄金比率)^n)
,黄金比率为1.618033987498948482…
Patrick Burns给出了一个用local()在R中进行记忆的方法示例<代码> >代码> > P>这正好提供了一个很好的插件机会,这使得我们可以很容易地将C++函数添加到R.<
因此,在稍微修改代码、使用软件包(将短代码段轻松编译、加载和链接为可动态加载的函数)以及计时和比较函数之后,我们的性能提高了700倍:
R> print(res)
test replications elapsed relative user.self sys.self
2 fibRcpp(N) 1 0.092 1.000 0.10 0
1 fibR(N) 1 65.693 714.054 65.66 0
R>
在这里,我们看到了92百万秒与65秒的流逝时间,相对比率为714。但是现在所有人都告诉你不要直接在R。。。。代码如下
## inline to compile, load and link the C++ code
require(inline)
## we need a pure C/C++ function as the generated function
## will have a random identifier at the C++ level preventing
## us from direct recursive calls
incltxt <- '
int fibonacci(const int x) {
if (x == 0) return(0);
if (x == 1) return(1);
return (fibonacci(x - 1)) + fibonacci(x - 2);
}'
## now use the snipped above as well as one argument conversion
## in as well as out to provide Fibonacci numbers via C++
fibRcpp <- cxxfunction(signature(xs="int"),
plugin="Rcpp",
incl=incltxt,
body='
int x = Rcpp::as<int>(xs);
return Rcpp::wrap( fibonacci(x) );
')
## for comparison, the original (but repaired with 0/1 offsets)
fibR <- function(seq) {
if (seq == 0) return(0);
if (seq == 1) return(1);
return (fibR(seq - 1) + fibR(seq - 2));
}
## load rbenchmark to compare
library(rbenchmark)
N <- 35 ## same parameter as original post
res <- benchmark(fibR(N),
fibRcpp(N),
columns=c("test", "replications", "elapsed",
"relative", "user.self", "sys.self"),
order="relative",
replications=1)
print(res) ## show result
如果您确实希望返回斐波那契数,并且没有使用此示例来探索递归的工作原理,那么您可以使用以下方法非递归地解决它:
fib = function(n) {round((1.61803398875^n+0.61803398875^n)/sqrt(5))}
具有线性成本的递归实现:
fib3 <- function(n){
fib <- function(n, fibm1, fibm2){
if(n==1){return(fibm2)}
if(n==2){return(fibm1)}
if(n >2){
fib(n-1, fibm1+fibm2, fibm1)
}
}
fib(n, 1, 0)
}
此解决方案可以使用ifelse
进行矢量化:
fib4 <- function(n){
fib <- function(n, fibm1, fibm2){
ifelse(n<=1, fibm2,
ifelse(n==2, fibm1,
Recall(n-1, fibm1+fibm2, fibm1)
))
}
fib(n, 1, 0)
}
fib4(1:30)
## [1] 0 1 1 2 3 5 8
## [8] 13 21 34 55 89 144 233
## [15] 377 610 987 1597 2584 4181 6765
## [22] 10946 17711 28657 46368 75025 121393 196418
## [29] 317811 514229
fib4因为这里已经提到的是一个参考实现:
fib <- function(n) {
if (n < 2) return(1)
fib(n - 2) + fib(n - 1)
}
system.time(fib(35))
## user system elapsed
## 36.10 0.02 36.16
library(memoise)
fib2 <- memoise(function(n) {
if (n < 2) return(1)
fib2(n - 2) + fib2(n - 1)
})
system.time(fib2(35))
## user system elapsed
## 0 0 0
fib记忆在哪里?除了实现上面提到的更好的算法外,您还可以尝试Radford Neal一直在开发的一些R补丁。我不确定你的问题,但你确定这是正确的实现。当然,您的代码将生成1,2,3,5,8,
,而正确的序列是0,1,1,2,3,5,8,
?不熟悉记忆以及如何在R中实现它。我正在实现此处指定的斐波那契包gmp
具有函数fibnum
,以任意精度计算斐波那契数。使用标准的double
,您最多只能得到n=55
左右。好吧,这是个好主意。地狱和记忆,听起来真的很神奇。我们通常称之为全局变量:-),但无论如何,我没有想到在线性时间中使用递归!很好的说明。后期添加:有几个选项可供记忆:请参阅。@hadley:在此处添加此选项作为答案:嗯,Rcpp。。。看起来真的很好很简单!!不错;-)您似乎还试图证明指数算法的合理性;)嗯,编译代码的速度是92毫秒,它没有实现指数算法,即使是在速度很快的计算机上。编译器必须以某种巧妙的方式进行优化。我认为这不是一个公平的测试。内联包是由R驱动的,因此得到了标准的gcc/g++选项。所以我称之为公平测试:因为它显示了编译程序可以为你做,如果你把R三的内衬翻译成C++三的内衬。在任何情况下,如果你真的想学习asm代码,你都可以。呵呵,都是真的。但它并没有说明R在其解释器中是否以及在何处存在低效。这与我们更相关,我们认为从R调用C就是承认R从根本上是一种坏语言(或者,至少是一种从根本上坏掉的S实现)。恕我直言,这是胡说八道。任何给定的系统都有一个特殊的弱点。我的观点是,我们可以通过结合相关的优势来构建更好的系统——甚至可以像这个例子所显示的那样轻松地做到这一点——而不是因为这些劣势而紧张。例如,看看钱伯斯去年秋天在斯坦福大学的演讲:它总是关于语言和工具的结合。我的拙劣观点是RCPP帮助你把C++和R的更好部分结合起来,但是你当然可以自由地把R扔进垃圾箱,使用本周流行的任何东西。祝你好运。这个函数精确到n=55
@MatthewLundberg一点也不!我还将初始条件更改为n,1,0
,使其在数学上正确,但这并不会改变运行时间或原始代码的含义。@MatthewLundberg nice,我还喜欢回忆
编辑,如果您在备忘录
包的功能上添加一两句话,它会很有用,一般来说。
> system.time(fibonacci(35))
usuário sistema decorrido
14.629 0.017 14.644
> system.time(fib3(35))
usuário sistema decorrido
0.001 0.000 0.000
fib4 <- function(n){
fib <- function(n, fibm1, fibm2){
ifelse(n<=1, fibm2,
ifelse(n==2, fibm1,
Recall(n-1, fibm1+fibm2, fibm1)
))
}
fib(n, 1, 0)
}
fib4(1:30)
## [1] 0 1 1 2 3 5 8
## [8] 13 21 34 55 89 144 233
## [15] 377 610 987 1597 2584 4181 6765
## [22] 10946 17711 28657 46368 75025 121393 196418
## [29] 317811 514229
fib <- function(n) {
if (n < 2) return(1)
fib(n - 2) + fib(n - 1)
}
system.time(fib(35))
## user system elapsed
## 36.10 0.02 36.16
library(memoise)
fib2 <- memoise(function(n) {
if (n < 2) return(1)
fib2(n - 2) + fib2(n - 1)
})
system.time(fib2(35))
## user system elapsed
## 0 0 0