Понимание рекурсии в Java немного лучше - PullRequest
2 голосов
/ 13 ноября 2010

Ладно, я действительно ничего не понимаю в рекурсии на Java. Скажем, у меня есть следующий код:

static int findShortestString(String[] paths, int lo, int hi) {
        if(lo==hi)
            return lo;
        int minindex=findShortestString(paths,lo+1, hi);
        if(safeStringLength(paths[lo])<safeStringLength(paths[minindex]))
            return lo;
        return minindex;

Теперь вопрос не в самом коде, а в том, как работает рекурсия. minindex устанавливается равным рекурсивному вызову. Таким образом, в первый раз, когда функция запускается и пытается установить minindex на что-то, она делает это, и затем функция вызывает сама Но когда тогда выполняется оператор if? Будет ли он работать только тогда, когда minindex наконец-то будет иметь реальную ценность? Я просто не могу обернуть голову вокруг этого. Если minindex вызывает функцию для рекурсии и рекурсии, то когда будет проверяться оператор if? Когда lo==hi? Я не понимаю: (

Ответы [ 7 ]

5 голосов
/ 13 ноября 2010

minindex не присваивается до тех пор, пока findShortestString не вернет , что не произойдет до lo == hi.

Каждый раз, когда метод вызывает себя, он сужает разницу между lo и hi на 1, так что в конечном итоге они будут равны *, и это значение будет возвращено.

Например, с paths = ["p1", "path2", "longpath3"]:

lo = 0, hi = 2
lo != hi -> call findShortestString(paths, 1, 2)

  lo = 1, hi = 2
  lo != hi -> call findShortestString(paths, 2, 2)

    lo = 2, hi = 2
    lo == hi -> return lo (=2)

  lo = 1, hi = 2, minindex = 2
  length of "path2" < length of "longpath3" -> return lo (= 1)

lo = 0, hi = 2, minindex = 1
length of "p1" < length of "path2" -> return lo (= 0)

У меня естьпопытался проиллюстрировать значения переменных на каждом уровне рекурсии, используя увеличивающиеся отступы.В начале каждого рекурсивного вызова предыдущие значения lo, hi и minindex сохраняются (в структуре, называемой «стеком»), и вместо них используются новые значения.Когда каждый вызов метода возвращает , ранее сохраненные значения «извлекаются» из стека для использования, и minindex присваивается из предыдущего возвращаемого значения.

*, если lo> hiдля начала, я думаю ...

2 голосов
/ 13 ноября 2010

Вот игра по пьесе исполнения:

Вы звоните findShortestString() сами

Если Ло не равно, привет, дела продолжаются. В противном случае они останавливаются здесь, и функция возвращает.

Как только вы снова вызываете findShortestString(), все в этом экземпляре функции полностью останавливается и не возобновит работу, пока у компьютера не будет значения, чтобы дать minindex (иначе функция возвращается.) Мы начнем все заново в новом экземпляре функция наверху. Единственный код, выполняемый до тех пор, пока одна из функций не вернется, это код ДО вызова метода. Это можно сравнить с циклом while.

Мы выходим за эту строку только после того, как один из экземпляров функции имеет lo==hi и возвращает.

Управление переключается на экземпляр функции до этого, который присваивает возвращаемое значение lo minindex.

Если (safeStringLength(paths[lo])<safeStringLength(paths[minindex])), то мы return lo. Остальное у нас return minindex. В любом случае, этот экземпляр функции завершен, и управление возвращается к предыдущему.

Каждая вызываемая функция теперь только выполняет код ПОСЛЕ вызова метода, поскольку метод не будет вызываться снова. Мы раскручиваем стек вызовов. Все возвраты теперь будут из последних 2 операторов, так как код сверху не будет выполнен снова. Обратите внимание, как только один экземпляр функции возвращается с верхней частью кода, завершая цикл while. Все остальные завершаются операторами возврата в части функции после рекурсивного вызова.

В конце концов последняя функция возвращается, и вы возвращаетесь к коду, из которого вызывали функцию изначально.

Вот более читаемая версия того, что на самом деле делает код:

В коде перед рекурсивным вызовом все, что происходит, - это создание цепочки вызовов до lo==hi. Каждый раз, когда функция вызывается, когда lo больше 1. Вот пример стека вызовов:

findShortestString(2,5);
findShortestString(3,5);
findShortestString(4,5);
findShortestString(5,5);

Когда они раскручиваются, каждый экземпляр функции сравнивает длину строк в индексах lo и индекс предыдущего индекса с самой короткой строкой.

compare strings at indexes 2 and 5
if the string at 2 is smaller, compare the strings at indexes 2 and 4.
Otherwise, compare the strings with indexes at 3 and 5.

Если lo>hi в начале, код будет продолжать выполняться до тех пор, пока lo не переполнит целое число и не станет отрицательным, затем до тех пор, пока lo наконец не доберется до hi, или 4,94,967,296 - (original lo - original hi) , Другими словами, это займет долгое время . Чтобы это исправить, добавьте проверку в начале метода, который выдает исключение, если lo>hi.

Код может быть лучше переписан так:

static int findShortestString(String[] paths, int lo, int hi) {
    int indexWithShortestString=lo;
    for( int i=lo; i<=hi-1; i++) {
        //assumption: lo and hi are both valid indexes of paths
        if (paths[i+1].length < paths[i].length)
            indexWithShortestString=i+1;
     }
}
1 голос
/ 13 ноября 2010

Подумайте о стеке.Каждый раз, когда рекурсивный метод вызывается, новый «фрейм» помещается поверх стека.Кадр содержит свои собственные «слоты» для каждой переменной, независимые и отличные от тех, что в кадрах ниже.

В конце концов, будет создан новый кадр, в котором значения lo и hi равны, и метод вернет без нажатия на другой кадр.(Это называется «базовым случаем».) Когда это return происходит, этот кадр выталкивается из стека, и кадр, который был чуть ниже его, продолжает свое выполнение во втором операторе if.В конечном итоге этот кадр также удаляется, и то же самое происходит с кадром чуть ниже и т. Д., Пока выполнение не вернется к исходному вызывающему объекту.

0 голосов
/ 13 ноября 2010

В основном выполнение функции заканчивается, когда вызывается оператор return. Все после оператора return, который вызывается, больше не имеет значения (или «существует»).

Следовательно, локальная переменная minindex будет существовать только при выполнении функции findShortestString, когда первый оператор if ложен.

Обрабатывает каждое выполнение функции findShortestString независимо от того, вызываются ли они рекурсивно или откуда-то еще в коде. то есть разное выполнение функции findShortestString может возвращаться по разным путям и иметь свои собственные значения и локальные переменные. В зависимости от входных значений они могут возвращаться в строке 3, 6 или 7.

minindenx существует только в исполнении, которое может запускать строку 4, и ему присваивается findShortestString (paths, lo + 1, hi), которое гарантированно имеет значение, если код верен, в противном случае вы получите бесконечную рекурсию, что приводит к переполнению стека (каламбур непреднамеренный).

0 голосов
/ 13 ноября 2010

Чтобы выполнить оператор

int minindex=findShortestString(paths,lo+1, hi);

, вызов метода findShortestString(paths,lo+1, hi) должен вернуть значение.Таким образом, следующий оператор if не произойдет, пока этот вызов метода не вернет значение.Однако этот метод может вызвать сам себя снова, и вы получите эффект вложенности.

0 голосов
/ 13 ноября 2010

Это действительно запутанная рекурсивная функция. Но у вас, как правило, все правильно. Каждый вызов findShortestString () помещает функцию в стек. Это будет продолжаться до тех пор, пока ... = привет. В этот момент стек разматывается, и соответствующие рекурсивные вызовы будут назначены их соответствующим целым числам.

В этой функции кажется, что вы когда-нибудь вернете только lo. Потому что либо (safeStringLength(paths[lo])<safeStringLength(paths[minindex]) будет верным, и вы вернете lo. Или lo==hi будет верным, и вы вернетесь lo

0 голосов
/ 13 ноября 2010

Каждый раз, когда findShortestString вызывает себя, в конечном итоге присваивается minindex, затем значение используется в операторе if.Индекс самой короткой строки всегда устанавливается более высоким, чем lo.

. Поэтому, если имеется стек вызовов с 10 уровнями findShortestString, minindex назначается 9 раз (первый вызовиз другой функции).

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