问题描述
我有一个应用程序,对于给定的固定数量的顶点,需要解决从给定的固定源 (S) 到给定的固定汇 (T) 的大量不同的最大流算法。每个最大流问题的不同之处在于有向弧本身随着它们的容量而变化。例如,请参见下文。
顶点的数量保持不变,但实际的弧及其容量因问题而异。
我有以下代码使用 boost 迭代解决上图中的图 1 和图 2 的最大流问题,因此(对文字墙表示歉意,我已尝试使其尽可能小。代码下面在我的 linux Box 上的 g++ 上完全编译,但我无法在 wandBox 等在线编译器上正确编译):
#include <boost/config.hpp>
#include <iostream>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/boykov_kolmogorov_max_flow.hpp>
using namespace boost;
typedef adjacency_list_traits<vecS,vecS,directedS> Traits;
typedef adjacency_list<
vecS,directedS,property<
vertex_name_t,std::string,property<vertex_index_t,int,property<vertex_color_t,boost::default_color_type,property<vertex_distance_t,double,property<vertex_predecessor_t,Traits::edge_descriptor>
> > > >,property<
edge_index_t,property<edge_capacity_t,property<edge_weight_t,property<edge_residual_capacity_t,property<edge_reverse_t,Traits::edge_descriptor>
> > > > >
Graph;
Graph g;
property_map<Graph,edge_index_t>::type e;
property_map<Graph,edge_capacity_t>::type cap;
property_map<Graph,edge_weight_t>::type cost;
property_map<Graph,edge_residual_capacity_t>::type rescap;
property_map<Graph,edge_reverse_t>::type rev;
property_map<Graph,vertex_color_t>::type colors;
void initialize(int nnodes) {
e = get(edge_index,g);
cap = get(edge_capacity,g);
cost = get(edge_weight,g);
rescap = get(edge_residual_capacity,g);
rev = get(edge_reverse,g);
colors = get(vertex_color,g);
for(int i = 0; i < nnodes; i++)
add_vertex(g);
}
void clearedges() {
Graph::vertex_iterator v,vend;
for (boost::tie(v,vend) = vertices(g); v != vend; ++v)
boost::clear_out_edges(*v,g);
}
void createedges(std::vector<std::pair<int,int>>& arcs,std::vector<double>& capacity) {
Traits::edge_descriptor edf,edr;//forward and reverse
for (int eindex = 0,sz = static_cast<int>(arcs.size()); eindex < sz; eindex++) {
int fr,to;
fr = arcs[eindex].first;
to = arcs[eindex].second;
edf = add_edge(fr,to,g).first;
edr = add_edge(to,fr,g).first;
e[edf] = 2 * eindex;
e[edr] = e[edf] + 1;
cap[edf] = capacity[eindex];
cap[edr] = capacity[eindex];
rev[edf] = edr;
rev[edr] = edf;
}
}
double solve_max_flow(int s,int t) {
double retval = boykov_kolmogorov_max_flow(g,s,t);
return retval;
}
bool is_part_of_source(int i) {
if (colors[i] == boost::black_color)
return true;
return false;
}
int main() {
initialize(6);
std::vector<std::pair<int,int>> arcs1 = { std::make_pair<int,int>(0,1),std::make_pair<int,2),int>(1,3),4),int>(2,int>(3,5),int>(4,5)
};
std::vector<double> capacities1 = { 10,10,1,4,3,2,10 };
clearedges();
createedges(arcs1,capacities1);
double maxflow = solve_max_flow(0,5);
printf("max flow is %f\n",maxflow);
for (int i = 0; i < 6; i++)
if (is_part_of_source(i))
printf("Node %d belongs to subset source is in\n",i);
Graph::edge_iterator e_,eend_;
int Eindex = 0;
for (boost::tie(e_,eend_) = edges(g); e_ != eend_; ++e_) {
int fr = source(*e_,g);
int to = target(*e_,g);
printf("(%d) Edge %d: (%d -> %d),capacity %f\n",Eindex,e[*e_],cap[*e_]);
Eindex++;
if (is_part_of_source(fr) && is_part_of_source(to) == false)
printf("----is part of ST Cut-----\n");
else
printf("x\n");
}
std::vector<std::pair<int,int>> arcs2 = { std::make_pair<int,5)
};
std::vector<double> capacities2 = { 10,0 };
clearedges();
createedges(arcs2,capacities2);
maxflow = solve_max_flow(0,i);
Eindex = 0;
for (boost::tie(e_,cap[*e_]);
Eindex++;
if (is_part_of_source(fr) && is_part_of_source(to) == false)
printf("----is part of ST Cut-----\n");
else
printf("x\n");
}
getchar();
}
我有以下问题。
(a) 如果底层顶点保持固定,但只有弧及其容量在迭代之间发生变化,有什么比使用 clear_out_edges
清除弧然后使用 add_edge
来更快的吗?添加具有新容量的新弧?此外,clear_out_edges
是否也正确清除了可能将边描述符作为键删除的属性映射条目?
(b) Boost 最大流算法似乎想要显式添加反向弧。截至目前,在函数 createedges
中,我通过前向边描述符 (edf
) 和反向边描述符 (edr
) 明确执行此操作。特别是当需要解决的最大流量问题的数量在 1000 秒内时,是否有任何性能损失?还有什么比这更有效的吗?
(c) 我能够通过以下代码部分正确枚举最小 S/T 切割的弧:
int Eindex = 0;
for (boost::tie(e_,cap[*e_]);
Eindex++;
if (is_part_of_source(fr) && is_part_of_source(to) == false)
printf("----is part of ST Cut-----\n");
else
printf("x\n");
}
有没有比上面更有效的方法或枚举 S/T 切割的弧线?
解决方法
有很多问题。如果您使用现代 C++ 和编译器警告,您可以减少代码并发现打印顶点描述符时的错误(printf 只是不安全;使用诊断程序!)。
这是我审核后的看法。
显着变化:
-
捆绑属性而不是单独的内部属性
-
这意味着传递命名参数(但请参阅 https://stackoverflow.com/a/64744086/85371)
-
没有更多的全局变量,如果简单的构造函数就足够了,就没有更多的循环初始化
-
不再有重复的代码(没有什么比使用 capacity1 和 capacity2 更容易出错的了)
-
使用
clear_vertex
而不是仅使用clear_out_edges
- 这可能没有什么区别 (?) 但似乎更好地表达了意图 -
不再使用 printf(我将使用 libfmt,它也在 C++23 中),例如
fmt::print("Max flow {}\nNodes {} are in source subset\n",maxflow,vertices(_g) | filtered(is_source));
印刷品
Max flow 10 Nodes {0,1,2,3} are in source subset
一键搞定
-
打印您认为正在打印的内容。特别是,如果可以,请使用库支持打印边缘
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/boykov_kolmogorov_max_flow.hpp>
#include <boost/range/adaptors.hpp>
#include <fmt/ostream.h>
#include <fmt/ranges.h>
using boost::adaptors::filtered;
using Traits = boost::adjacency_list_traits<boost::vecS,boost::vecS,boost::directedS>;
using V = Traits::vertex_descriptor;
using E = Traits::edge_descriptor;
using Capacity = double;
using Color = boost::default_color_type;
struct VertexProps {
// std::string name;
Color color;
Capacity distance;
E predecessor;
};
struct EdgeProps {
int id;
Capacity weight,residual;
E reverse;
};
using Graph = boost::adjacency_list<
boost::vecS,boost::directedS,VertexProps,// see https://stackoverflow.com/a/64744086/85371 :(
boost::property<boost::edge_capacity_t,Capacity,EdgeProps>>;
struct MyGraph {
MyGraph(size_t nnodes) : _g(nnodes) {}
void runSimulation(auto const& arcs,auto const& capacities)
{
reconfigure(arcs,capacities);
Capacity maxflow = solve_max_flow(0,5);
auto cap = get(boost::edge_capacity,_g);
auto is_source = [this](V v) { return _g[v].color == Color::black_color; };
fmt::print("Max flow {}\nNodes {} are in source subset\n",vertices(_g) | filtered(is_source));
for (E e : boost::make_iterator_range(edges(_g))) {
bool st_cut =
is_source(source(e,_g)) and
not is_source(target(e,_g));
fmt::print("Edge {} (id #{:2}),capacity {:3} {}\n",e,_g[e].id,cap[e],st_cut ? "(ST Cut)" : "");
}
}
private:
Graph _g;
void reconfigure(auto const& arcs,auto const& capacities)
{
assert(arcs.size() == capacities.size());
for (auto v : boost::make_iterator_range(vertices(_g))) {
// boost::clear_out_edges(v,g);
boost::clear_vertex(v,_g);
}
auto cap = get(boost::edge_capacity,_g);
auto eidx = get(&EdgeProps::id,_g);
auto rev = get(&EdgeProps::reverse,_g);
auto eindex = 0;
for (auto [fr,to] : arcs) {
auto edf = add_edge(fr,to,_g).first;
auto edr = add_edge(to,fr,_g).first;
eidx[edf] = 2 * eindex;
eidx[edr] = eidx[edf] + 1;
cap[edf] = cap[edr] = capacities[eindex];
rev[edf] = edr;
rev[edr] = edf;
++eindex;
}
}
Capacity solve_max_flow(V src,V sink)
{
return boykov_kolmogorov_max_flow(
_g,src,sink,// named arguments
boost::reverse_edge_map(get(&EdgeProps::reverse,_g))
.residual_capacity_map(get(&EdgeProps::residual,_g))
.vertex_color_map(get(&VertexProps::color,_g))
.predecessor_map(get(&VertexProps::predecessor,_g))
.distance_map(get(&VertexProps::distance,_g))
// end named arguments
);
}
};
int main() {
MyGraph g{6};
using namespace std;
for (auto&& [arcs,capacities] : { tuple
// 1
{vector{pair{0,1},{0,2},{1,3},4},{2,{3,5},{4,5}},vector{10,10,4,3,10}},// 2
{vector{pair{0,0}},})
{
g.runSimulation(arcs,capacities);
}
}
印刷品
Max flow 10
Nodes {0,3} are in source subset
Edge (0,1) (id # 0),capacity 10
Edge (0,2) (id # 2),capacity 10
Edge (1,0) (id # 1),2) (id # 4),3) (id # 6),4) (id # 8),capacity 1 (ST Cut)
Edge (2,0) (id # 3),capacity 10
Edge (2,1) (id # 5),4) (id #10),capacity 4 (ST Cut)
Edge (3,1) (id # 7),capacity 10
Edge (3,4) (id #12),capacity 3 (ST Cut)
Edge (3,5) (id #14),capacity 2 (ST Cut)
Edge (4,1) (id # 9),capacity 1
Edge (4,2) (id #11),capacity 4
Edge (4,3) (id #13),capacity 3
Edge (4,5) (id #16),capacity 10
Edge (5,3) (id #15),capacity 2
Edge (5,4) (id #17),capacity 10
Max flow 2
Nodes {0,4} are in source subset
Edge (0,3) (id # 4),4) (id # 6),capacity 4
Edge (3,5) (id # 8),2) (id # 7),5) (id #10),capacity 0 (ST Cut)
Edge (5,3) (id # 9),4) (id #11),capacity 0
旁注
如果您认为 main 过于复杂,这里有另一种方法来为两个调用编写它:
g.runSimulation({{0,{10,10});
g.runSimulation({{0,0});