简体   繁体   中英

Recursion to iteration conversion using dynamic programming

    public static int n;
    public static int w;
    public static int[] s;
    public static int[] p;

    static void Main(string[] args)
    {
        n = 5;
        w = 5;

        s = new int[n + 1];
        p = new int[n + 1];
        Random rnd = new Random();

        for (int i = 1; i <= n; i++)
        {

            s[i] = rnd.Next(1, 10);
            p[i] = rnd.Next(1, 10);
        }

        Console.WriteLine(F_recursion(n, w));
        Console.WriteLine(DP(n, w));
    }

    // recursive approach
    public static int F_recursion(int n, int w)
    {
        if (n == 0 || w == 0)
            return 0;
        else if (s[n] > w)
            return F_recursion(n - 1, w);
        else
        {                          
            return Math.Max(F_recursion(n - 1, w), (p[n] + F_recursion(n - 1, w - s[n])));
        }
    }

    // iterative approach
    public static int DP(int n, int w)
    {
        int result = 0;

        for (int i = 1; i <= n; i++)
        {

            if (s[i] > w)
            {
                continue;
            }
            else
            {                   
                result += p[i];
                w = w - s[i];
            }
        }

        return result;
    }

I need to convert F_recursion function to iterative. I currently written following function DP that sometimes works but not always. I learned that problem is in F_recursion(n - 1, w - s[n] ) I have no idea how to make w - s[n] work correctly in iterative solution. If change w - s[n] and w - s[i] to only w then program always work.

In Console:

s[i] = 2 p[i] = 3
-------
s[i] = 3 p[i] = 4
-------
s[i] = 5 p[i] = 3
-------
s[i] = 3 p[i] = 8
-------
s[i] = 6 p[i] = 6
-------
Recursive:11
Iteration:7

but sometimes it works

s[i] = 5 p[i] = 6
-------
s[i] = 8 p[i] = 1
-------
s[i] = 3 p[i] = 5
-------
s[i] = 3 p[i] = 1
-------
s[i] = 7 p[i] = 7
-------
Recursive:6
Iteration:6

The following approach might be useful, when bigger numbers are involved (specially for s ) and consequently a 2 dimensional array would be unnecessary big and only a few w values would actually be used in computing the result.

The idea: precompute possible w values, by starting at w and for each i in [n, n-1, ..., 1] determine the values w_[i] , where w_[i+1] >= s[i] without duplicates. Then iterate i_n over n and compute sub-results only for valid w_[i] values.

I chose an array of Dictionary as datastructure, since it's relatively easy to design sparse data this way.

public static int DP(int n, int w)
{
    // compute possible w values for each iteration from 0 to n
    Stack<HashSet<int>> validW = new Stack<HashSet<int>>();
    validW.Push(new HashSet<int>() { w });
    for (int i = n; i > 0; i--)
    {
        HashSet<int> validW_i = new HashSet<int>();
        foreach (var prevValid in validW.Peek())
        {
            validW_i.Add(prevValid);
            if (prevValid >= s[i])
            {
                validW_i.Add(prevValid - s[i]);
            }
        }
        validW.Push(validW_i);
    }

    // compute sub-results for all possible n,w values.
    Dictionary<int, int>[] value = new Dictionary<int,int>[n + 1];
    for (int n_i = 0; n_i <= n; n_i++)
    {
        value[n_i] = new Dictionary<int, int>();
        HashSet<int> validSubtractW_i = validW.Pop();
        foreach (var w_j in validSubtractW_i)
        {
            if (n_i == 0 || w_j == 0)
                value[n_i][w_j] = 0;
            else if (s[n_i] > w_j)
                value[n_i][w_j] = value[n_i - 1][w_j];
            else
                value[n_i][w_j] = Math.Max(value[n_i - 1][w_j], (p[n_i] + value[n_i - 1][w_j - s[n_i]]));
        }
    }

    return value[n][w];
}

It's important to understand that some space and computation is "wasted" in order to precompute possible w values and to support the sparse data structures. So this approach might perform bad for large data sets with small values in s , where most w values will be possible sub-results.

After some more thought I realized, if space is a concern, you can actually throw away the sub-results of everything except the previous outer loop iteration, since the recursion in this algorithm follows a strict n-1 pattern. However, I'm not including this into my code for now.

Your approach does not work because your dynamic programmig state space (which apparently is only one variable) does not match the signature of the recursive method. The goal of the dynamic programming approach should be to define and fill a state space such that all results for evaluation are available when needed. On inspection of the recursive method, notice that the recursive calls of F_recursion may change both arguments, n and w . This is an indication that a two-dimensional state space should be used.

The first argument (which apparently limits the range of items) can range from 0 to n while the second argument (which apparently is some bound for the total of an item property) can range from 0 to w .

You should define a two dimensional state space

int[,] value = new int[n,w];

for accomodation of the values. Next, you should initialize the values to undefined; you can use the value Int32.MaxValue for this, because it will behave in a suitable way if the minimum with some different value is calculated.

Next, the iterative version of the algorithm shoud use two loops which iterate in a forwad manner, unlike the recursive iteration which decreases the arguments.

for (int i = 0; i < n; i++)
{
    for (int j = 0; j < w; j++)
    {
        // logic for the recurrence relation goes here
    }
}

In the innermost block you can use a modified version of the recurrence relation. Instead of using recursive calls, you access values which are stored in value ; instead of returning values, you write the values to value .

Semantically this is the same as memoization , but instead of using actual recursive calls, the order of evaluation asserts that necessary values always exist, making additional logic unneccessary.

Once the state space is filled, you have to examine its last state (namely the part of the array where the first index is n-1 ) to determine the maximal value for the entire input.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM