是否有一种记忆效率的方法来探索从输入的排列生成的解决方案?

时间:2014-01-31 21:11:05

标签: c++ combinatorics

编辑我可能已经解决了这个问题,请在下面评论我的答案。

我有std::vector<Problem> problems,我需要生成相应的std::vector<Solution> solutions

我有一个算法可以为给定的Solution生成一个候选Problem,但需要注意的是我尝试生成的 order 会影响整个集合是否可以得到解决。

// This function exists already. Solution::ok() tells us whether it worked.
Solution solve (
    const Problem &,
    const std::vector<Solution> & solutions_so_far);

Solution的存在不仅取决于已经解决的问题,而且解决方案的性质也会发生变化,因此我无法缓存任何结果。

我需要一种算法来探索problems的所有排列,根据需要进行回溯,并在找到完整的解决方案后立即返回。

我们要求solutions[i]对应problems[i] ,否则输出的特定顺序无关紧要。请注意,上面的solutions_so_far表示problems可能已经被洗牌。

这是我需要实现的界面

// Returns a corresponding list, or an empty list if no complete solution found
std::vector<Solution> solve (
    std::vector<Problem>::iterator begin,
    std::vector<Problem>::iterator end)
{
    // ???
}

现在我被卡住了。我该怎么做?

另外,我可以就地进行吗?即使用std :: move重新排序problems是可以的,但理想情况下我不想在堆上分配任何东西。我想这个函数可以在它递归之前改变输入,但是我不能使这个模糊的想法具体化,或者说服自己我完全覆盖搜索空间而不重复任何工作。

3 个答案:

答案 0 :(得分:1)

您可以在索引引用的问题集上执行算法,因此不会创建副本,只需索引排列:

std::vector<Solution> solve(std::vector<Problem>::iterator begin,
                std::vector<Problem>::iterator end)
{
    // Create a solution 
    std::vector<Solution> ret(std::distance(begin, end));
    // Define the search space 
    std::vector<int> indices(std::distance(begin, end)); // problem space
    std::iota(indices.begin(), indices.end(), 0);        // fill with the indices

    do
    { // The mapping "solution-problem" can be done through the permutation state
        int i(0);
        for (auto it(indices.begin()), ite(indices.end()); it != ite; ++it)
        {
            ret[i] = solve(*(begin+(*it)), ret); // solve should acept ret range
            if (ret[i++].ok())
            {
                if (it + 1 == ite) return ret; // all solved !
            }
            else break; // try another permutation
        }
    } while (std::next_permutation(indices.begin(), indices.end()));


    return std::vector<Solution>(); // empty list
}

索引向量是必需的,因为std :: next_permutation需要“&lt;”运算符定义为(在这种情况下)问题类(或comp函数)并且没有提到这样的函数(在MSVC中你得到“错误C2678:二进制'&lt;':没有找到一个带左手操作数的运算符类型'问题'(或没有可接受的转换)“)

答案 1 :(得分:0)

本书combinatorial generation有一个函数,它将排列的词典列表映射到第66页的整数。例如:

F4(0) = [1,2,3,4]
F4(1) = [1,2,4,3]

所有你需要的只是一个迭代器,它接受一个排列(表示为索引数组)并按顺序迭代你的两个列表。这将要求您在堆上分配一个大小为N的数组(其中N是输入向量的长度)。

我会警告你,虽然你过了8个左右的项目,你根本无法遍历所有的排列(只有太多)。

如果您绝对无法分配任何内存,那么您可以放弃重新排序输入列表,但这会增加代码的复杂性。

答案 2 :(得分:0)

我想我有,但我不确定这是否正确。也许有人可以一眼就看出来?

首先,单个案件解决方案。

typedef std :: vector <Problem> P;
typedef std :: vector <Solution> S;

bool solve (
    P :: iterator problem,
    S :: iterator so_far_start,
    S :: iterator so_far_end,
    Solution * output)
{
    if (/* can find s consistent with [so_far_start...so_far_end)*/)
    {
        *output = s;
        return true;
    }
    else
        return false;
}

将此扩展到有序列表。

bool solve_naive (
    P :: iterator begin,
    P :: iterator end,
    S :: iterator out_begin,
    S :: iterator out_end)
{
    auto in = begin;
    auto out = end;

    while (in < end)
    {
        if (! solve (in, out_begin, out, &*out))
            return false;

        ++in;
        ++out;
    }

    return true;
}

现在聪明一点。

// reordering problems [at...end) as needed,
// write out corresponding solutions [out_at...out_end)
// consistent with [out_begin...out_at)
bool solve_permutations (
    P :: iterator at,
    P :: iterator end,
    S :: iterator out_begin,
    S :: iterator out_at,
    S :: iterator out_end,
    unsigned * limit)
{
    if (end == at)
        return true;

    if (0 == -- *limit)
        return false;

    if (solve_naive (at, end, out_at, out_end))
        return true;

    // try each other input in first position, let
    // recursion solve for some ordering of the remainder
    for (auto in = at + 1; in < end; ++in)
    {
        std :: swap (*in, *at);

        // this one must be solvable in first position
        if (! route (at, out_begin, out_at, &*out_at))
             continue;

        // all the rest must be solvable in any position
        if (solve_permutations (
            at + 1,
            end,
            out_begin,
            out_at + 1,
            out_end,
            limit))
        {
            return true;
        }
    }

    return false;
}

将所有这些结合在一起。

S solve_hopefully (P :: iterator begin, P :: iterator end)
{
    S solutions (end - begin, {});

    if (end - begin < WIDTH_LIMIT)
    {
        unsigned limit = DEPTH_LIMIT;

        // Will help if this high-level function
        // is called repeatedly.
        std :: random_shuffle (begin, end);

        return solve_permutations (
            begin,
            end,
            solutions .begin (),
            solutions .begin (),
            solutions .end (),
            & limit)
        ? solutions : S ();
    }
    else for (unsigned i = 0; i < SHUFFLE_LIMIT; ++i)
    {
        std :: random_shuffle (begin, end);

        if (solve_naive (begin, end, solutions .begin (), solutions .end ())
            return solutions;
    }

    return {};
}

这对你们好吗?