簡體   English   中英

如何配置 boost::graph 以使用我自己的(穩定的)頂點索引?

[英]How to configure boost::graph to use my own (stable) index for vertices?

最簡單的boost::adjacency_list使用std::size_t作為底層的 vertex_descriptor(索引)。

boost::adjacency_list<
    boost::vecS,
    boost::vecS,
    boost::directedS,
    boost::no_property,
    boost::no_property
>  graph;

一旦我知道了頂點描述符,我就可以快速訪問所需的頂點。

auto vertex = graph[idx]; // idx is the veretx descriptor

但是,當圖發生變異時,不能保證vertex_decriptor是穩定的。

auto v0 = boost::add_vertex(graph);
auto v1 = boost::add_vertex(graph);
auto v2 = boost::add_vertex(graph);

boost::remove_vertex(v0, graph); // v1 and v2 are no longer valid

我希望能夠快速找到一個特定的頂點 - 這意味着我希望避免遍歷圖形結構來搜索我知道存在的頂點。

我的想法是,我可以通過與 VertexListS 模板參數不同的選擇器以某種方式調整boost::adjacency_list ,這將允許我使用我自己提供的 vertex_descripor(索引)。

我探索了使用關聯容器選擇器(例如內置boost::hash_mapS )的可能性,但似乎我無法控制它在調用add_vertex時返回的確切 ID。 我希望能夠為每個頂點使用我自己的 id (vertex_descriptor)。

我會嘗試更清楚一點,使用我希望的代碼:

// the following type will serve as the vertex_descriptor:
using my_id_type = std::size_t;

struct MyVertex
{
    my_id_type id;
    // some other fields
}

// add vertices with unique identifiers that will also serve as the vertex_descriptors
boost::add_vertex(MyVertex{1111}, graph); // I expect this to return 1111
boost::add_vertex(MyVertex{2222}, graph);
boost::add_vertex(MyVertex{1234}, graph);


boost::remove_vertex(1111, graph); // I expect this to remove the first vertex

// access a specific vertex using its unique, stable descriptor:
auto vertex = graph[1234];

這可以使用 boost::graph 實現嗎?

這可以使用 boost::graph 實現嗎?

對於 BGL,答案幾乎總是“是”。 它是現存最深刻的通用庫設計之一。


令我驚訝的是,在adjacency_list的類型層次結構中出現了一些新東西。 顯然,最近有一個named_graph mixin(實際上是maybe_name_graph ),它使用頂點包上的特征來檢測“內部名稱”。

這意味着您可以靠近。 雖然頂點描述符不會成為您的 ID,但您可以進行 O(1) 查找。 而且接口有一些便利,所以你可以這樣寫:

add_edge(1111, 2222, g);
add_edge(2222, 1111, g);

筆記:

  • 查找的時間復雜度為 O(1),因為名稱-頂點查找基於無序(哈希)索引
  • 如果您不小心將id類型設置為與vertex_descriptor類型相同(或者如果您的參數對兩者的轉換具有同樣“遠”的轉換),則會遇到問題(例如add_edge的不明確重載)。
  • 據我所知,內部名稱屬性不會自動作為vertex_index_tvertex_name_t屬性。

步驟#1:圖表

struct Vertex {
    size_t      id;
    std::string other = "default-constructed";
};

using Graph =
    boost::adjacency_list<boost::vecS, boost::listS, boost::directedS, Vertex>;

到目前為止沒有驚喜。

  • 選擇添加第二個成員other只是為了顯示它何時/如何默認構造
  • 選擇listS因為它
    • 一種。 提供參考/描述符穩定性(刪除的頂點除外)
    • b. 導致不透明vertex_descriptor ( void* ) 與重載決議中的 id 類型 ( size_t ) 不沖突

步驟#2:命名特征

接下來我們教 BGL 關於我們的內部頂點名稱。

這完全由Vertex bundle 參數化,因此請記住,使用相同 bundle 的不同圖形將使用相同的名稱特征。

// traits
template <> struct boost::graph::internal_vertex_name<Vertex> {
    struct type {
        using result_type = size_t;
        result_type const& operator()(Vertex const& bundle) const {
            return bundle.id;
        }
    };
};

template <> struct boost::graph::internal_vertex_constructor<Vertex> {
    struct type {
      private:
        using extract_name_type = typename internal_vertex_name<Vertex>::type;

        using vertex_name_type = typename remove_cv<typename remove_reference<
            typename extract_name_type::result_type>::type>::type;

      public:
        using argument_type = vertex_name_type;
        using result_type = Vertex;

        result_type operator()(const vertex_name_type& id) const {
            return {id};
        }
    };
};

