Как эффективно визуализировать рекурсивную функцию?

в настоящее время я занимаюсь обучением рекурсии в классе программирования. Я заметил, как трудно моим студентам понять концепцию рекурсии. Есть ли хороший способ визуализировать, что функция делает для педагогических целей?

в качестве примера, вот функция R для получения n-го числа Фибоначчи:

fib_r <- function(n) {
    if(n <= 2) return(1)
    fib_r(n-1) + fib_r(n-2)
}

спасибо.

4 ответов


вот как я бы объяснил рекурсивные функции в R:

во-первых, я согласен с @AEBilgrau, что факториал является хорошим примером для рекурсии. (Лучше, чем Фибоначчи в моем opionion.)

тогда я бы быстро прошел через теоретическую основу, почему факториал можно определить как рекурсивную функцию, что-то простое, как

4! = 4*3*2*1 = 4*3!

тогда вы можете представить им соответствующие рекурсивный R функции

fact=function(x) if (x==0) return(1) else return(x*fact(x-1))
fact(3)
#6

но представьте им также следующий вывод

#|fact(3) called
#|fact(3) is calculated via 3*fact(2)
#|fact(2) is unknown yet. Therefore calling fact(2) now
#|Waiting for result from fact(2)
#|  fact(2) called
#|  fact(2) is calculated via 2*fact(1)
#|  fact(1) is unknown yet. Therefore calling fact(1) now
#|  Waiting for result from fact(1)
#|  |   fact(1) called
#|  |   fact(1) is calculated via 1*fact(0)
#|  |   fact(0) is unknown yet. Therefore calling fact(0) now
#|  |   Waiting for result from fact(0)
#|  |   |   fact(0) called
#|  |   |   fact(0)=1 per definition. Nothing to calculate.
#|  |   |   fact(0) returning 1 to waiting fact(1)
#|  |   fact(1) received 1 from fact(0)
#|  |   fact(1) can now calculate 1*fact(0)=1*1=1
#|  |   fact(1) returning 1 to waiting fact(2)
#|  fact(2) received 1 from fact(1)
#|  fact(2) can now calculate 2*fact(1)=2*1=2
#|fact(3) received 2 from fact(2)
#|fact(3) can now calculate 3*fact(2)=3*2=6
#[1] 6

полученные от

#helper function for formatting
tabs=function(n) paste0("|",rep("\t",n),collapse="")

fact=function(x) {
  #determine length of call stack
  sfl=length(sys.frames())-1
  #we need to define tmp and tmp1 here because they are used in on.exit
  tmp=NULL
  tmp1=NULL

  #on.exit will print the returned function value when we exit the function ...
  #... i.e., when one function call is removed from the stack
  on.exit({
    if (sfl>1) {
      cat(tabs(sfl),"fact(",x,") returning ",
          tmp," to waiting fact(",x+1,")\n",sep="")
    }
  })
  cat(tabs(sfl),"fact(",x,") called\n",sep="")
  if (x==0) {
    cat(tabs(sfl),"fact(0)=1 per definition. Nothing to calculate.\n",sep="")
    #set tmp for printing in on.exit
    tmp=1
    return(1)
  } else {
    #print some info for students
    cat(tabs(sfl),"fact(",x,") is calculated via ",x,"*fact(",x-1,")\n",sep="")
    cat(tabs(sfl),"fact(",x-1,
        ") is unknown yet. Therefore calling fact(",x-1,") now\n",sep="")
    cat(tabs(sfl),"Waiting for result from fact(",x-1,")\n",sep="")
    #call fact again
    tmp1=fact(x-1)
    #more info for students
    cat(tabs(sfl),"fact(",x,") received ",tmp1," from fact(",x-1,")\n",sep="")
    tmp=x*tmp1
    cat(tabs(sfl),"fact(",x,") can now calculate ",
        x,"*fact(",x-1,")=",x,"*",tmp1,"=",tmp,"\n",sep="")
    return(tmp)
  }
}

fact(3)

вот мой пример, вероятно, используемый в довольно многих учебниках:

recursive_sum <- function(n){
  if(n == 1) {print("Remember 1, add everything together"); return(n)}
  print(paste0("Remember ", n, ", pass ", n-1, " to recursive function"))
  n + recursive_sum(n-1)
}

выход:

> recursive_sum(4)
[1] "Remember 4, pass 3 to recursive function"
[1] "Remember 3, pass 2 to recursive function"
[1] "Remember 2, pass 1 to recursive function"
[1] "Remember 1, add everything together"
[1] 10

Я думаю, что факториальная функция является хорошим примером для рекурсии. Сочетание этого с распечаткой (как предлагают другие) кажется хорошим способом описать, что происходит:

factorial <- function(n) {
  cat("factorial(", n, ") was called.\n", sep = "")
  if (n == 0) {
    return(1)
  } else {
    return(n * factorial(n - 1))
  }
}

factorial(4)
#factorial(4) was called.
#factorial(3) was called.
#factorial(2) was called.
#factorial(1) was called.
#factorial(0) was called.
#[1] 24

вы также можете реализовать нерекурсивную факторную функцию и сравнить вычислительную эффективность. Или, может быть, спросите их, что проблематично с вышеуказанной реализацией (e.g что происходит с factorial(-4)).

Что касается более правильной визуализации (а не только простых примеров), есть сайты которые иллюстрируют дерево рекурсии.

Edit: рекурсия в Гугле также является полезным уроком.


выведите значение переменной n на fib_r

print("iteraction at: ", n)