g
of a directed graph type, say ListDigraph and an algorithm template <typename Digraph> int algorithm(const Digraph&);
template <typename Digraph> class ReverseDigraph;
The graph adaptors are special classes that serve for considering other graph structures in different ways. They can be used exactly the same as "real" graphs, i.e. they conform to the graph concepts, thus all generic algorithms can be performed on them. However, the adaptor classes cannot be used alone but only in conjunction with actual graph representations. They do not alter the physical graph storage, they just give another view of it. When the methods of the adaptors are called, they use the underlying graph structures and their operations, thus these classes have only negligible memory usage and do not perform sophisticated algorithmic actions.
This technique yields convenient tools that help writing compact and elegant code, and makes it possible to easily implement complex algorithms based on well tested standard components.
For solving the problem introduced above, we could use the following code.
ListDigraph g;
ReverseDigraph<ListDigraph> rg(g);
int result = algorithm(rg);
Note that the original digraph g
remains untouched during the whole procedure.
LEMON also provides simple "creator functions" for the adaptor classes to make their usage even simpler. For example, reverseDigraph() returns an instance of ReverseDigraph, thus the above code can be written like this.
ListDigraph g; int result = algorithm(reverseDigraph(g));
Another essential feature of the adaptors is that their Node
and Arc
types convert to the original item types. Therefore, the maps of the original graph can be used in connection with the adaptor.
In the following code, Dijksta's algorithm is run on the reverse oriented graph but using the original node and arc maps.
ListDigraph g; ListDigraph::ArcMap length(g); ListDigraph::NodeMap dist(g); ListDigraph::Node s = g.addNode(); // add more nodes and arcs dijkstra(reverseDigraph(g), length).distMap(dist).run(s);
In the above examples, we used ReverseDigraph in such a way that the underlying digraph was not changed. However, the adaptor class can even be used for modifying the original graph structure. It allows adding and deleting arcs or nodes, and these operations are carried out by calling suitable functions of the underlying digraph (if it supports them).
For this, ReverseDigraph<GR> has a constructor of the following form.
ReverseDigraph(GR& gr);
This means that in a situation, when the modification of the original graph has to be avoided (e.g. it is given as a const reference), then the adaptor class has to be instantiated with GR
set to be const
type (e.g. GR = const ListDigraph
), as in the following example.
int algorithm1(const ListDigraph& g) { ReverseDigraph<const ListDigraph> rg(g); return algorithm2(rg); }
u
, each other node is reachable from u
(along a directed path) and u
is reachable from each node. The latter condition is the same that each node is reachable from u
in the reversed digraph.
template <typename Digraph> bool stronglyConnected(const Digraph& g) { typedef typename Digraph::NodeIt NodeIt; NodeIt u(g); if (u == INVALID) return true; // Run BFS on the original digraph Bfs<Digraph> bfs(g); bfs.run(u); for (NodeIt n(g); n != INVALID; ++n) { if (!bfs.reached(n)) return false; } // Run BFS on the reverse oriented digraph typedef ReverseDigraph<const Digraph> RDigraph; RDigraph rg(g); Bfs<RDigraph> rbfs(rg); rbfs.run(u); for (NodeIt n(g); n != INVALID; ++n) { if (!rbfs.reached(n)) return false; } return true; }
Note that we have to use the adaptor with 'const Digraph
' type, since g
is a const
reference to the original graph structure. The stronglyConnected() function provided in LEMON has a quite similar implementation.
FilterArcs can be used when some arcs have to be hidden from a digraph. A filter map has to be given to the constructor, which assign bool
values to the arcs specifying whether they have to be shown or not in the subgraph structure. Suppose we have a ListDigraph structure g
. Then we can construct a subgraph in which some arcs (a1
, a2
etc.) are hidden as follows.
ListDigraph::ArcMap filter(g, true); filter[a1] = false; filter[a2] = false; // ... FilterArcs<ListDigraph> subgraph(g, filter);
The following more complex code runs Dijkstra's algorithm on a digraph that is obtained from another digraph by hiding all arcs having negative lengths.
ListDigraph::ArcMap<int> length(g); ListDigraph::NodeMap<int> dist(g); dijkstra(filterArcs( g, lessMap(length, constMap<ListDigraph::Arc>(0)) ), length).distMap(dist).run(s);
Note the extensive use of map adaptors and creator functions, which makes the code really compact and elegant.
ListGraph ug; ListGraph::NodeMap<bool> node_filter(ug); ListGraph::EdgeMap<bool> edge_filter(ug); SubGraph<ListGraph> sg(ug, node_filter, edge_filter);
As you see, we needed two filter maps in this case: one for the nodes and another for the edges. If a node is hidden, then all of its incident edges are also considered to be hidden independently of their own filter values.
The subgraph adaptors also make it possible to modify the filter values even after the construction of the adaptor class, thus the corresponding graph items can be hidden or shown on the fly. The adaptors store references to the filter maps, thus the map values can be set directly and even by using the enable()
, disable()
and status()
functions.
ListDigraph g; ListDigraph::Node x = g.addNode(); ListDigraph::Node y = g.addNode(); ListDigraph::Node z = g.addNode(); ListDigraph::NodeMap<bool> filter(g, true); FilterNodes<ListDigraph> subgraph(g, filter); std::cout << countNodes(subgraph) << ", "; filter[x] = false; std::cout << countNodes(subgraph) << ", "; subgraph.enable(x); subgraph.disable(y); subgraph.status(z, !subgraph.status(z)); std::cout << countNodes(subgraph) << std::endl;
The above example prints out this line.
3, 2, 1
Similarly to ReverseDigraph, the subgraph adaptors also allow the modification of the underlying graph structures unless the graph template parameter is set to be const
type. Moreover the item types of the original graphs and the subgraphs are convertible to each other.
The iterators of the subgraph adaptors use the iterators of the original graph structures in such a way that each item with false
filter value is skipped. If both the node and arc sets are filtered, then the arc iterators check for each arc the status of its end nodes in addition to its own assigned filter value. If the arc or one of its end nodes is hidden, then the arc is left out and the next arc is considered. (It is the same for edges in undirected graphs.) Therefore, the iterators of these adaptors are significantly slower than the original iterators.
Using adaptors, these efficiency aspects should be kept in mind. For example, if rather complex algorithms have to be performed on a subgraph (e.g. the nodes and arcs need to be traversed several times), then it could worth copying the altered graph into an efficient structure (e.g. StaticDigraph) and run the algorithm on it. Note that the adaptor classes can also be used for doing this easily, without having to copy the graph manually, as shown in the following example.
ListDigraph g; ListDigraph::NodeMap<bool> filter_map(g); // construct the graph and fill the filter map { StaticDigraph tmp_graph; ListDigraph::NodeMap<StaticDigraph::Node> node_ref(g); digraphCopy(filterNodes(g, filter_map), tmp_graph) .nodeRef(node_ref).run(); // use tmp_graph }
bool
edge map of the underlying graph must be given to the constructor of the class, which define the direction of the arcs in the created adaptor (with respect to the inherent orientation of the original edges).
ListGraph graph;
ListGraph::EdgeMap<bool> dir_map(graph, true);
Orienter<ListGraph> directed_graph(graph, dir_map);
Sine the adaptor classes conform to the graph concepts, we can even apply an adaptor to another one. The following image illustrates a situation when a SubDigraph and an Undirector adaptor is applied to a digraph.
LEMON also provides some more complex adaptors, for instance, SplitNodes, which can be used for splitting each node of a directed graph into an in-node and an out-node. Formally, the adaptor replaces each node u in the graph with two nodes, namely uin and uout. Each arc (u,v) of the original graph will correspond to an arc (uout,vin). The adaptor also adds an additional bind arc (uin,uout) for each node u of the original digraph.
The aim of this class is to assign costs or capacities to the nodes when using algorithms which would otherwise consider arc costs or capacities only. For example, let us suppose that we have a digraph g
with costs assigned to both the nodes and the arcs. Then Dijkstra's algorithm can be used in connection with SplitNodes as follows.
typedef SplitNodes<ListDigraph> SplitGraph; SplitGraph sg(g); SplitGraph::CombinedArcMap<NodeCostMap, ArcCostMap> combined_cost(node_cost, arc_cost); SplitGraph::NodeMap<double> dist(sg); dijkstra(sg, combined_cost).distMap(dist).run(sg.outNode(u));
On the other hand, node disjoint paths cannot be found directly using a standard algorithm. However, SplitNodes adaptor makes it really simple. If a maximum flow computation is performed on this adaptor, then the bottleneck of the flow (i.e. the minimum cut) will be formed by bind arcs, thus the found flow will correspond to the union of some node disjoint paths in terms of the original digraph. For example, in the above digraph, there are only three node disjoint paths.
In flow, circulation and matching problems, the residual network is of particular importance, which is implemented in ResidualDigraph. Combining this adaptor with various algorithms, a range of weighted and cardinality optimization methods can be implemented easily.
To construct a residual network, a digraph structure, a flow map and a capacity map have to be given to the constructor of the adaptor as shown in the following code.
ListDigraph g; ListDigraph::ArcMap<int> flow(g); ListDigraph::ArcMap<int> capacity(g); ResidualDigraph<ListDigraph> res_graph(g, capacity, flow);