Превратить рекурсивную функцию в цикл for?

Каждая ли рекурсивная функция имеет эквивалент цикла for? (Оба достигают того же результата).

У меня есть эта рекурсивная функция:

private static boolean recur(String word, int length) {
    if(length == 1 || length == 2)
        return false;
    if(length == 0)
        return true;
    if(words[length].contains(word.substring(0, length)))
        return recur(word.substring(length), word.length() - length);
    return recur(word, length-1);
}

Учитывая, что words — это Set[], и где words[i] = множество слов длины i.

Что я пытаюсь сделать, так это: инициировать рекурсию со словом (скажем, «stackoverflow», без пробелов), я пытаюсь найти, можно ли это слово разрезать на подслова («стек», «над», «поток») .. минимальная длина подслова равна 3, и учитывая, что подслово длины i находится в наборе words[i].

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

Вам нужно больше информации??

Спасибо.


person Mazyod    schedule 10.02.2011    source источник


Ответы (5)


Хвостовую рекурсию всегда можно развернуть в цикл, и ваш код довольно близок к хвостовой рекурсии, так что да.

private static boolean recur(String word, int length) {
    if(length == 1 || length == 2)
        return false;
    if(length == 0)
        return true;
    int nextLength;
    String nextWord;
    if(words[length].contains(word.substring(0, length))) {
      nextWord = word.substring(length);
      nextLength = word.length() - length;
    } else {
      nextWord = word;
      nextLength = length - 1;
    }
    return recur(nextWord, nextLength);
}

Теперь это правильная хвостовая рекурсия. Теперь, чтобы превратить его в цикл:

private static boolean recur(String word, int length) {
    int nextLength = length;
    String nextWord = word;
    while( true ) {
        if(nextLength == 1 || nextLength == 2)
            return false;
        if(nextLength == 0)
            return true;
        if(words[nextLength].contains(nextWord.substring(0, nextLength))) {
            nextWord = nextWord.substring(nextLength);
            nextLength = nextWord.length() - nextLength;
        } else {
            nextWord = nextWord;
            nextLength = nextLength - 1;
        }
    }
}

Обратите внимание, что этот код можно дополнительно оптимизировать, я просто хотел продемонстрировать «автоматический» метод превращения рекурсии в цикл.

person biziclop    schedule 10.02.2011
comment
Это не только близко к хвостовой рекурсии. Есть два рекурсивных вызова, и оба они выполняются в последнюю очередь. Хвостовая рекурсия не означает, что существует только один рекурсивный вызов, который является последней строкой функции (как в вашем собственном примере). - person musiKk; 10.02.2011
comment
Ну, автомат - это ИМЕННО то, что я искал, по крайней мере, пока, спасибо. И TBH, мне это нужно для решения проблемы программирования, поэтому показ мне, как его можно оптимизировать, поможет мне избежать ошибок превышения срока;) - person Mazyod; 10.02.2011
comment
@musiKk Мы узнали более строгое определение хвостовой рекурсии в университете, но я проверил его, и вы правы. Этапы преобразования все те же. - person biziclop; 10.02.2011
comment
@Mazyod Jaleel См. ответ @Dave Hinton. Это не столько оптимизация, сколько удаление визуальных помех, но все же. - person biziclop; 10.02.2011
comment
@biziclop: можете ли вы помочь мне с этой рекурсией - stackoverflow.com/questions/13229722/ . Я думаю, что это похоже на эту проблему. Я не уверен, что это можно назвать хвостовой рекурсией. Рекурсия находится в конце каждого оператора if. - person Ashwin; 08.11.2012

На общий вопрос: Да, каждую рекурсию можно превратить в цикл. Вы можете явно смоделировать стек, созданный и используемый рекурсией, и изменить его в цикле.

Для конкретной проблемы:

Глядя на свой код как на функцию и игнорируя побочные эффекты, я думаю, вы могли бы просто вернуть false.

Если вам действительно нужны разделенные слова, попробуйте следующее:

