C++置换树

ufj5ltwl  于 2023-02-06  发布在  其他
关注(0)|答案(3)|浏览(94)

我有任务,我想计算最有利可图的顺序来安排他们。
我不想检查每一个排列并进行n * n!次计算,而是想构建一个排列树,即每一层的子排列数减1,在每个节点上已经计算过的子排列将被保存,不再重新计算。
例如,如果我有4个任务,树将如下所示:

我所附的代码丢失了,我不知道如何构建树,也不知道如何给节点提供索引,我知道如何处理二叉树,但不知道如何处理每一层的子树数量不同的树。
(The每个任务的价值取决于它的位置。我知道如何做到这一点,所以我没有把它包括在问题中)。

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;
}
wz3gfoph

wz3gfoph1#

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);
    }
}

当使用默认参数current_level = 0调用时,如下面的代码所示,此函数在第一行退出,不执行任何操作。您需要决定是从0还是从1开始索引。
除此之外,算法的总体轮廓看起来还可以,尽管我没有明确检查正确性。
现在,更广泛地说:这是一个测试你是否能写一个树结构的练习,还是你想完成这项工作?2在后一种情况下,你可能想使用一个预先构建的数据结构,就像boost图形库中的那样。
如果这是一个构建树结构的练习,那么它是否专门用来测试您是否可以编写处理原始指针到指针的代码?如果不是,你应该使用正确的C++容器来完成这个任务。例如,你可能想把子节点列表存储在std::vector中,而不是用一个指针-一个指针,它告诉我们存在多少个子节点的唯一方法是节点在树中的深度。(如果您为了非常具体的原因而对某个东西进行超优化,那么对于这样一个极其专业化的结构可能会有一些用例,但看起来并不是这样的。)

cwxwcias

cwxwcias2#

根据您的解释,您试图构建的是一个重用子树进行常见排列的数据结构:

012 -> X
210 -> X

这样X只被示例化一次。当然,这是递归的,可以看到

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

如果你仔细观察,就会发现有2^n个这样的子树,因为任何前缀都可以使用或不使用n个输入任务中的任何一个,这意味着你可以将子树表示为一个大小为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

其结构如下:

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

其中例如位置3处的节点具有已经使用的任务0和1,并且“指向”所有(n-2)个子树。
当然,构建这个树并不完全是小事,但是它压缩了搜索空间,并允许您重用特定子树的结果。
可以按如下方式构建表格:

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.

kyks70gy

kyks70gy3#

递归生成排列的方法是,如果你有 n 个项,那么所有项的排列都是 n 个项中的每一个与剩下的 n-1个项的排列的连接。在代码中,如果你传递项的集合,这会更容易做到。
下面我用一个std::vector<int>来做这个,一旦使用了一个向量,那么遵循“零规则”模式,让节点拥有子节点的向量,这样就不需要手动动态分配任何东西,这会更有意义:

#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;
}

相关问题