[英]C++ permutation tree
我有任務,我想計算最有利可圖的訂單來安排它們。
我不想檢查每個排列並進行 n*n 計算,而是想構建一個排列樹,即每個級別的孩子數量減少 1。並且在每個節點處,已經計算出的子排列將被保存而不是重新計算。
例如,如果我有 4 個任務,樹將如下所示:
我的附加代碼丟失了。 我不知道如何構建樹並為節點提供如圖所示的索引。 我知道如何處理二叉樹,但不知道如何處理每個層級的孩子數量不同的樹。
(每個任務的價值取決於它的位置。我知道該怎么做,所以我沒有把它包括在問題中)。
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);
}
}
當使用默認參數current_level = 0
調用時,如下面的代碼所示,此 function 在第一行退出而不執行任何操作。 您需要決定是從 0 還是從 1 開始索引。
除此之外,該算法的總體輪廓看起來還不錯,盡管我沒有明確檢查其正確性。
現在,更廣泛地說:這是一個練習,看看您是否可以編寫樹結構,或者您是否正在嘗試完成工作? 在后一種情況下,您可能希望使用像 boost 圖形庫中那樣的預構建數據結構。
如果這是構建樹結構的練習,那么它是否專門用於查看您是否可以編寫處理原始指針到指針的代碼的練習? 如果不是,您應該使用正確的 C++ 容器來完成這項工作。 例如,您可能希望將子節點列表存儲在std::vector
中,而不是使用指向指針的指針,唯一的方法是告訴存在多少子節點是樹中節點的深度。 (如果您出於非常具體的原因對某些東西進行超級優化,那么這種極其專業的結構可能會有一些用例,但看起來這並不是這里發生的事情。)
生成排列的遞歸方法是,如果您有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;
}
根據您的解釋,您正在嘗試構建的是一個數據結構,它為常見的排列重用子樹:
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});
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.