简体   繁体   English

C++排列树

[英]C++ permutation tree

I have tasks and I want to calculate the most profitable order to arrange them.我有任务,我想计算最有利可图的订单来安排它们。

Instead of checking every permutation and doing n*n, calculations, I want to build a tree of permutations, that is, the number of children at each level decreases by 1. and at each node the sub-permutation that has already been calculated will be saved and not recalculated.我不想检查每个排列并进行 n*n 计算,而是想构建一个排列树,即每个级别的孩子数量减少 1。并且在每个节点处,已经计算出的子排列将被保存而不是重新计算。

For example, if I have 4 tasks, the tree will look like this:例如,如果我有 4 个任务,树将如下所示:

树的例子

My attached code is missing.我的附加代码丢失了。 I don't know how to build the tree and the give nodes the indexes as in the figure.我不知道如何构建树并为节点提供如图所示的索引。 I know how to deal with a binary tree, but not with a tree where the number of children is different at each lavel.我知道如何处理二叉树,但不知道如何处理每个层级的孩子数量不同的树。

(The value of each task depends on its location. I know how to do that, so I didn't include it in the question). (每个任务的价值取决于它的位置。我知道该怎么做,所以我没有把它包括在问题中)。

int n = 4;

struct node
{
    int task_index = -1;
    double value;
    struct node **next;
};

void build_tree(node *current_node, int current_level = 0)
{
    if (current_level < 1 || current_level >= n)
        return;

    // current_node->task_index = ? ;
    current_node->next = new node *[n - current_level];

    for (int i = 0; i < n - current_level; i++)
    {
        build_tree(current_node->next[i], current_level + 1);
    }
}

void print_tree(node *current_node, int current_level = 0)
{
    // print indexes
}

void delete_tree(node *current_node, int current_level = 0)
{
    // delete nodes
}

int main()
{
    struct node *root = new node;
    build_tree(root);
    print_tree(root);
    delete_tree(root);
    delete root;

    return 0;
}
void build_tree(node *current_node, int current_level = 0)
{
    if (current_level < 1 || current_level >= n)
        return;

    // current_node->task_index = ? ;
    current_node->next = new node *[n - current_level];

    for (int i = 0; i < n - current_level; i++)
    {
        build_tree(current_node->next[i], current_level + 1);
    }
}

When called with the default parameter of current_level = 0 , as you illustrate in your code below, this function exits on the first line without doing anything.当使用默认参数current_level = 0调用时,如下面的代码所示,此 function 在第一行退出而不执行任何操作。 You need to decide whether you are indexing starting from 0 or from 1.您需要决定是从 0 还是从 1 开始索引。

Other than that, the general outline of the algorithm looks okay, although I did not explicitly check for correctness.除此之外,该算法的总体轮廓看起来还不错,尽管我没有明确检查其正确性。

Now, more broadly: is this an exercise to see if you can write a tree structure, or are you trying to get the job done?现在,更广泛地说:这是一个练习,看看您是否可以编写树结构,或者您是否正在尝试完成工作? In the latter case you probably want to use a prebuilt data structure like that in the boost graph library.在后一种情况下,您可能希望使用像 boost 图形库中那样的预构建数据结构。

If it's an exercise in building a tree structure, is it specifically an exercise to see if you can write code dealing with raw pointers-to-pointers?如果这是构建树结构的练习,那么它是否专门用于查看您是否可以编写处理原始指针到指针的代码的练习? If not, you should work with the correct C++ containers for the job.如果不是,您应该使用正确的 C++ 容器来完成这项工作。 For instance you probably want to store the list of child nodes in a std::vector rather than have a pointer-to-pointer with the only way to tell how many child nodes exist being the depth of the node in the tree.例如,您可能希望将子节点列表存储在std::vector中,而不是使用指向指针的指针,唯一的方法是告诉存在多少子节点是树中节点的深度。 (There may be some use case for such an extremely specialized structure if you are hyper-optimizing something for a very specific reason, but it doesn't look like that's what's going on here.) (如果您出于非常具体的原因对某些东西进行超级优化,那么这种极其专业的结构可能会有一些用例,但看起来这并不是这里发生的事情。)