have a list with the word to split.
iterate until list after iteration is the same as before.
    iterate over list
        if the word can be split, replace it in the list with its parts
    end of loop
end of loop
person Jens Schauder    schedule 10.02.2011

Краткий ответ: в целом можно, в данном случае можно легко, но если вы исправите ошибку в логике своего кода, вам придется беспокоиться о поддержке стека самостоятельно.

Длинный ответ:

Во-первых, итеративная версия вашего рекурсивного метода:

private static boolean recur(String word, int length) {
    while(true) {
        if(length == 1 || length == 2)
            return false;
        if(length == 0)
            return true;
        if(words[length].contains(word.substring(0, length))) {
            int newlen = word.length() - length;
            word = word.substring(length);
            length = newlen;
        }
        else {
            --length;
        }
    }
}

Это работает, заменяя рекурсивный вызов присваиванием параметрам и помещая все это в цикл.

Но, как и ваш исходный код, он содержит ошибку!

(Я не могу придумать настоящие слова, с которыми работает эта ошибка, поэтому представьте, что мои придуманные слова — настоящие слова.)

Предположим, ваше длинное слово — ABCDEFGH, и что ABCD, EFGH и ABCDE — все слова, а FGH — нет. recur сначала ищет самое длинное слово, поэтому оно будет соответствовать ABCDE, затем обнаруживает, что FGH не является словом, и говорит вам, что ABCDEFGH нельзя разрезать на подслова.

Но его можно разделить на ABCD и EFGH! Он пропускает более короткое начальное совпадение, потому что вы не рассматриваете другие варианты после того, как нашли совпадение. (И если бы ваш код начинался с поиска более короткого совпадения, он все равно не работал бы, если бы ABC было словом, а DEFGH — нет.)

So:

private static boolean recur(String word, int length) {
    if(length == 1 || length == 2)
        return false;
    if(length == 0)
        return true;
    return (words[length].contains(word.substring(0, length)))
            && recur(word.substring(length), word.length() - length))
           || recur(word, length-1);
}

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

person dave4420    schedule 10.02.2011
comment
В общем, вы можете. Все, что вам нужно сделать, это подумать о разделении между рекурсивным и нерекурсивным, как о разделении между неявным или явным стеком. - person R. Martinho Fernandes; 10.02.2011
comment
По какой-то причине я пропустил этот особый случай, и вы избавили меня от необходимости его находить! но тем не менее, это не ответ на вопрос .. так что спасибо, но я не могу выбрать его как лучший ответ .. - person Mazyod; 10.02.2011
comment
@Martinho Совершенно верно --- я хотел указать, что это было не так просто, когда исходная функция не была хвостовой рекурсией, и в спешке написал ее неправильно :-( - person dave4420; 10.02.2011
comment
да, легко понять, почему это возможно (вам просто нужно подумать о стеке), но не так просто понять, как это возможно, если только это не хвост рекурсия. - person R. Martinho Fernandes; 10.02.2011

Я отвечаю на ваш первый вопрос: да, у каждой рекурсивной функции есть итеративный эквивалент. Подробнее.

Это также отчасти связано с CPS (не совсем потому, что хвостовые вызовы на самом деле не поддерживаются популярными виртуальными машинами Java). Преобразование хвостовой рекурсивной функции (например, в вопросе) должно быть особенно простым.

person musiKk    schedule 10.02.2011

@biziclop демонстрирует, как сделать рефакторинг кода нерекурсивным. Я бы пошел дальше и сократил код так, чтобы.

  • length больше не передается в качестве аргумента.
  • Два цикла используются для упрощения логики.
  • измените значение локального слова, чтобы упростить его.

код

private static boolean recur(String word) {
    LOOP: for(;;) {
        for (int len = word.length(); len >= 3; len--)
            if (words[len].contains(word.substring(0, len))) {
                word = word.substring(len);
                continue LOOP;
            }
        return word.length() == 0;
    }
}
person Peter Lawrey    schedule 10.02.2011