具有最小和最大

时间:2017-03-31 17:16:50

标签: c++ arrays algorithm multidimensional-array knapsack-problem

我遇到类似于背包问题的问题,更具体地说是multidimentional variation

我有一堆对象,都有成本,价值和类别。我需要在最大成本下对Knapsack进行优化,但在每个类别中都有特定数量的对象。

我已经成功地在C ++中实现了原始的背包算法,而没有注意这些类别。

当我尝试添加类别时,我发现我可以简单地将其视为一个多维背包问题,每个类别在新维度中的权重为0或1。

我的主要问题是我不仅有一个最大的,例如5个食物类型的物体,但也是最小的,因为我需要完全 5个食物类型的物体。

我无法弄清楚如何在算法中添加最小值。

显然,我可以使用一般情况,其中每个维度都有最大值和最小值,并针对总计进行优化,因为我的所有维度都只有一个范围,因此无论如何最终都会优化值。此外,我可以将值的最小值设置为零,以避免一个维度没有最小值,并且它仍然可以工作。

我在使用C ++,但老实说甚至伪代码也没关系,我只需要算法。

显然,如果可能的话,我也需要快速multidimensional variation

以下是测试用例的示例。由于这主要是一个优化问题,实例很大,但它应该适用于任何实例大小。可能的类别数量和类别字段数量是固定的。

你有一个最多可以容纳100个单位重量的背包,以及1000个物体的列表,每个物体都有一个值,一个重量和一个类型。你特别需要携带10种食物,15种衣物和5种工具。每个对象都有一个完全任意(但大于0)的美元值和单位的权重。我需要找到关于最大重量和每种类型物品的具体数量的最佳配置。

对象列表将始终包含至少一个有效配置,这意味着它将始终至少有足够的每种类型的对象,最终在最大权重下,所以我不必为“没有回答“案例。我只需找到(可能)大量可用物品的最佳答案。

2 个答案:

答案 0 :(得分:2)

确切地知道每个类别中可以选择多少项是一个很大的限制因素。考虑最简单的情况,即有一个类别。您可以选择恰好N个对象来最大化成本和[v_i x_i]的值[w_i x_i]< W,其中x_i等于0或1(遵循维基百科的表示法)。新约束是sum [x_i] = N.此约束可以通过在动态编程中添加另一个维度来包含在问题中,但是显式检查解决方案是否有效并且具有所需元素的数量。

香草背包问题

以下是一个简短的演示:以记忆的方式作为标准0/1背包问题的解决方案的起点:

#include <cstdio>
#include <iostream>
#include <vector>
#include <map>
#include <algorithm>

using uint = unsigned int;

template <typename T>
struct item {
    T value;
    uint weight;
};

template <typename T>
T knapSack(uint W, const std::vector< item<T> >& items) {

    std::map< std::pair<uint, uint>, T> cache;

    std::function<T(uint, uint)> recursion;
    recursion = [&] (uint n, uint w) {
        if (n == 0)
            return 0;
        auto it = cache.find(std::make_pair(n,w));
        if (it != cache.end())
            return it->second;
        T _v = items[n-1].value;
        uint _w = items[n-1].weight;
        T nextv;
        if (_w <= w)
            nextv = std::max(_v + recursion(n-1,w-_w),recursion(n-1,w));
        else
            nextv = recursion(n-1,w);
        cache.insert(std::make_pair(std::make_pair(n,w),nextv));
        return nextv;   
    };

    return recursion(items.size(),W);
}

我的实现(使用递归lambda函数)强调可读性优于最优性。选择索引为&lt; N和权重之和&lt; W是索引&lt;的对象的选择。 N-1和权重之和&lt; W或N-1处的对象以及索引<1的对象。 N-1和权重之和&lt; W - w [N-1]。

具有固定所需对象数量的一个类别的背包问题

我们可以继续添加一个新的限制来跟踪所选元素的数量。我们将通过注意每个递归步骤中新对象的选择比之前的对象多0或1个元素来实现这一点,就像它具有相同或更大的权重总和一样 - 即,选择索引为&lt; N和权重之和&lt; W是索引&lt;的K对象的选择。 N-1和权重之和&lt; W或N-1处的物体与指数<1的K-1物体一起。 N-1和权重之和&lt; W - w [N-1]。但是,我们也想跟踪违规行为 - 例如,我们找不到索引为&lt;当K> N时为N.在这种情况下,我们应该报告最大可能值为0,因为选择是不可能的,但我们应该将其标记为“无效”,以区别于递归的平凡基本情况。此外,任何试图将其用作子解决方案的链上方的任何解决方案也应标记为无效。因此,我们将返回类型从简单值更改为一对值和布尔值。作为基本案例的一部分,我们将所有K> N的条目标记为最大值为0但无效:

