Как рассчитать вывод? - PullRequest
1 голос
/ 05 декабря 2009

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

Учитывая следующий фрагмент кода:

#include <stdio.h>

void fun(int a){
 if(a>0){
    fun(--a);
    printf("%d ",a);
    fun(--a);
    printf("%d ",a);
 }
return;
}

int main(void){
  int  num = 5;
  fun(num);
return 0;
}

Это не какое-либо домашнее задание, но я не могу решить этот вопрос в условиях экзамена. (Теоретический экзамен без компилятора)

Какой стандартный способ решения такого вопроса? Пожалуйста, объясните с небольшим примером. Любой указатель в правильном направлении или какая-либо веб-ссылка будет приветствоваться.

Ответы [ 5 ]

8 голосов
/ 05 декабря 2009

Возьми ручку и бумагу; Нарисуйте вызовы функции вместе с параметрами - у вас будет своего рода двоичное дерево. Отслеживайте исполнение и записывайте все соответствующие данные на странице. Это также поможет вам понять функциональность.

Ветвление, связанное с рекурсивными вызовами (особенно бинарными, такими как этот), очень логично и интуитивно понятно, когда вы рисуете его на бумаге. Так меня учили в школе, и, по-моему, это хороший способ понять такие вещи, по крайней мере, в начале, когда не все так интуитивно понятно.

Пример:

            fun [5]
          /        \
       fun[4]       fun[3]
      /      \        |    \
  fun[3]   fun[2]   fun[2]  fun[1]

Я нарисовал дерево вызова так же, как вы можете нарисовать его на бумаге. Это должно помочь сделать происходящее более понятным для вас. И это действительно так, как я справлялся с подобными вещами в те времена, так что поверьте мне - это работает :)

3 голосов
/ 05 декабря 2009

Написание рекурсивного метода немного похоже на составление плана по выстраиванию домино и выбиванию их из строя. Вы должны предвидеть, как каждый отдельный рекурсивный вызов (домино) добавит свою часть к выполнению всей задачи.

Это означает, что «мясо» рекурсивного метода находится не в рекурсивной части, а в «конечных случаях» - частях кода, которые выполняются, когда вы не собираетесь повторно вызывать метод, последнее домино в цепочке, которую вы нажимаете, чтобы начать веселье.

Итак, давайте посмотрим на ваш первый пример, рекурсивная программа для (целочисленного) деления. Алгоритм деления, который вы пытаетесь реализовать, "для положительных d и n, пусть n (0) будет n. Продолжайте вычитать d из n (i) шаг за шагом, пока на каком-то шаге q n (q) не станет меньше d . Ваш ответ q. "

Ключ в том, чтобы сначала взглянуть на случай КОНЦА. Что если в начале n уже меньше d? Затем вы сделали «ноль шагов», поэтому ваш результат деления равен 0.

В псевдокоде:

int divide(int n, int d) {
if (n < d) {
return 0;
}
....
}

А что если n не меньше d (больше или равно d)? Затем мы хотим попробовать еще один шаг в процессе деления с меньшим n. То есть снова запустите функцию деления с «тем же d» и n = «старое n» - d. Но как только это разделение заканчивается, оно только говорит нам, сколько шагов вычитания потребовалось для (n-d) / d. Мы знаем, что н / д требует еще один шаг. Таким образом, мы должны добавить этот шаг к результату:

int divide(int n, int d) {
if (n < d) {
return 0;
} else {
return divide( n-d, d ) + 1;
}
}

Что на самом деле говорит это второе возвращение? Он говорит: «Я не знаю, как вычислить результат сам, но я знаю, что он ОДИН БОЛЬШЕ, чем результат для функции« делить (nd, d) »). Поэтому я передам этот метод вызову» а затем просто добавьте один к тому, что он мне вернет. "

И процесс продолжается. Мы продолжаем добавлять домино «делить» в цепочку, пока не достигнем операции деления, где n «сжалось», чтобы быть меньше, чем d ... наш конечный случай, наш нулевой результат. Теперь мы опрокидываем первое домино (последнее, которое мы добавили в цепочку), возвращая «0». И домино начинают падать. Каждый раз, когда одно домино опрокидывает другое домино, мы добавляем «1» к результату метода, пока, наконец, первый вызов метода не станет последним падением домино, и он вернет результат деления.

Давайте попробуем несколько примеров:

12/18

разрыв (12,18) ---> возвращает 0, поскольку 12 меньше 18

результат равен 0.

* * Тысяча двадцать восемь 20/5:
divide(20, 5)
---> returns divide(20-5, 5) + 1
------> returns divide(15-5, 5) +1
---------> returns divide(10-5, 5) +1
------------> returns divide(5-5, 5) +1
---------------> returns 0, since 5-5 is 0, which is less than 5
and now the dominoes fall...
------------> returns 0 + 1
---------> returns 1 + 1
------> returns 2 + 1
---> returns 3 + 1
result is 4.

* +1032 * 8/3:
divide(8, 3)
---> returns divide(8-3, 3) + 1
------> returns divide(5-3, 3) +1
---------> returns 0, since 5-3 is 2, which is less than 3
and now the dominoes fall...
------> returns 0 + 1
---> returns 1 + 1
result is 2.

1 голос
/ 05 декабря 2009

Вы должны быть компилятором и компьютером. Запишите стопку по ходу дела:

Введите основной, вызовите веселье с 5. В веселье 5 больше 0, поэтому Сначала я уменьшаю 5 до 4, затем снова вызываю веселье

Здесь, если вы пишете, я бы переместился в сторону и "начал новый стек"

Я вхожу в веселье с 4, что больше 0 Я уменьшаю 4 до 3, затем снова вызываю веселье

