Skip to content

Flows and spanning trees

Maximum flow

Both maximum-flow algorithms take a digraph and a capacity per arc, and both require outward_incidence_graph, inward_incidence_graph, has_vertex_map and has_arc_map — the residual network is walked in both directions, so static_forward_digraph is not accepted.

They are not ranges: run() computes the flow, and the results are read afterwards.

edmonds_karp

#include "melon/algorithm/edmonds_karp.hpp"

std::vector<int> capacity = ...;   // one per arc

edmonds_karp alg(graph, capacity, 0u, 4u);
alg.run();

std::println("max flow = {}", alg.flow_value());
for(auto && a : alg.minimum_cut()) std::print(" {}", a);

Augments along shortest unsaturated paths, found by breadth-first search — O(V·E²) in the worst case, independent of the capacity values.

dinitz

#include "melon/algorithm/dinitz.hpp"

dinitz alg(graph, capacity, 0u, 4u);
alg.run();
std::println("max flow = {}", alg.flow_value());

Dinitz's algorithm: rank the vertices by BFS, then push blocking flows through the level graph — O(V²·E), and much better than that in practice. It keeps a per-vertex consumable view of the remaining out- and in-arcs so a saturated arc is never rescanned within a phase.

Prefer dinitz unless you have a specific reason not to: same interface, same results, better asymptotics.

Common members

Member Effect
set_source(s) / set_target(t) change the terminals
reset() zero the flow, keep the graph and capacities
run() compute a maximum flow
flow_value() the value of the flow — the sum over the source's out-arcs
minimum_cut() the arcs of a minimum cut, as a range

set_source, set_target and reset() chain, so a series of st computations on one graph reuses all the allocations:

dinitz alg(graph, capacity);
for(auto && [s, t] : pairs) {
    alg.reset().set_source(s).set_target(t).run();
    record(s, t, alg.flow_value());
}

Warning

flow_value() sums the flow on the arcs leaving the source, so it is only the maximum flow value after run() has converged. There is no per-arc flow accessor in the public interface yet — if you need the flow decomposition rather than the value and the cut, this is currently a gap.

Minimum spanning tree

kruskal

#include "melon/algorithm/kruskal.hpp"
#include "melon/views/undirect.hpp"

auto ugraph = views::undirect(graph);

for(auto && e : kruskal(ugraph, cost_map)) std::print(" {}", e);
//  5 6 0 1 7

Kruskal's algorithm on an undirected graph, backed by disjoint_sets. It is a range: edges are yielded in increasing cost order as they are accepted into the tree, so you can stop early — after k edges, or once the running total exceeds a budget:

int budget = 100, total = 0;
for(auto && e : kruskal(ugraph, cost_map)) {
    if(total + cost_map[e] > budget) break;
    total += cost_map[e];
    chosen.push_back(e);
}

On a disconnected graph it yields a minimum spanning forest — one tree per component, with no marker between them. Pair it with connected_components if you need to know which is which.

Since views::undirect keeps the arc identifiers as edge identifiers, the cost map produced by static_digraph_builder is directly usable, as above.

The Prim alternative

There is no separate Prim implementation: running dijkstra with minimum_spanning_tree_semiring and store_paths = true performs Prim's traversal, and pred_arc(v) gives the tree edges. Kruskal is usually what you want — it is a range, it handles forests, and it does not need has_vertex_map — but the Prim route wins on dense graphs and gives you the tree rooted where you chose.

What is missing

melon has no min-cost flow, no bipartite matching, no general matching, and no network simplex — the last is on the roadmap. For those today, Boost.Graph or LEMON remain the answer.