template <typename T>
std::pair<T,bool> knapSackConstrained(uint W, uint K, const std::vector< item<T> >& items) {

    std::map< std::tuple<uint, uint, uint>, std::pair<T,bool> > cache;

    std::function<std::pair<T, bool>(uint, uint, uint)> recursion;
    recursion = [&] (uint n, uint w, uint k) {
        if (k > n)
            return std::make_pair(0,false);
        if (n == 0 || k == 0)
            return std::make_pair(0,true);
        auto it = cache.find(std::make_tuple(n,w,k));
        if (it != cache.end())
            return it->second;
        T _v = items[n-1].value;
        uint _w = items[n-1].weight;
        T nextv;
        bool nextvalid = true;
        if (_w <= w) {
            auto take = recursion(n-1,w-_w,k-1);
            auto reject = recursion(n-1,w,k);
            if (take.second and reject.second) {
                nextv = std::max(_v + take.first,reject.first);
            } else if (take.second) {
                nextv = _v + take.first;
            } else if (reject.second) {
                nextv = reject.first;
            } else {
                nextv = 0;
                nextvalid = false;
            }   
        } else {
            std::tie(nextv,nextvalid) = recursion(n-1,w,k);
        }
        std::pair<T,bool> p = std::make_pair(nextv,nextvalid);
        cache.insert(std::make_pair(std::make_tuple(n,w,k),p));
        return p;   
    };

    return recursion(items.size(),W,K);
}

这是一个运行此代码及其输出的简单主程序:

int main(int argc, char *argv[]) {
    std::vector< item<int> > items = {{60,10},{10,6},{10,6}};
    int  j = 13;
    std::cout << "Unconstrained: " << knapSack(j,items) << std::endl;
    for (uint k = 1; k <= items.size(); ++k) {
        auto p = knapSackConstrained(j,k,items);
        std::cout << "K = " << k << ": " << p.first;
        if (p.second)
            std::cout << std::endl;
        else
            std::cout << ", no valid solution" << std::endl;
    }
    return 0;
}

% OUTPUT %
Unconstrained: 60
K = 1: 60
K = 2: 20
K = 3: 0, no valid solution

由于3个权重之和已经大于阈值,因此无法解决所有这三个问题。

具有固定所需对象数量的多个类别的背包问题

以上只能部分解决您的问题,因为您有多个类别而不是一个类别。但是,我相信这可以扩展到多维而无需太多额外的工作。实际上,我怀疑以下代码是多维案例的正确策略,模数错误 - 它需要一些好的测试用例进行验证。单个参数K被替换为类别编号的向量,并且项结构被赋予类别字段。基本案例必须考虑每个可能的K> N案例(对于每个类别),此外必须扩展(i-1)st权重小于W的检查以检查至少有一个项目需要更多(i-1)st类别。

#include <cstdio>
#include <iostream>
#include <vector>
#include <map>
#include <algorithm>

using uint = unsigned int;

template <typename T>
struct item {
    T value;
    uint weight;
    uint category;
};

template <typename T>
std::pair<T,bool> knapSack(uint W, const std::vector<uint>& K, const std::vector< item<T> >& items) {

    std::map< std::tuple<uint, uint, std::vector<uint> >, std::pair<T,bool> > cache;

    std::function<std::pair<T, bool>(uint, uint, std::vector<uint>)> recursion;
    recursion = [&] (uint n, uint w, std::vector<uint> k) {

        auto it = cache.find(std::make_tuple(n,w,k));
        if (it != cache.end())
            return it->second;

        std::vector<uint> ccount(K.size(),0);
        for (uint c = 0; c < K.size(); ++c) {
            for (uint i = 0; i < n; ++i) {
                if (items[i].category == c)
                    ++ccount[c];
            }
        }
        for (uint c = 0; c < k.size(); ++c) {
            if (k[c] > ccount[c]) {
                auto p = std::make_pair(0,false);
                cache.insert(std::make_pair(std::make_tuple(n,w,k),p));
                return p;
            }
        }

        uint sumk = 0; for (const auto& _k : k) sumk += _k;
        if (n == 0 || sumk == 0) {
            auto p = std::make_pair(0,true);
            cache.insert(std::make_pair(std::make_tuple(n,w,k),p));
            return p;
        }

        T _v = items[n-1].value;
        uint _w = items[n-1].weight;
        uint _c = items[n-1].category;
        T nextv;
        bool nextvalid = true;
        if (_w <= w and k[_c] > 0) {
            std::vector<uint> subk = k;
            --subk[_c];
            auto take = recursion(n-1,w-_w,subk);
            auto reject = recursion(n-1,w,k);
            if (take.second and reject.second) {
                nextv = std::max(_v + take.first,reject.first);
            } else if (take.second) {
                nextv = _v + take.first;
            } else if (reject.second) {
                nextv = reject.first;
            } else {
                nextv = 0;
                nextvalid = false;
            }   
        } else {
            std::tie(nextv,nextvalid) = recursion(n-1,w,k);
        }
        std::pair<T,bool> p = std::make_pair(nextv,nextvalid);
        cache.insert(std::make_pair(std::make_tuple(n,w,k),p));
        return p;   
    };

    return recursion(items.size(),W,K);
}

int main(int argc, char *argv[]) {
    std::vector< item<int> > items = {{60,10,0}, {100,20,1}, {120,30,0}, {140,35,1}, {145,40,0}, {180,45,1}, {160,50,1}, {170,55,0}};
    int  j = 145;
    for (uint k1 = 0; k1 <= items.size(); ++k1) {
        for (uint k2 = 0; k2 <= items.size(); ++k2) {
            auto p = knapSack(j,std::vector<uint>({k1,k2}),items);
            if (p.second)
                std::cout << "K0 = " << k1 << ", K1 = " << k2 << ": " << p.first << std::endl;
        }
    }
    return 0;
}

% OUTPUT (with comments) %

K0 = 0, K1 = 0: 0
K0 = 0, K1 = 1: 180 // e.g. {} from 0, {180} from 1
K0 = 0, K1 = 2: 340 // e.g. {} from 0, {160,180} from 1
K0 = 0, K1 = 3: 480 // e.g. {} from 0, {140,160,180} from 1
K0 = 1, K1 = 0: 170 // e.g. {170} from 0, {} from 1
K0 = 1, K1 = 1: 350 // e.g. {170} from 0, {180} from 1
K0 = 1, K1 = 2: 490 // e.g. {170} from 0, {140, 180} from 1
K0 = 1, K1 = 3: 565 // e.g. {145} from 0, {100, 140, 180} from 1
K0 = 2, K1 = 0: 315 // e.g. {145,170} from 0, {} from 1
K0 = 2, K1 = 1: 495 // e.g. {145,170} from 0, {180} from 1
K0 = 2, K1 = 2: 550 // e.g. {60,170} from 0, {140,180} from 1
K0 = 2, K1 = 3: 600 // e.g. {60,120} from 0, {100,140,180} from 1
K0 = 3, K1 = 0: 435 // e.g. {120,145,170} from 0, {} from 1
K0 = 3, K1 = 1: 535 // e.g. {120,145,170} from 0, {100} from 1
K0 = 3, K1 = 2: 605 // e.g. {60,120,145} from 0, {100,180} from 1
K0 = 4, K1 = 0: 495 // e.g. {60,120,145,170} from 0, {} from 1

对于具有两个类别的给定项目集,输出似乎是正确的,尽管我的手动检查可能未能发现某些问题[此答案的早期版本确实存在一些错误]。所有未打印的案例都是没有解决方案的案例。

返回所选对象的集合

如果您希望函数返回所选对象集,原则上这不是障碍 - 代码变得更加混乱。最容易理解的事情是简单地将std::set<std::size_t>添加到由recursionknapSack返回的对象元组中,并存储在缓存中,表示索引的集合。选择的对象。每次添加新对象时,都可以扩充此集合。结果代码涉及大量复制整数集,并且可能远非最佳 - 更好的解决方案可能涉及静态布尔向量,其条目打开和关闭。但是,它有效并且有意义,所以这里是:

#include <cstdio>
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <algorithm>

using uint = unsigned int;

template <typename T>
struct item {
    T value;
    uint weight;
    uint category;
};