Повторите

Я вхожу в веселье с 3, который больше 0 Я уменьшаю 3 до 2, затем снова вызываю веселье

Повторите еще раз

Я вхожу в веселье с 2, который больше 0 Я уменьшаю 2 до 1, затем снова вызываю веселье

И еще раз

Я ввожу веселье с 1, который больше 0 Я уменьшаю 1 до 0, затем снова вызываю веселье

И введите в последний раз, на этот раз

Я ввожу веселье с 0, что не больше 0 Я возвращаюсь

Теперь вы возвращаетесь туда, где вы были:

Я ввожу веселье с 1, который больше 0 Я уменьшаю 1 до 0, затем снова вызываю веселье Я распечатываю 0

В командах печати запишите это в еще один пробел, который теперь содержит только «0». продолжить функцию:

Я ввожу веселье с 1, который больше 0 Я уменьшаю 1 до 0, затем снова вызываю веселье Я печатаю 0 Я уменьшаю 0 до -1 и снова вызываю веселье

Вот еще один стек, но -1 не больше 0, поэтому он ничего не делает. Вернемся к функции:

Я ввожу веселье с 1, который больше 0 Я уменьшаю 1 до 0, затем снова вызываю веселье Я печатаю 0 Я уменьшаю 0 до -1 и снова вызываю веселье Я печатаю -1

И мы заканчиваем этот стек. Мы возвращаемся к старому стеку (мы только что закончили вводить веселье с 1, поэтому ищем стек, который заканчивается «уменьшить до 1 и снова вызвать веселье»):

Я вхожу в веселье с 2, который больше 0 Я уменьшаю 2 до 1, затем снова вызываю веселье Я распечатываю 1 Я уменьшаю 1 до 0, затем снова вызываю веселье

Вызов fun(0) ничего не делает, поэтому мы возвращаемся и продолжаем:

Я вхожу в веселье с 2, который больше 0 Я уменьшаю 2 до 1, затем снова вызываю веселье Я печатаю 1 Я уменьшаю 1 до 0, затем снова вызываю веселье Я печатаю 0

Затем мы переходим к следующему самому старому стеку (мы только что закончили вводить веселье с 2, так что ищите стек, который заканчивается «уменьшить до 2 и снова вызвать веселье»):

Я вхожу в удовольствие с 3, который больше 0 Я уменьшаю 3 до 2, затем снова вызываю веселье Я печатаю 2 Я уменьшаю 2 до 1, затем снова вызываю веселье

Вот важная экономия времени! Мы уже однажды звонили fun(1), нет необходимости снова и снова это делать. Что распечатал fun(1)? Посмотрите вверх, и вы увидите, что он добавил «0-1» к выводу, так что сэкономьте время и просто добавьте это.

Это продолжается до тех пор, пока вы не закончите. Это большая работа, но записать ваши текущие стеки - самый простой способ завершить ее. Ради того, чтобы этот короткий ответ был коротким, остальное зависит от вас. :)

0 голосов
/ 05 декабря 2009

Добавьте printf("%d\n", a); в начале функции (вне оператора if) и запустите код, чтобы увидеть ход выполнения. Вы можете добавить другой параметр в функцию, которая принимает глубину рекурсии в качестве аргумента, и использовать его для отступа оператора print.

Вывод следующей программы поможет вам понять ход выполнения.

#include <stdio.h>

void indent(int d)
{
        for(int i = 0; i < d; i++)
                printf("\t");
}
void fun(int a, int d)
{
    indent(d);
    printf("function called with a = %d\n", a);
    if(a>0)
    {
        fun(--a, d + 1);
        indent(d);
        printf("%d\n", a);
        fun(--a, d + 1);
        indent(d);
        printf("%d\n", a);
    }
    else
    {
        indent(d);
        printf("returning as a<%d> <= 0\n", a);
    }
    return;
}

int main(void)
{
    int  num = 5;
    fun(num, 0);
    return 0;
}

Вот вывод:

function called with a = 5
    function called with a = 4
        function called with a = 3
            function called with a = 2
                function called with a = 1
                    function called with a = 0
                    returning as a<0> <= 0
                0
                    function called with a = -1
                    returning as a<-1> <= 0
                -1
            1
                function called with a = 0
                returning as a<0> <= 0
            0
        2
            function called with a = 1
                function called with a = 0
                returning as a<0> <= 0
            0
                function called with a = -1
                returning as a<-1> <= 0
            -1
        1
    3
        function called with a = 2
            function called with a = 1
                function called with a = 0
                returning as a<0> <= 0
            0
                function called with a = -1
                returning as a<-1> <= 0
            -1
        1
            function called with a = 0
            returning as a<0> <= 0
        0
    2
4
    function called with a = 3
        function called with a = 2
            function called with a = 1
                function called with a = 0
                returning as a<0> <= 0
            0
                function called with a = -1
                returning as a<-1> <= 0
            -1
        1
            function called with a = 0
            returning as a<0> <= 0
        0
    2
        function called with a = 1
            function called with a = 0
            returning as a<0> <= 0
        0
            function called with a = -1
            returning as a<-1> <= 0
        -1
    1
3

Отслеживание рекурсии может быть действительно веселым. Наслаждайтесь!

0 голосов
/ 05 декабря 2009

Это довольно сложный вопрос, на который нужно ответить только ручкой и бумагой. Я полагаю, что он должен убедиться, что вы поняли, как это делает компьютер, особенно два рекурсивных вызова fun.

При моделировании выполнения на бумаге помните, что при рекурсивном вызове fun процессор запоминает содержимое локальных переменных (здесь аргумент a) и , к какому оператору возвращаться после вызываемая функция завершается.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...