使用递归算法求解背包

Posted

技术标签:

【中文标题】使用递归算法求解背包【英文标题】:Solving Knapsack using recursive algorithm 【发布时间】:2016-03-29 14:59:35 【问题描述】:

所以,我正在尝试从我们的教科书中实现这个算法。

我写了这个:

// Knapsack_memoryfunc.cpp : Defines the entry point for the console application.
//Solving Knapsack problem using dynamic programmig and Memory function

#include "stdafx.h"
#include "iostream"
#include "iomanip"
using namespace std;

int table[20][20] =  0 ;
int value, n, wt[20], val[20], max_wt;

// ---CONCERNED FUNCTION-----

int MNSack(int i, int j)

    value = 0;
    if (table[i][j] < 0)
        if (j < wt[i])
            value = MNSack(i - 1, j);
        else
            value = fmax(MNSack(i - 1, j), val[i] + MNSack(i - 1, j - wt[i]));

    table[i][j] = value;
    return table[i][j];


// --------------------------

void items_picked(int n, int max_wt)

    cout << "\n Items picked : " << endl;
    while (n > 0)
    
        if (table[n][max_wt] == table[n - 1][max_wt])   // if value doesnot change in table column-wise, item isn't selected
            n--;                                        // n-- goes to next item
        else                                            // if it changes, it is selected
        
            cout << " Item " << n << endl;
            max_wt -= wt[n];                            // removing weight from total available (max_wt)
            n--;                                        // next item
        
    


int main()


    cout << " Enter the number of items : ";
    cin >> n;
    cout << " Enter the Maximum weight : ";
    cin >> max_wt;
    cout << endl;
    for (int i = 1; i <= n; i++)
    
        cout << " Enter weight and value of item " << i << " : ";
        cin >> wt[i] >> val[i];
    

    for (int i = 0; i <= n; i++)
        for (int j = 0; j <= max_wt; j++)
            table[i][j] = 0;

    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= max_wt; j++)
            table[i][j] = -1;

    cout << " Optimum value : " << MNSack(n, max_wt);

    cout << " \n Table : \n";
    for (int i = 0; i <= n; i++)
    
        for (int j = 0; j <= max_wt; j++)
            if (table[i][j] == -1)
                cout << setw(5) << "-";
            else
                cout << setw(5) << table[i][j];
        cout << endl;
    

    items_picked(n, max_wt);


    return 0;

这是问题和输出:

在某些地方,例如最优值,它似乎是正确的,但并不完全可以接受。 我试过调试它,但递归函数很难。有人可以帮忙吗?

【问题讨论】:

嘿嘿嘿,我觉得HenryLee的回答是对的,不过还是想给你点东西留着。你的代码有点糟糕,你解决这个问题的方式在程序员下被称为记忆化。这是一篇漂亮的博文,对我帮助很大,可以让你的代码更漂亮。 programminggenin.blogspot.de/2013/01/memoization-in-c.html @Mehno 你能详细说明一下吗?据我了解,该算法仅计算所需的值。我怎样才能更好地实现它? @Mehno 建议的是一种让你的编码风格更好的技术。你的算法没有什么不好的。但是,如果您有兴趣,我可以向您展示如何使用自下而上的动态规划来解决相同的问题,它只需要很少的行数,并且使代码更好。 @HenryLee 正确。算法很棒。我只是认为,对矩阵使用全局变量有点难看。没有什么不好的。我只是认为指出这种技术会有所帮助。 @HenryLee 如果你有一个链接或其他东西,我会很高兴。我也许能学到新东西^^ 【参考方案1】:
int MNSack(int i, int j)

    value = 0;
    if (table[i][j] < 0)
    
        if (j < wt[i])
            value = MNSack(i - 1, j);
        else
            value = max(MNSack(i - 1, j), val[i] + MNSack(i - 1, j - wt[i]));

        table[i][j] = value;
    
    return table[i][j];

问题就在这里。当你的表项大于等于 0 时,你会跳过递归,但仍将表项设置为 0,如果你的表项大于 0,这将是不对的。

您只需要在需要更改时更新表格项,因此将其放在大括号中即可解决此问题。

【讨论】:

【参考方案2】:

自下而上的解决方案。

#include <iostream>
#include <algorithm>
#include <iomanip>
using namespace std;


int main()

    int table[20][20] =  0 ;
    int value, n, wt[20], val[20], max_wt;

    cout << " Enter the number of items : ";
    cin >> n;
    cout << " Enter the Maximum weight : ";
    cin >> max_wt;
    cout << endl;
    for (int i = 1; i <= n; i++)
    
        cout << " Enter weight and value of item " << i << " : ";
        cin >> wt[i] >> val[i];
    

    // Initialization
    for (int i = 0; i <= n; i++)
        for (int j = 0; j <= max_wt; j++)
            table[i][j] = 0;

    // In practice, this can be skipped in a bottom up solution
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= max_wt; j++)
            table[i][j] = -1;

    for (int i = 1; i <= n; i++)
    
        for (int j = 1; j <= max_wt; j++)
        
            if (j < wt[i])
                table[i][j] = table[i - 1][j];
            else
                table[i][j] = max(table[i - 1][j], val[i] + table[i - 1][j - wt[i]]);
        
    

    cout << " Optimum value : " << table[n][max_wt] << endl;

    cout << " \n Table : \n";
    for (int i = 0; i <= n; i++)
    
        for (int j = 0; j <= max_wt; j++)
            if (table[i][j] == -1)
                cout << setw(5) << "-";
            else
                cout << setw(5) << table[i][j];
        cout << endl;
    

    return 0;

您可以看到这将递归更改为循环,因此避免了全局变量。它还使代码更简单,因此您可以避免检查表项是否有效(在您的示例中等于 -1)。

这种解决方案的缺点是,它总是遍历所有可能的节点。但它每项获得更好的系数,因为递归和双重检查表项的成本更高。自顶向下和自底向上都具有相同的复杂度顺序 O(n^2),很难说哪个更快。

【讨论】:

嘿!在探索递归方法之前,我实际上使用了这种精确的方法来解决问题。是否可以使用递归而不使用全局变量来做到这一点? @LonelyC 很高兴听到这个消息。在没有全局变量的情况下总是可以做到这一点,只需将变量作为参数传递给每个函数。请注意,如果您需要更改参数的值,则必须改为传递变量的引用(或 C 中的指针)。在这个例子中,我们只需要改变表中的值,并且由于表是作为指针传递的,所以我们不必担心。

以上是关于使用递归算法求解背包的主要内容,如果未能解决你的问题,请参考以下文章

C++经典算法问题:背包问题(迭代+递归算法)!含源码示例

递归迭代和分治法

背包问题的算法

C语言 背包问题 递归算法

0/1背包-递归算法

算法之使用递归求解全排列