筆記

  • 我們當然可以在第二個專業化中對已知信息進行硬編碼:

     template <> struct boost::graph::internal_vertex_constructor<Vertex> { struct type { Vertex operator()(size_t id) const { return {id}; } }; };
  • 通過引用返回 id非常重要。 如果不這樣做會導致UB沒有來自庫/編譯器的診斷

步驟 #3 添加/查找頂點

現在您可以添加頂點。 通過“名稱”(您的id ):

auto x = add_vertex(1111, g);                // by id

或者您在問題中預期的老式方式:

add_vertex(Vertex{2222, "twotwotwotwo"}, g); // or with full bundle

重復添加無效:

assert(add_vertex(1111, g) == x);

存在不同的查找。 vertex_by_property返回一個optional<vertex_descriptor>給定一個頂點包。

assert(x == *g.vertex_by_property(g[x]));

它通過從傳遞的包中提取“內部名稱”來做到這一點,因此包不需要在 id 之外包含任何其他 state:

assert(x == *g.vertex_by_property(Vertex{1111}));

雖然感覺像是一個實現細節,但實際實現名稱 -> 描述符索引的multi_index_container也暴露了:

assert(x == *g.named_vertices.find(1111));

步驟 #4 添加邊

add_edge(1111, 2222, g);
add_edge(2222, 1111, g);

從你之前的問題中借用了一頁:)

顯然,您仍然可以通過頂點描述符添加邊。

步驟 #5 其他操作

從之前的答案中借用更多頁面:

print_graph(g, get(&Vertex::id, g), std::cout << "---\n");
print_graph(g, get(&Vertex::other, g), std::cout << "---\n");

std::cout << "---\n";
for (auto *v : boost::make_iterator_range(vertices(g))) {
    auto const& [id, other] = g[v];
    std::cout << id << " " << std::quoted(other) << "\n";
}

if (auto v = g.vertex_by_property({1111})) {
    std::cout << "==== removing " << g[*v].id << "\n";
    clear_vertex(*v, g);  // clear edges
    remove_vertex(*v, g); // remove the vertex
}

print_graph(g, get(&Vertex::id, g), std::cout << "---\n");
print_graph(g, get(&Vertex::other, g), std::cout << "---\n");

完整演示

生活在 Coliru

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graph_utility.hpp>
#include <iostream>
#include <iomanip>

struct Vertex {
    size_t      id;
    std::string other = "default-constructed";
};

using Graph =
    boost::adjacency_list<boost::vecS, boost::listS, boost::directedS, Vertex>;

// traits
template <> struct boost::graph::internal_vertex_name<Vertex> {
    struct type {
        using result_type = size_t;
        result_type const& operator()(Vertex const& bundle) const {
            return bundle.id;
        }
    };
};

template <> struct boost::graph::internal_vertex_constructor<Vertex> {
    struct type {
      private:
        using extractor = typename internal_vertex_name<Vertex>::type;
        using name_t    = std::decay_t<typename extractor::result_type>;

      public:
        using argument_type = name_t;
        using result_type = Vertex;

        result_type operator()(const name_t& id) const { return {id}; }
    };
};

int main() {
    Graph g;

    {
        auto x = add_vertex(1111, g);                // by id
        add_vertex(Vertex{2222, "twotwotwotwo"}, g); // or with full bundle

        // duplicate additions have no effect
        assert(add_vertex(1111, g) == x);

        // different lookups exist
        assert(x == *g.named_vertices.find(1111));
        assert(x == *g.vertex_by_property(Vertex{1111}));
        assert(x == *g.vertex_by_property(g[x]));
    }

    add_edge(1111, 2222, g);
    add_edge(2222, 1111, g);

    print_graph(g, get(&Vertex::id, g), std::cout << "---\n");
    print_graph(g, get(&Vertex::other, g), std::cout << "---\n");

    std::cout << "---\n";
    for (auto *v : boost::make_iterator_range(vertices(g))) {
        auto const& [id, other] = g[v];
        std::cout << id << " " << std::quoted(other) << "\n";
    }

    if (auto v = g.vertex_by_property({1111})) {
        std::cout << "==== removing " << g[*v].id << "\n";
        clear_vertex(*v, g);  // clear edges
        remove_vertex(*v, g); // remove the vertex
    }

    print_graph(g, get(&Vertex::id, g), std::cout << "---\n");
    print_graph(g, get(&Vertex::other, g), std::cout << "---\n");
}

印刷

---
1111 --> 2222 
2222 --> 1111 
---
default-constructed --> twotwotwotwo 
twotwotwotwo --> default-constructed 
---
1111 "default-constructed"
2222 "twotwotwotwo"
==== removing 1111
---
2222 --> 
---
twotwotwotwo --> 

邊注:

  • 您不能對頂點使用關聯容器選擇器。 指定hash_mapS導致unordered_set<void*, hash<void*>, equal_to<void*>, allocator<void*>>作為m_vertices類型。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM