Рекурсия внутри петель - PullRequest
0 голосов
/ 04 февраля 2020

Итак, моим профессором по рекурсии было задано следующее задание:

Задача 1. Вам даны весы для взвешивания грузов. С левой стороны лежит единственный камень с известным весом W <2N. У вас есть набор из N различных весов, весом 1, 2, 4, ..., 2 <sup>N-1 единиц массы соответственно. Определите, сколько возможных способов размещения некоторых весов по бокам весов, чтобы уравновесить их (привести их в состояние равновесия).

Решение также было дано

#include <stdio.h>

int N;

int no_ways(int W, int index) {
    if (!W)
        return 1;
    if (index == N)
        return 0;

    int ans = 0, i;
    for (i = 0; i * (1 << index) <= W; i++)
        ans += no_ways(W - i * (1 << index), index + 1);

    return ans;
}

void main() {
    int W;
    scanf("%d%d", &N, &W);

    printf("%d\n", no_ways(W, 0));
    return 0;
}

В этом я понял, как тестировались базовые условия, однако я не мог понять рекурсивный вызов внутри для l oop и как значение индекса отличается в каждом рекурсивном вызове.

Любой более простой подход или помочь в понимании этой программы?

PS: я новичок в рекурсии, и мне показалось, что это слишком сложно для понимания

Ответы [ 2 ]

1 голос
/ 22 февраля 2020

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

Проблема, которую решает ваш код, имеет центральное условие, подобное этому:

У вас есть набор весов, взвешивающий 1, 2, 4, ..., 2 N-1 единиц массы соответственно, с произвольным числом каждого веса

и дополнительно вы можете поместить эти веса на одну сторону только весов (ту, которая противоположна камню).

Это позволяет, например, сбалансировать весы с 2- единица измерения камня в двух направлениях, как показывает ответ от grek40: одно решение - одно взвешивание 2, другое - два взвешивания по 1 единице каждый.

Вот как ваш код достигает it.

Параметр W для функции no_ways представляет несбалансированный (часть) вес вашего камня, а параметр index обозначает наименьший вес, который вы можете использовать. Таким образом, чтобы найти все возможные решения, мы называем no_ways(W,0), что соответствует балансу общего веса W со всеми доступными весами.

Два базовых случая: «не осталось несбалансированного веса», что означает, что мы нашли решение, поэтому мы возвращаем 1; и «мы исчерпали допустимый диапазон весов», что означает, что мы не можем найти решение, поэтому мы возвращаем 0.

Затем мы пытаемся расширить частичное решение, пытаясь добавить самые легкие доступные весы к весам. Самый легкий вес - (1 << index), то есть 2 index , поэтому мы умножаем его, увеличивая значения i и вычитая его из W; это делается с помощью:

    for (i = 0; i * (1 << index) <= W; i++)
                      (W - i * (1 << index),          )

, и мы пытаемся сбалансировать оставшиеся W - i * (1 << index) со следующим доступным весом (определяемым следующим значением index), вызывая:

    for (i = 0; i * (1 << index) <= W; i++)
               no_ways(W - i * (1 << index), index + 1)

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

    for (i = 0; i * (1 << index) <= W; i++)
        ans += no_ways(W - i * (1 << index), index + 1);

Сумма возвращается по рекурсии, так что на верхнем уровне мы получаем число всех найденных решений.

Я немного изменил ваш код, чтобы построить и распечатать явное представление каждого найденного решения. Он состоит из массива int stack[] и переменной top, которая указывает свободную позицию в стеке. Первоначально top==0, стек пуст.

Всякий раз, когда for() l oop уменьшает значение до W, он помещает значение в стек и перемещает указатель top, так что рекурсивно вызовы создают решение в массиве. По возвращении из рекурсии мы уменьшаем top, чтобы новая итерация for() могла поместить новое значение в то же место.

Когда у нас новое решение, печатается весь стек.

Вот код:

    int stack[15];
    int top = 0;

    void print_stack() {
        int k;
        for (k = 0; k < top; k ++)
            printf(" %d", stack[k]);
        printf("\n");
    }

    int N;

    int no_ways(int W, int index) {
        if (!W) {
            print_stack();
            return 1;
        }
        if (index == N)
            return 0;

        int ans = 0, i;
        for (i = 0; i * (1 << index) <= W; i++) {
            stack[top ++] = i * (1 << index);
            ans += no_ways(W - i * (1 << index), index + 1);
            top --;
        }

        return ans;
    }

Вы можете легко найти все добавленные мной строки - это строки, содержащие 'stack' или 'top'.

Для случая W = 2, N = 2, исследованный grek40, печатает код:

 0 2
 2
2

В последней строке показано, что найдены два решения: первая - это вес 2, полученный с одним взвешиванием в 2 единицы, и ноль взвешивания в 1 единицу (правильно), а другой - ДВЕ весит одну единицу.

Вот результаты для W = 5 и N = 3:

 1 0 4
 1 4
 3 2
 5
4

Это решения: 5 = 1 + 4 (правильно), 5 = 1 + 2 * 2 (при весе 2 единицы, использованном дважды), 5 = 3 * 1 + 2 (при весе 1 единицы, использованном трижды) и 5 ​​= 5 * 1 (с пятью единицами весит единицу).
Всего найдено решений: 4.

Я тестировал код в онлайн-компиляторе / отладчике по адресу https://www.onlinegdb.com/

РЕДАКТИРОВАТЬ

* 10 94 * Для решения поставленной задачи, то есть:

, имеющий ровно один вес, равный 1 единице, один вес, равный 2 единицам, и т. Д. Через степени от 2 до одного веса 2 N-1 единиц, которые могут быть размещены на обеих сторонах весов, находят баланс

. Вы можете изменить решение следующим образом.

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

W + s 0 × 1 + s 1 × 2 + s 2 × 4 + .. . + s N-1 × 2 N-1 = 0

, выбрав каждый s n термин равно –1, 0 или 1.
Этого можно добиться простой модификацией кода:

int no_ways(int W, int index) {
    if (!W)
        return 1;
    if (index == N)
        return 0;

    int ans = 0, i;
    for (i = -1; i <= 1; i++)   // i equals -1, 0 or 1
        ans += no_ways(W + i * (1 << index), index + 1);

    return ans;
}
1 голос
/ 04 февраля 2020

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

// Example Input:
W = 2, N = 2

// first call
no_ways(2, 0)
// loop0: i = 0 to 2

    // first recursive call
    no_ways(2, 1)
    // loop1: i = 0 to 1

        no_ways(2, 2)
        = 0 (index == N)

        no_ways(0, 2)
        = 1 (W == 0)

    = 1 (sum of recursions in loop1)

    no_ways(1, 1)
    // loop2: i = 0 to 0

        no_ways(1, 2)
        = 0 (index == N)

    = 0 (sum of recursions in loop2)

    no_ways(0, 1)
    = 1 (W == 0)

= 2 (sum of recursions in loop0)

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

Как Дэвид C. Ранкин упоминается в комментариях, этот алгоритм не очень хорош. Он всегда будет достигать глубины рекурсии (число вложенных вызовов), равной N для любого W > 0, даже при том, что было бы возможно обнаружить рано, когда указанный c путь рекурсии не может дать какой-либо ненулевой результат.

Алгоритм написан таким образом, что при увеличении index только W значения, которые можно разделить на 2^index, являются разрешимыми. Так (например) любая рекурсия первого вызова функции, где W нечетное число, никогда не приведет к какому-либо результату, кроме 0, поскольку все веса с index > 0 являются весами четных чисел.

...