The recursive way to generate permutations is if you have n items then all of the permutations of the items are each of the n items concatenated with the permutations of the n -1 remaining items.生成排列的递归方法是,如果您有n 个项目,那么这些项目的所有排列都是 n 个项目中的每一个与n -1剩余项目的排列串联在一起。 In code this is easier to do if you pass around the collection of items.在代码中,如果您传递项目集合,这样做会更容易。

Below I do it with an std::vector<int> .下面我用std::vector<int>来做。 Once using a vector it makes more sense to just follow the "rule of zero" pattern and let the nodes have vectors of children and then not need to dynamically allocate anything manually:一旦使用向量,更有意义的是遵循“零规则”模式,让节点有子向量,然后不需要手动动态分配任何东西:

#include <vector>
#include <algorithm>
#include <iostream>

struct node
{
    int task_index = -1;
    double value;
    std::vector<node> next;
};

std::vector<int> remove_item(int item, const std::vector<int>& items) {
    std::vector<int> output(items.size() - 1);
    std::copy_if(items.begin(), items.end(), output.begin(),
        [item](auto v) {return v != item; }
    );
    return output;
}

void build_tree(node& current_node, const std::vector<int>& tasks)
{
    auto n = static_cast<int>(tasks.size());
    for (auto curr_task : tasks) {
        node child{ curr_task, 0.0, {} };
        if (n > 1) {
            build_tree(child, remove_item(curr_task, tasks));
        }
        current_node.next.emplace_back(std::move(child));
    }
}

void print_tree(const node& current_node)
{
    std::cout << "( " << current_node.task_index << " ";
    for (const auto& child : current_node.next) {
        print_tree(child);
    }
    std::cout << " )";
}

int main()
{
    node root{ -1, 0.0, {} };
    build_tree(root, { 1, 2, 3 });
    print_tree(root);

    return 0;
}

From your explanation what you are trying to build is a data structure that reuses sub-trees for common permutations:根据您的解释,您正在尝试构建的是一个数据结构,它为常见的排列重用子树:

012 -> X
210 -> X

such that X is only instantiated once.这样X只被实例化一次。 This, of course, is recursive, seeing as当然,这是递归的,因为

01 -> Y
10 -> Y
Y2 -> X

If you look at it closely, there are 2^n such subtrees, because any prefix can have any one of the n input tasks used or not.如果仔细观察,有 2^n 个这样的子树,因为任何前缀都可以使用或不使用 n 个输入任务中的任何一个。 This means you can represent the subtree as an index into an array of size 2^n, with a total footprint O(n*2^n), which improves on the vastly larger >n: tree:这意味着您可以将子树表示为大小为 2^n 的数组的索引,总占用空间为 O(n*2^n),这改进了更大的 >n: 树:

struct Edge {
  std::size_t task;
  std::size_t sub;
};
struct Node {
  std::vector<Edge> successor; // size in [0,n]
};
std::vector<Node> permutations; // size exactly 2^n

This will have this structure:这将具有以下结构:

permutations: 0 1 2 3 4 ...
              |-^
              |---^
              |-------^
                |---^
                  |-^

Where the node at, eg, location 3 has both task 0 and 1 already used and "points" to all (n-2) subtrees.例如,位置 3 处的节点已经使用了任务 0 和 1,并且“指向”所有 (n-2) 个子树。


Of course, building this is not entirely trivial, but it compressed the search space and allows you re-use results for specific sub-trees.当然,构建它并不完全是微不足道的,但它压缩了搜索空间并允许您重复使用特定子树的结果。

You can build the table like this:您可以像这样构建表:

permutations.resize(1<<n);
for (std::size_t i = 0; i < size(permutations); ++i) {
    permutations[i].successor.reserve(n); // maybe better heuristic?
    for (std::size_t j = 0; j < n; ++j) {
        if (((1<<j) & i) == 0) {
            permutations[i].successor.push_back({j,(1<<j)|i});
        }
    }
}

Here is a live demo for n=4.这是 n=4 的现场演示。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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