Как рекурсивный возврат с возвратом обрабатывается с типом void

Чтобы обобщить этот вопрос, я заимствую материал из раздаточного материала Zelenski CS. И это имеет отношение к моему конкретному вопросу, поскольку несколько лет назад я прошел курс у другого инструктора и изучил этот подход к С++. Раздаточный материал находится здесь. Мое понимание C++ низкое, так как я использую его время от времени. По сути, несколько раз, когда мне нужно было написать программу, я возвращался к материалам класса, находил что-то похожее и начинал оттуда.

В этом примере (стр. 4) Джулия ищет слово, используя рекурсивный алгоритм в строковой функции. Чтобы уменьшить количество рекурсивных вызовов, она добавила точку принятия решения bool containsWord().

string FindWord(string soFar, string rest, Lexicon &lex)
{
  if (rest.empty()) {
   return (lex.containsWord(soFar)? soFar : "");
  } else {
   for (int i = 0; i < rest.length(); i++) {
     string remain = rest.substr(0, i) + rest.substr(i+1);
     string found = FindWord(soFar + rest[i], remain, lex);
     if (!found.empty()) return found;
   }
  }
 return ""; // empty string indicates failure
}

Чтобы добавить гибкости тому, как используется этот алгоритм, можно ли его реализовать как тип void?

void FindWord(string soFar, string rest, Lexicon &lex, Set::StructT &words)
{
  if (rest.empty()) {
    if (lex.containsWord(soFar)) //this is a bool
       updateSet(soFar, words); //add soFar to referenced Set struct tree
  } else {
   for (int i = 0; i < rest.length(); i++) {
     string remain = rest.substr(0, i) + rest.substr(i+1);
     return FindWord(soFar + rest[i], remain, lex, words); //<-this is where I am confused conceptually
   }
  }
 return; // indicates failure
}

А как же без возвратов

void FindWord(string soFar, string rest, Lexicon &lex, Set::StructT &words)
{
  if (rest.empty()) {
    if (lex.containsWord(soFar)) 
       updateSet(soFar, words); //add soFar to Set memory tree
  } else {
   for (int i = 0; i < rest.length(); i++) {
     string remain = rest.substr(0, i) + rest.substr(i+1);
     FindWord(soFar + rest[i], remain, lex, words); //<-this is where I am confused conceptually
   }
  }
}

person forest.peterson    schedule 05.08.2012    source источник


Ответы (1)


Первый фрагмент кода попробует все перестановки rest, добавленные к начальному значению soFar (вероятно, пустая строка?). Он остановится на первом найденном слове, которое находится в lex. Это слово будет возвращено немедленно, как только оно будет найдено, и поиск будет прерван в этой точке. Если ни одного не было в lex, в конце концов будет возвращена пустая строка, когда все for циклы дойдут до конца.

Второй фрагмент попробует только одно слово: объединение начальных строк soFar и rest. Если эта конкатенированная строка находится в lex, она вызовет вместе с ней updateSet. Затем он вернется, указывая на неудачу. Дальнейший поиск выполняться не будет, поскольку return внутри цикла for является безусловным.

Так что эти две функции совершенно разные. Чтобы второй код вел себя как первый, вам нужно, чтобы он возвращал что-то еще, чтобы указать на успех, и возвращался только из цикла for, когда возвращаемое значение вызова FindWord указывает на успех. Очевидно, что void нельзя использовать для сигнализации failure и success. По крайней мере, для этого вам нужно вернуть значение bool.

И без возврата ваш третий код выполнит исчерпывающий поиск. Будут опробованы все возможные перестановки исходного строкового значения rest, чтобы найти их в лексиконе.

Вы можете визуализировать происходящее следующим образом:

FindWord:   soFar=""     rest=...........
    for:    i=...    rest[i]=a
       call findWord

FindWord:   soFar=a       rest=..........
    for:    i=...    rest[i]=b
       call findWord

FindWord:   soFar=ab       rest=.........
    for:    i=...    rest[i]=c
       call findWord
       if return, the loop will be cut short
       if not, the loop continues and next i will be tried

 ......

FindWord:   soFar=abcdefgh...      rest=z
    for:    i=0      rest[0]=z
       call findWord

FindWord:   soFar=abcdefgh...z      rest=""      // base case
    // for:    i=N/A    rest[i]=N/A
    if soFar is_in lex                           // base case
      then do_some and return soFar  OR  success
      else             return ""     OR  failure

Каждый раз, когда достигается базовый случай (rest пусто), у нас есть n+1 FindWord кадров вызова в стеке для n букв в исходной rest строке.

Каждый раз, когда мы достигаем дна, мы выбираем все буквы из rest. Выполняется проверка, находится ли он в lex, и управление возвращается на один уровень вверх.

Поэтому, если возвратов нет, каждый цикл for будет выполняться до конца. Если возврат безусловный, будет опробована только одна перестановка — тривиальная. Но если возврат условный, все остановится только при первом успехе.

person Will Ness    schedule 05.08.2012
comment
@Will_Ness Я добавил версию без возвратов - она ​​близка к последней рабочей версии, которую я использовал. Можете ли вы объяснить или указать ссылку, которая объясняет цель возврата. - person forest.peterson; 05.08.2012
comment
@forest.peterson не спешите с принятием. :) :) Некоторые/многие люди будут считать, что ваша проблема полностью решена, и даже не будут смотреть на ваш вопрос. :) - person Will Ness; 05.08.2012
comment
@Will_Ness они могли бы провести некоторое время в классе CS, объясняя операторы return - они полагали, что это интуитивно понятно. Я буду читать больше, думать об этом и пробовать другую версию кода, и ждать больше комментариев здесь. В void рекурсии return размещение кажется важным инструментом для настройки алгоритма. - person forest.peterson; 05.08.2012
comment
это помогает, я буду пробовать и ошибаться до конца дня и учиться, чтобы лучше понять - person forest.peterson; 05.08.2012
comment
о return: он возвращается из текущей работающей функции к вызывающей стороне. В такой ситуации говорят, что управление переходит обратно к вызывающей функции. Здесь он возвращается в цикл for. Если в этот момент эта функция тоже решит вернуться, она прерывает цикл. Но если нет, цикл продолжается - пробуется следующий i - следующий символ выбирается из rest - и выполняется следующий вызов функции FindWord с новыми аргументами soFar и rest, создавая новый кадр выполнения (или кадр вызова) для FindWord на стек вызовов во время выполнения. - person Will Ness; 05.08.2012