template <typename T>
std::tuple<T,bool,std::set<size_t> > knapSack(uint W, std::vector<uint> K, const std::vector< item<T> >& items) {

    std::map< std::tuple<uint, uint, std::vector<uint> >, std::tuple<T,bool,std::set<std::size_t> > > cache;

    std::function<std::tuple<T,bool,std::set<std::size_t> >(uint, uint, std::vector<uint>&)> recursion;

    recursion = [&] (uint n, uint w, std::vector<uint>& k) {

        auto it = cache.find(std::make_tuple(n,w,k));
        if (it != cache.end())
            return it->second;

        std::vector<uint> ccount(K.size(),0);
        for (uint i = 0; i < n; ++i) {
            ++ccount[items[i].category];
        }

        for (uint c = 0; c < k.size(); ++c) {
            if (k[c] > ccount[c]) {
                auto p = std::make_tuple(0,false,std::set<std::size_t>{});
                cache.insert(std::make_pair(std::make_tuple(n,w,k),p));
                return p;
            }
        }
        uint sumk = 0; for (const auto& _k : k) sumk += _k;
        if (n == 0 || sumk == 0) {
            auto p = std::make_tuple(0,true,std::set<std::size_t>{});
            cache.insert(std::make_pair(std::make_tuple(n,w,k),p));
            return p;
        }

        T _v = items[n-1].value;
        uint _w = items[n-1].weight;
        uint _c = items[n-1].category;
        T nextv;
        bool nextvalid = true;
        std::set<std::size_t> nextset;
        if (_w <= w and k[_c] > 0) {
            --k[_c];
            auto take = recursion(n-1,w-_w,k);
            ++k[_c];
            auto reject = recursion(n-1,w,k);
            T a = _v + std::get<0>(take);
            T b = std::get<0>(reject);
            if (std::get<1>(take) and std::get<1>(reject)) {
                nextv = std::max(a,b);
                if (a > b) {
                    nextset = std::get<2>(take);
                    nextset.insert(n-1);
                } else {
                    nextset = std::get<2>(reject);
                }
            } else if (std::get<1>(take)) {
                nextv = a;
                nextset = std::get<2>(take);
                nextset.insert(n-1);
            } else if (std::get<1>(reject)) {
                nextv = b;
                nextset = std::get<2>(reject);
            } else {
                nextv = 0;
                nextvalid = false;
                nextset = {};
            }   
        } else {
            std::tie(nextv,nextvalid,nextset) = recursion(n-1,w,k);
        }
        auto p = std::make_tuple(nextv,nextvalid,nextset);
        cache.insert(std::make_pair(std::make_tuple(n,w,k),p));
        return p;   
    };

    return recursion(items.size(),W,K);
}

int main(int argc, char *argv[]) {
    std::vector< item<int> > items = {{60,10,0}, {100,20,1}, {120,30,0}, {140,35,1}, {145,40,0}, {180,45,1}, {160,50,1}, {170,55,0}};
    int  j = 145;
    for (uint k1 = 0; k1 <= items.size(); ++k1) {
        for (uint k2 = 0; k2 <= items.size(); ++k2) {
            auto p = knapSack(j,std::vector<uint>({k1,k2}),items);
            if (std::get<1>(p)) {
                std::cout << "K0 = " << k1 << ", K1 = " << k2 << ": " << std::get<0>(p);
                std::cout << "; contents are {";
                for (const auto& index : std::get<2>(p))
                    std::cout << index << ", ";
                std::cout << "}" << std::endl;
            }
        }
    }
    return 0;
}

这是

的输出
K0 = 0, K1 = 0: 0; contents are {}
K0 = 0, K1 = 1: 180; contents are {5, }
K0 = 0, K1 = 2: 340; contents are {5, 6, }
K0 = 0, K1 = 3: 480; contents are {3, 5, 6, }
K0 = 1, K1 = 0: 170; contents are {7, }
K0 = 1, K1 = 1: 350; contents are {5, 7, }
K0 = 1, K1 = 2: 490; contents are {3, 5, 7, }
K0 = 1, K1 = 3: 565; contents are {1, 3, 4, 5, }
K0 = 2, K1 = 0: 315; contents are {4, 7, }
K0 = 2, K1 = 1: 495; contents are {4, 5, 7, }
K0 = 2, K1 = 2: 550; contents are {0, 3, 5, 7, }
K0 = 2, K1 = 3: 600; contents are {0, 1, 2, 3, 5, }
K0 = 3, K1 = 0: 435; contents are {2, 4, 7, }
K0 = 3, K1 = 1: 535; contents are {1, 2, 4, 7, }
K0 = 3, K1 = 2: 605; contents are {0, 1, 2, 4, 5, }
K0 = 4, K1 = 0: 495; contents are {0, 2, 4, 7, }

算法复杂性

这不是我的强项,但我相信运行时复杂度是假多项式,因为该算法与标准背包算法非常相似。

答案 1 :(得分:0)

我实际上没有直接回答你的问题,无论是伪代码还是特定语言的算法的实际实现,但我在这里可以做的是给你一个我认为与可能有助于指导您开发工作算法的主题:

虽然其中许多可能不完全是Knapsack算法问题;我认为这些主题可能与您的算法的整体成就有关。我认为这些将是有用的,因为首先,背包问题本身是具有许多实现和方案的分区算法的变体。此外,并行编程和多线程编程的使用也可能有助于大型数据集。我发现这些少数几本书和白皮书我觉得这本书很精彩。

基本上你有一个具有卷K的背包KV需要细分为较小的{KV1, KV2, ... KVn}卷,其中包含每种类型都有{{1}的不同数据类型},valueweight,项目category or classification表示其消耗的音量部分。您还有一个限制,即weight限制了您必须至少有一个[min, max]category的限制。然后,使用这些参数作为基本方案,您希望最大化classification以尽可能多地包含KV,但希望尽可能高效地执行此操作,以最少的时间充满希望{{1避免elements

查看其他独特的算法,例如分区算法,人口密度和增长,图像压缩等,可以让您深入了解您的具体问题,因为这些算法的整体基础和注意事项本质上是相似的。