I am trying to find a way to solve the longest path problem for a graph with the following characteristics:
Directed
Weighted (including negative weights)
NOT acyclic, although I am only looking for simple paths.
I have had a look at Boost.Graph for the first time the last two days. It seems very complex and I want to be sure that I can solve my problem with this library before I dig deeper into the documentation just to find out that I actually can't use it.
So can I use Boost.Graph for my problem? Is it maybe even NP-Complete? And even if I could use Boost.Graph, would there be a better alternative based on performance?
Here is some code using the boost graph library to find the longest ( most heavily weighted ) path between two vertices in a graph. The graph is checked for cycles which are removed by deleting the back edges in a copy of the graph.
A refactored and documented version of this code is available from a public fossil repository at https://chiselapp.com/user/ravenspoint/repository/longest_path/dir?ci=tip
#include <iostream>
#include <random>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/graph/bellman_ford_shortest_paths.hpp>
#include <boost/graph/topological_sort.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/graph/depth_first_search.hpp>
using namespace std;
using namespace boost;
class cVertex
{
};
class cEdge
{
public:
void Weight( int w )
{
myWeight = w;
}
int Weight()
{
return myWeight;
}
void negWeight()
{
myWeight = - myWeight;
}
int myWeight;
};
typedef adjacency_list<
vecS, vecS, directedS,
cVertex, cEdge > graph_t;
typedef graph_traits< graph_t >::vertex_iterator vit_t;
typedef graph_traits< graph_t >::edge_iterator eit_t;
typedef graph_traits< graph_t >::edge_descriptor ed_t;
graph_t theGraph;
class cPredecessorMap
{
public:
cPredecessorMap(graph_t& g )
: myGraph( g )
{
p.resize( num_vertices(myGraph) );
}
vector<int>::iterator
begin()
{
return p.begin();
}
vector<int>
Path( int start, int end )
{
vector<int> path ;
int next = end;
while ( next != start )
{
path.push_back( next );
next = p[ next ];
}
path.push_back( start );
// reverse into path from to
reverse(path.begin(),path.end());
return path;
}
private:
graph_t& myGraph;
vector<int> p;
};
void Add( int src, int dst, int weight )
{
theGraph[add_edge( src, dst, theGraph ).first].Weight( weight );
}
void Construct()
{
std::random_device rd;
std::mt19937_64 gen(rd());
std::uniform_int_distribution<int> dis(0,9);
for( int kvertex = 0; kvertex < 10; kvertex++ )
add_vertex( theGraph );
// connect successive vertices with random weights
for( int kvertex = 1; kvertex < 10; kvertex++ )
Add( kvertex-1, kvertex, dis( gen ) );
// connect random edges
for( int krandom = 0; krandom < 3; krandom++ )
{
int v1, v2;
do
{
v1 = dis(gen);
v2 = dis(gen);
}
while( v1 == v2 );
if( v1 > v2 )
{
int tmp;
tmp = v1;
v1 = v2;
v2 = tmp;
}
Add( dis(gen), dis(gen), dis( gen ));
}
}
void Construct2()
{
for( int kvertex = 0; kvertex < 3; kvertex++ )
add_vertex( theGraph );
Add( 0, 1, 1 );
Add( 0, 2, 2 );
Add( 1, 2, 3 );
}
void ConstructWithCycle()
{
for( int kvertex = 0; kvertex < 10; kvertex++ )
add_vertex( theGraph );
// connect successive vertices with random weights
for( int kvertex = 1; kvertex < 10; kvertex++ )
Add( kvertex-1, kvertex, 1 );
Add( 6, 4, 1 );
Add( 7, 3, 1 );
}
void DisplayEdges()
{
graph_traits<graph_t>::edge_iterator ei, ei_end;
for (boost::tie(ei, ei_end) = edges(theGraph); ei != ei_end; ++ei)
{
std::cout << "(" << source(*ei, theGraph)
<< "," << target(*ei, theGraph)
<< ", w= " << theGraph[ *ei ].Weight()
<< " )\n ";
}
std::cout << std::endl;
}
bool TopSort()
{
try
{
std::vector< int > c;
topological_sort(theGraph, std::back_inserter(c));
}
catch( not_a_dag )
{
cout << "not a dag\n";
return false;
}
cout << "top sort OK\n";
return true;
}
/* Find longest path through the graph
#param[in] g the graph
#param[in] start vertex
#param[in] end vertex
#param[out] path of vertices
#param[out] dist length of path
*/
void Longest(
graph_t& g,
int start,
int end,
vector<int>& path,
int& dist )
{
// create copy of graph with negative weights
graph_t negGraph = g;
graph_traits<graph_t>::edge_iterator ei, ei_end;
for (boost::tie(ei, ei_end) = edges(negGraph); ei != ei_end; ++ei)
{
negGraph[ *ei ].negWeight( );
}
// find shortest paths through negative weight graph
int N = num_vertices(negGraph);
cPredecessorMap pmap( theGraph );
vector<int> distance(N);
bellman_ford_shortest_paths(
negGraph,
N,
weight_map(get(&cEdge::myWeight, negGraph))
.predecessor_map(make_iterator_property_map(pmap.begin(), get(vertex_index, negGraph)))
.distance_map(&distance[0])
.root_vertex( start )
);
path = pmap.Path(start,end);
dist = - distance[ end ];
}
class dag_visitor:public default_dfs_visitor
{
public:
unsigned int * src;
unsigned int * dst;
template < typename Edge, typename Graph >
void back_edge( Edge e, Graph& g)
{
*src = source( e, g );
*dst = target( e, g );
throw runtime_error("cyclic");
}
};
void ConvertToDAG( graph_t& g)
{
dag_visitor vis;
unsigned int src, dst;
vis.src = &src;
vis.dst = &dst;
bool cyclic = true;
while( cyclic )
{
try
{
depth_first_search(g, visitor(vis));
cyclic = false;
}
catch( runtime_error& e )
{
cyclic = true;
cout << "removing " << src << " -> " << dst << endl;
remove_edge( src, dst, g );
}
}
}
int main()
{
ConstructWithCycle();
// create copy to be converted to DAG
graph_t dag( theGraph );
ConvertToDAG( dag );
// find longest path from first to last vertex
vector< int > path;
int dist;
Longest( dag, 0, 9, path, dist );
DisplayEdges();
cout << "Longest path: ";
for( int v : path )
cout << v << " ";
cout << " length " << dist << endl;
return 0;
}
Related
As I cited in previous question:
Is it possible to generate multiple custom vertices using the Bundle Properties from Boost Graph Library?
Boost Maximum Weighted Matching in undirected bipartite random graphs hangs in an infinite loop
I'm working on an application benchmark that compare the performance of the boost maximum weighted matching and auction algorithm for the transportation problem on solving the assignment problem for bipartite graphs.
Currently I've implemented a version of the auction algorithm using the bundle proprieties of boost graph library, this implementation is inspired by a vector version from github. I've done this in order to put on the same level both algorithms, to make a fair benchmark. Here it is:
#include "../include/Auction.h"
#include "../include/BipartiteGraph.h"
void auction_algorithm(Graph& graph, const int& n, duration& elapsed) {
const Weight eps = 1;
int unassigned_bidders = n;
GraphProp& gp = graph[boost::graph_bundle];
EdgeFilter any_interconnect = boost::keep_all{};
VertexFilter bidders = [graph](V v) -> bool { return boost::get<Bidder>(&(graph)[v]); };
VertexFilter items = [graph](V v) -> bool { return boost::get<Item>(&(graph)[v]); };
FMap map_bidders = FMap(graph, any_interconnect, bidders);
FMap map_items = FMap(graph, any_interconnect, items);
auto iterator_bidder = boost::make_iterator_range(boost::vertices(map_bidders));
auto iterator_item = boost::make_iterator_range(boost::vertices(map_items));
auto t_start = now();
while (unassigned_bidders > 0) {
for (auto uncasted_bidder : iterator_bidder) {
if (gp.bidder2item[static_cast<int>(uncasted_bidder)] != -1) continue;
Bidder* bidder = boost::get<Bidder>(&graph[uncasted_bidder]);
// 1 Bid
int id_item1 = -1;
Weight val_item1 = -1;
Weight val_item2 = -1;
for (auto uncasted_item : iterator_item) {
Item* item = boost::get<Item>(&graph[static_cast<int>(uncasted_item)]);
Weight val = boost::get(boost::edge_weight_t(), graph, (boost::edge(uncasted_bidder, uncasted_item, graph)).first) - item->cost;
if (val > val_item1) {
val_item2 = val_item1;
val_item1 = val;
id_item1 = item->id;
}
else if (val > val_item2) {
val_item2 = val;
}
}
bidder->best_item = id_item1 + n;
bidder->val_first_best_item = val_item1;
bidder->val_second_best_item = val_item2;
// 2 Compete
Weight bid = bidder->val_first_best_item - bidder->val_second_best_item + eps;
auto best_item = boost::get<Item>(&graph[bidder->best_item]);
if (bid > best_item->high_bid) {
best_item->high_bid = bid;
best_item->high_bidder = bidder->id;
}
}
// 3 Assign
for (auto uncasted_item : iterator_item) {
Item* item = boost::get<Item>(&graph[uncasted_item]);
if (item->high_bid == -1) continue;
item->cost += item->high_bid;
if (gp.item2bidder[item->id] != -1) {
gp.bidder2item[gp.item2bidder[item->id]] = -1;
unassigned_bidders++;
}
gp.item2bidder[item->id] = item->high_bidder;
gp.bidder2item[gp.item2bidder[item->id]] = item->id;
unassigned_bidders--;
}
}
elapsed = now() - t_start;
}
Weight perform_au(Graph& graph, duration& elapsed) {
int n = int(boost::num_vertices(graph) / 2);
Weight total_cost_auction = 0;
auction_algorithm(graph, n, elapsed);
std::cout << "\nThe matching is: ";
for (int bidder = 0; bidder < n; ++bidder) {
std::cout << "(" << bidder << "," << graph[boost::graph_bundle].bidder2item[bidder] << ")";
int item = graph[boost::graph_bundle].bidder2item[bidder];
total_cost_auction += boost::get(boost::edge_weight_t(), graph, (boost::edge(bidder, item + n, graph)).first);
}
std::cout << "\n";
return total_cost_auction;
}
I have compared this to the vector implementation and notice that the latter is much faster than mine (however they return the same amount of total cost). Is it due to the complexity of the boost::get? If so, why is it so heavy?
I'm using the g++ compiler on a Ubuntu machine and to compile the application I run the following line in my console:
g++ -std=c++2a -o ../bin/app BipartiteGraph.cpp MaximumWeightedMatching.cpp Auction.cpp AuctionArray.cpp Main.cpp
I share the link of my github repository so you can have a look at the whole project.
PS: If you have any suggestions for speeding up the algorithm, that would be great!
UPDATE: 09/08/2022
Requirement: Make the auction algorithm generic like the style of the Boost Graph Library. This is the last implementation that I've made.
UPDATE: 10/08/2022
I've made a class that maintain the all stuff like it was before with the Bundle Properties:
UPDATE: 14/08/2022
Actual version
Weight perform_au(const Graph& graph, Duration& elapsed, int& n_iteration_au, bool verbose)
{
int n = int(boost::num_vertices(graph) / 2);
std::vector<int> assignments(n);
Auction<Graph, Weight> auction_problem(n);
auto t_start = now();
auction_problem.auction_algorithm(graph, assignments);
elapsed = now() - t_start;
std::cout << " Finished \nThe matching is: ";
for (int bidder = 0; bidder < n; ++bidder)
std::cout << "(" << bidder << "," << assignments[bidder] << ")";
std::cout << "\n";
if (verbose) auction_problem.printProprieties();
n_iteration_au = auction_problem.getNIterationAu();
return auction_problem.getTotalCost(graph);
}
#ifndef _AA_H
#define _AA_H
#include <vector>
#include <unordered_map>
#include <boost/graph/adjacency_list.hpp>
template<typename T>
using AdjacencyIterator = boost::graph_traits<T>::adjacency_iterator;
template<typename Graph, typename Type>
class Auction
{
private:
struct Bidder {
int best_item = -1;
double val_first_best_item = -1;
double val_second_best_item = -1;
};
struct Item {
double cost = 0;
int high_bidder = -1;
double high_bid = -1;
};
int n_iteration_au = 0;
int vertices = 0;
std::unordered_map<int, Bidder> unassigned_bidder;
std::unordered_map<int, Bidder> assigned_bidder;
std::unordered_map<int, Item> item_map;
bool is_assignment_problem(const Graph& graph);
void auctionRound(const Graph& graph, const double& eps, const auto& vertex_idMap);
public:
void auction_algorithm(const Graph& graph, std::vector<int>& ass);
int getNIterationAu();
Type getTotalCost(const Graph& graph);
void printProprieties();
Type getMaximumEdge(const Graph& graph);
void reset();
Auction(int vertices)
{
this->vertices = vertices;
for (int i : boost::irange(0, vertices))
{
this->unassigned_bidder.insert(std::make_pair(i, Bidder{}));
this->item_map.insert(std::make_pair(i, Item{}));
}
}
};
template<typename Graph, typename Type>
inline int Auction<Graph, Type>::getNIterationAu() { return n_iteration_au; }
template<typename Graph, typename Type>
Type Auction<Graph, Type>::getMaximumEdge(const Graph& graph)
{
Type max = 0;
typedef boost::graph_traits<Graph>::edge_iterator edge_iterator;
std::pair<edge_iterator, edge_iterator> ei = boost::edges(graph);
for (edge_iterator edge_iter = ei.first; edge_iter != ei.second; ++edge_iter)
if (boost::get(boost::edge_weight_t(), graph, *edge_iter) > max)
max = boost::get(boost::edge_weight_t(), graph, *edge_iter);
return max;
}
template<typename Graph, typename Type>
inline Type Auction<Graph, Type>::getTotalCost(const Graph& graph)
{
Type total_cost_auction = 0;
for (int bidder = 0; bidder < vertices; ++bidder)
total_cost_auction += boost::get(boost::edge_weight_t(), graph, (boost::edge(bidder, assigned_bidder[bidder].best_item + vertices, graph)).first);
return total_cost_auction;
}
template<typename Graph, typename Type>
bool Auction<Graph, Type>::is_assignment_problem(const Graph& graph)
{
for (auto v1 : boost::make_iterator_range(boost::vertices(graph)))
{
AdjacencyIterator<Graph> ai, a_end;
boost::tie(ai, a_end) = boost::adjacent_vertices(v1, graph);
if (ai == a_end) return false;
else
for (auto v2 : boost::make_iterator_range(ai, a_end))
if ((v1 < vertices && v2 < vertices) || (v1 > vertices && v2 > vertices))
return false;
}
return true;
}
template<typename Graph, typename Type>
inline void Auction<Graph, Type>::printProprieties()
{
for (auto& bidder : assigned_bidder)
std::cout << "|Bidder:" << bidder.first << "|Best item:" << bidder.second.best_item << "|Value first best item:" << bidder.second.val_first_best_item << "|Value second best item:" << bidder.second.val_second_best_item << "|\n";
for (auto& item : item_map)
std::cout << "|Item:" << item.first << "|Cost:" << item.second.cost << "|Higher bidder:" << item.second.high_bidder << "|Higher bid:" << item.second.high_bid << "|\n";
}
template<typename Graph, typename Type>
void Auction<Graph, Type>::auctionRound(const Graph& graph, const double& eps, const auto& vertex_idMap)
{
for (auto& bidder : unassigned_bidder)
{
int id_item1 = -1;
double val_item1 = -1;
double val_item2 = -1;
AdjacencyIterator<Graph> ai, a_end;
boost::tie(ai, a_end) = boost::adjacent_vertices(vertex_idMap[bidder.first], graph);
for (auto item : boost::make_iterator_range(ai, a_end)) // itero iniziando da quelli che hanno meno vertici?
{
double val = (boost::get(boost::edge_weight_t(), graph, (boost::edge(bidder.first, static_cast<int>(item), graph)).first)) // * (vertices))
- item_map[static_cast<int>(item) - vertices].cost;
if (val > val_item1)
{
val_item2 = val_item1;
val_item1 = val;
id_item1 = static_cast<int>(item) - vertices;
}
else if (val > val_item2) val_item2 = val;
}
bidder.second.best_item = id_item1;
bidder.second.val_second_best_item = val_item2;
bidder.second.val_first_best_item = val_item1;
double bid = bidder.second.val_first_best_item - bidder.second.val_second_best_item + eps;
if (item_map.find(bidder.second.best_item) != item_map.end())
{
if (bid > item_map[bidder.second.best_item].high_bid)
{
item_map[bidder.second.best_item].high_bid = bid;
item_map[bidder.second.best_item].high_bidder = bidder.first;
}
}
}
for (auto& item : item_map)
{
if (item.second.high_bid == -1) continue;
item.second.cost += item.second.high_bid;
int id_to_remove = -1;
for (auto& ass_bidr : assigned_bidder)
{
if (ass_bidr.second.best_item == item.first)
{
id_to_remove = ass_bidr.first;
break;
}
}
if (id_to_remove != -1)
{
unassigned_bidder.insert(std::make_pair(id_to_remove, assigned_bidder[id_to_remove]));
assigned_bidder.erase(id_to_remove);
}
assigned_bidder.insert(std::make_pair(item.second.high_bidder, unassigned_bidder[item.second.high_bidder]));
unassigned_bidder.erase(item.second.high_bidder);
}
}
template<typename Graph, typename Type>
void Auction<Graph, Type>::auction_algorithm(const Graph& graph, std::vector<int>& ass)
{
if (!is_assignment_problem(graph)) throw("Not an assignment problem");
auto vertex_idMap = boost::get(boost::vertex_index, graph);
double eps = static_cast<double>(1.0 / (vertices + 1));
while (unassigned_bidder.size() > 0)
{
auctionRound(graph, eps, vertex_idMap);
n_iteration_au += 1;
}
for (auto& a : assigned_bidder) ass[a.first] = a.second.best_item;
}
#endif
Why would it not be heavy.
Again,
FMap map_bidders = FMap(graph, any_interconnect, bidders);
FMap map_items = FMap(graph, any_interconnect, items);
Just "wishing" things to be a property map doesn't make them so.
Also, your filter predicates:
EdgeFilter any_interconnect = boost::keep_all{};
VertexFilter bidders = [graph](V v) -> bool { return boost::get<Bidder>(&(graph)[v]); };
VertexFilter items = [graph](V v) -> bool { return boost::get<Item>(&(graph)[v]); };
FMap map_bidders = FMap(graph, any_interconnect, bidders);
FMap map_items = FMap(graph, any_interconnect, items);
They...
copy the entire graph(!), twice
uselessly get<> a variant element, just to discard it and return bool
Slightly better:
VertexFilter bidders = [&graph](V v) -> bool {
return graph[v].which() == 0;
};
VertexFilter items = [&graph](V v) -> bool {
return graph[v].which() == 1;
};
FMap map_bidders = FMap(graph, {}, bidders);
FMap map_items = FMap(graph, {}, items);
But it's all kind of useless. I'm not suprised this stuff takes time, because you know your graph is structured (N bidders)(N items), so
auto iterator_bidder = boost::make_iterator_range(vertices(map_bidders));
auto iterator_item = boost::make_iterator_range(vertices(map_items));
CouldShould just be:
auto [b,e] = vertices(graph);
auto iterator_bidder = boost::make_iterator_range(b, b + n);
auto iterator_item = boost::make_iterator_range(b + n, e);
And even those are overkill, since your vertex descriptor is integral anyways:
auto const bidders = boost::irange(0, n);
auto const items = boost::irange(n, 2 * n);
I'll read some more later (family time first), because I'm already noticing more (e.g. why is listS used as the edge container selector?).
Will post here when done.
I'm new to boost::graph (and boost really). I want to use boost::filtered_graph many times on the same original graph, and use the write_graphviz function to let me visualise the results. I think my understanding must be off though because the following code isn't doing what I think it should: to output the same graph with print_graph and write_graphviz.
MWE (compiled with C++14, gcc 9.3 on Ubuntu 20.04; boost version 1.73):
#include <cstdio>
#include <fstream>
#include <iostream>
#include <boost/graph/copy.hpp>
#include <boost/graph/filtered_graph.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/graph_utility.hpp>
#include <boost/graph/graphviz.hpp>
using namespace std;
typedef boost::adjacency_list< boost::vecS, boost::vecS > Graph;
typedef boost::graph_traits<Graph>::vertex_descriptor vertex_descriptor;
typedef boost::graph_traits<Graph>::vertex_iterator vertex_iterator;
template <typename GraphType>
struct uniform_random_vertex_filter
{
uniform_random_vertex_filter() : prob(1.0) {} // default constructor is required
uniform_random_vertex_filter(float p) : prob(p) {}
bool operator()(const typename boost::graph_traits<GraphType>::vertex_descriptor& v) const
{
return drand48() < prob; // randomly select some vertices
}
private:
float prob;
};
int main(int argn, char **argc) {
unsigned int n = 5;
ofstream of;
Graph g(n);
vertex_iterator vit, uit, vend;
// build a complete graph on n vertices (I'm sure there's a simpler command for this):
for (boost::tie(vit,vend) = vertices(g); vit != vend; ++vit) {
for (uit = vit; uit != vend; ++uit) {
if (uit == vit) { continue; }
add_edge(*vit, *uit, g);
}
}
std::cout << "Original graph (OriginalGraph.dot):" << std::endl;
boost::print_graph(g);
of.open("OriginalGraph.dot", std::ios::trunc);
boost::write_graphviz(of, g);
of.close();
uniform_random_vertex_filter<Graph> vfilter(0.5);
boost::filtered_graph<Graph, boost::keep_all, uniform_random_vertex_filter<Graph> >
filteredGraph(g, boost::keep_all(), vfilter);
std::cout << "Filtered graph -- random selection of vertices (RandomVertexSelection.dot):" << std::endl;
boost::print_graph(filteredGraph);
Graph F;
boost::copy_graph(filteredGraph,F);
of.open("RandomVertexSelection.dot", std::ios::trunc);
boost::write_graphviz(of, F);
of.close();
return 0;
}
Which produces this output:
> Debug/BoostGraphFilter
Original graph:
0 --> 1 2 3 4
1 --> 2 3 4
2 --> 3 4
3 --> 4
4 -->
Filtered graph -- random selection of vertices (RandomVertexSelection.dot):
0 --> 1 2 3 4
1 --> 2 3
2 --> 3
>
--- which is fine, but the dot files are:
> cat OriginalGraph.dot
digraph G {
0;
1;
2;
3;
4;
0->1 ;
0->2 ;
0->3 ;
0->4 ;
1->2 ;
1->3 ;
1->4 ;
2->3 ;
2->4 ;
3->4 ;
}
> cat RandomVertexSelection.dot
digraph G {
0;
1;
2;
}
Hence the filtered_graph that's printed isn't the same as that written to .dot file (which has lost all the edges in this case).
Can someone please help me understand what I've done wrong?
Your filter is random. And since you didn't retain any state to make it transparent or deterministic, the results are random. Simple as that.
Ironically, at the same time you managed to get completely deterministic results across runs because you fail to use random correctly (e.g. seeding the generator).
In your case, the simplest would be to copy before first use: Live On Coliru
using Graph = boost::adjacency_list<boost::vecS, boost::vecS>;
Graph make_complete_graph(size_t n);
void report(Graph const& g, std::string name);
struct uniform_random_vertex_filter {
float prob = 1.0f;
bool operator()(auto v) const {
return drand48() < prob;
}
};
int main() {
Graph g = make_complete_graph(5);
report(g, "OriginalGraph");
for (int pct = 30; pct < 100; pct+=10) {
Graph F;
boost::copy_graph(boost::filtered_graph( //
g, //
boost::keep_all{},
uniform_random_vertex_filter{pct / 100.0f}),
F);
report(F, "RandomVertexSelection_" + std::to_string(pct));
}
}
// build a complete graph on n vertices
Graph make_complete_graph(size_t n)
{
Graph g(n);
for (auto [vit, vend] = vertices(g); vit != vend; ++vit) {
for (auto uit = vit; uit != vend; ++uit) {
if (uit != vit)
add_edge(*vit, *uit, g);
}
}
return g;
}
void report(Graph const& g, std::string name) {
boost::print_graph(g, std::cout << name << ":");
std::ofstream of(name + ".dot");
boost::write_graphviz(of, g);
}
If you want a stable random "cut" of a graph, make the filter stateful. This could be handy e.g. if you have a graph too large to copy.
Fixing The Random
As a sidenote, fixing the random to actually be seeded and reliably uniform:
Live On Coliru
#include <boost/graph/copy.hpp>
#include <boost/graph/filtered_graph.hpp>
#include <boost/graph/graph_utility.hpp>
#include <boost/graph/graphviz.hpp>
#include <fstream>
#include <iostream>
#include <random>
using Graph = boost::adjacency_list<boost::vecS, boost::vecS>;
using Filter = std::function<bool(Graph::vertex_descriptor)>;
Graph make_complete_graph(size_t n);
void report(Graph const& g, std::string name);
int main() {
Graph g = make_complete_graph(5);
report(g, "OriginalGraph");
std::mt19937 urbg{std::random_device{}()};
for (int pct = 30; pct < 100; pct += 10) {
Graph F;
std::bernoulli_distribution dist(pct / 100.);
boost::copy_graph(
boost::filtered_graph(g, boost::keep_all{},
Filter([&](auto) { return dist(urbg); })),
F);
report(F, "RandomVertexSelection_" + std::to_string(pct));
}
}
// build a complete graph on n vertices
Graph make_complete_graph(size_t n)
{
Graph g(n);
for (auto [vit, vend] = vertices(g); vit != vend; ++vit) {
for (auto uit = vit; uit != vend; ++uit) {
if (uit != vit)
add_edge(*vit, *uit, g);
}
}
return g;
}
void report(Graph const& g, std::string name) {
boost::print_graph(g, std::cout << name << ":");
std::ofstream of(name + ".dot");
boost::write_graphviz(of, g);
}
Now prints different random cuts every run.
A C++11 implementation of the Dijkstra Algorithm by Michal Forisek does compute the shortest distance quite fast & elegantly with not too much code. But how can I return the path too ?
struct edge
{
edge(const int _to, const int _len): to(_to), length(_len)
{
}
int to;
int length;
};
int dijkstra(const vector< vector<edge> > &graph, int source, int target) {
vector<int> min_distance( graph.size(), INT_MAX );
min_distance[ source ] = 0;
set< pair<int,int> > active_vertices;
active_vertices.insert( {0,source} );
while (!active_vertices.empty()) {
int where = active_vertices.begin()->second;
if (where == target) return min_distance[where];
active_vertices.erase( active_vertices.begin() );
for (auto ed : graph[where])
if (min_distance[ed.to] > min_distance[where] + ed.length) {
active_vertices.erase( { min_distance[ed.to], ed.to } );
min_distance[ed.to] = min_distance[where] + ed.length;
active_vertices.insert( { min_distance[ed.to], ed.to } );
}
}
return INT_MAX;
}
int main()
{
std::vector<edge> node0 {edge(1,1), edge (3,7), edge (2,1)};
std::vector<edge> node1 {edge(0,1), edge (2,2), edge (3,4)};
std::vector<edge> node2 {edge(1,2), edge (3,3), edge (0,1)};
std::vector<edge> node3 {edge(2,3), edge (0,7), edge (1,4)};
std::vector<std::vector<edge>> graph {node0, node1, node2, node3};
int r = dijkstra(graph, 0, 3);
return 0;
}
You can make it return the shortest path by creating a "parents" list. Basically, this list will hold the parent of each vertex you track. By parent, I mean that for any vertex, the parent of that vertex is the node previous to it in the shortest path. When you update the min_distance list, you should also update the "parents" list by setting the parent of your vertex "ed.to" to "where". Then you can return the parents list and trace through it to find the shortest path. Simply visit the goal node's parent, and move sequentially until you find a node whose parent is the source. That's your path.
Instead of returning:
Starting at the destination, examine all nodes with edges going to it. Pick the node adjacent to the destination with the lowest min distance+ed.length to the destination node. If the adjacent node is not in the min distance map, ignore it.
This is your new destination. Repeat until your destination is your source.
Basically you can greedily walk back to the start, because you know which node is cheapest to get to the start.
This is cheap if your edges are bidirectional, or if you have a way to look up edges backwards.
Otherwise, tracking both min distance and node you came from in the min distance map lets you do it just as easily.
struct edge
{
int to;
int length;
};
using node = std::vector<edge>;
using graph = std::vector<node>;
void add_edge( graph& g, int start, int finish, int length ) {
if ((int)g.size() <= (std::max)(start, finish))
g.resize( (std::max)(start,finish)+1 );
g[start].push_back( {finish, length} );
g[finish].push_back( {start, length} );
}
using path = std::vector<int>;
struct result {
int distance;
path p;
};
result dijkstra(const graph &graph, int source, int target) {
std::vector<int> min_distance( graph.size(), INT_MAX );
min_distance[ source ] = 0;
std::set< std::pair<int,int> > active_vertices;
active_vertices.insert( {0,source} );
while (!active_vertices.empty()) {
int where = active_vertices.begin()->second;
if (where == target)
{
int cost = min_distance[where];
// std::cout << "cost is " << cost << std::endl;
path p{where};
while (where != source) {
int next = where;
for (edge e : graph[where])
{
// std::cout << "examine edge from " << where << "->" << e.to << " length " << e.length << ":";
if (min_distance[e.to] == INT_MAX)
{
// std::cout << e.to << " unexplored" << std::endl;
continue;
}
if (e.length + min_distance[e.to] != min_distance[where])
{
// std::cout << e.to << " not on path" << std::endl;
continue;
}
next = e.to;
p.push_back(next);
// std::cout << "backtracked to " << next << std::endl;
break;
}
if (where==next)
{
// std::cout << "got lost at " << where << std::endl;
break;
}
where = next;
}
std::reverse( p.begin(), p.end() );
return {cost, std::move(p)};
}
active_vertices.erase( active_vertices.begin() );
for (auto ed : graph[where])
if (min_distance[ed.to] > min_distance[where] + ed.length) {
active_vertices.erase( { min_distance[ed.to], ed.to } );
min_distance[ed.to] = min_distance[where] + ed.length;
active_vertices.insert( { min_distance[ed.to], ed.to } );
}
}
return {INT_MAX};
}
int main()
{
graph g;
add_edge(g, 0, 1, 1);
add_edge(g, 0, 3, 7);
add_edge(g, 0, 2, 1);
add_edge(g, 1, 2, 2);
add_edge(g, 1, 3, 4);
add_edge(g, 2, 3, 3);
auto r = dijkstra(g, 0, 3);
std::cout << "cost is " << r.distance << ": ";
for (int x:r.p) {
std::cout << x << " then ";
}
std::cout << "and we are done.\n";
return 0;
}
Live example.
I am accepting undirected weighted graph from input file. File contains 200 nodes.
I have written code like this.
typedef pair<int, int> ipair;
class Graph
{
int V;
list< pair <int, int> > *adj;
public:
Graph(int V);
};
Graph::Graph( int V)
{
this-> V = V;
adj = new list<ipair>[V];
}
bool Graph :: read_file()
{
const long int N = 1000000;
std::ifstream infile("Dijkstra.txt");
if(!infile.is_open()) return false;
std::string line;
int i = 0;
while ( i < N && getline(infile, line) )
{
std::istringstream str(line);
int u;
str >> u;
if ( u > N )
{
// Problem.
abort();
}
int v;
int w;
while ( str >> v >> w)
{
adj[u].push_back(make_pair(v,w));
}
++i;
}
}
int main()
{
Graph g(200);
g.read_file();
g.print_graph();
return 0;
}
I/P file :
1 80,982 163,8164 170,2620 145,648 200,8021 173,2069 92,647 26,4122 140,546 11,1913 160,6461 27,7905 40,9047 150,2183 61,9146 159,7420 198,1724 114,508 104,6647 30,4612 99,2367 138,7896 169,8700 49,2437 125,2909 117,2597 55,6399
2 42,1689 127,9365 5,8026 170,9342 131,7005 172,1438 34,315 30,2455 26,2328 6,8847 11,1873 17,5409 157,8643 159,1397 142,7731 182,7908 93,8177
Node 1 is connected to node 80 with weight 982
Node 1 is connected to node 163 with weight 8164
Node 2 is connected to node 42 with weight 1689
Node 2 is connected to node 127 with weight 9365
etc.....
Now with this I can accept node 1 is connected to node 80 with weight 982 (1 80,982 ) but what about remaining nodes which are connected with node 1 ??
i.e. How to make loop for accepting v and w ??
I think instead of list< pair <int, int> > *adj in Graph class you can use vector<list< pair <int, int> >> adj to store the data.
I have modified your program slightly to store all the pair of data related to a node.
#include <iostream>
#include <utility>
#include <list>
#include <fstream>
#include <sstream>
#include <cstdlib>
#include <vector>
using namespace std;
typedef pair<int, int> ipair;
class Graph
{
int V;
vector<list< pair <int, int> >> adj;
public:
bool read_file();
void print_graph();
Graph(int V);
};
Graph::Graph( int V)
{
this-> V = V;
adj.reserve(V);
}
bool Graph:: read_file()
{
const long int N = 1000000;
std::ifstream infile("Dijkstra.txt");
if(!infile.is_open()) return false;
std::string line;
int i = 0;
while ( i < N && getline(infile, line) ) {
std::istringstream str(line);
int u;
str >> u;
if ( u > N ) {
// Problem.
abort();
}
int v;
int w;
char c;
while ( str >> v >> c >> w) {
if (u <= (int)adj.size()) { // index (u-1) exists
auto list = adj[u-1]; // get the existing list
list.push_back(make_pair(v,w)); // add new data
adj[u-1] = list; // store it the same index
} else { // index (u-1) doesn't exist
list<ipair> list; // create a new list
list.push_back(make_pair(v,w)); // store the values
adj.push_back(list); // add it in the vector
}
}
++i;
}
return true;
}
void Graph::print_graph() {
int node = 1;
for (auto& x : adj) {
cout << "from node: " << node++ << endl;
for (auto it = begin(x); it != end(x); ++it) {
cout << it->first << " " << it->second << "\n";
}
}
}
int main() {
Graph g(200);
g.read_file();
g.print_graph();
return 0;
}
You can regex match ,.
e.g.
std::regex pairs_regex("(\d+),(\d+)");
auto pairs_it = std::sregex_iterator(line.begin(), line.end(), pairs_regex);
auto pairs_end = std::sregex_iterator();
for(;pairs_it != pairs_end; ++pairs_it)
{
std::string v = pairs_it->str(1);
std::string w = pairs_it->str(2);
adj[u].push_back(make_pair(std::stoi(v), std::stoi(w)));
}
N.B. it would be safer if adj were a std:: container, not a raw array. The loop body would populate a list<pair<int, int>> temp, then adj.emplace_back(std::move(temp))
Hello I want to build a graph for connecting sentences.
for example my files has following lines.
ab cd ef
ef gh ij
ij kl mn
xy ab cd
So I want each node should have one line i.e. ab cd ef should be one node and it should be connected to ef gh ij which should be connected to ij kl mn.
Basically last word of a line should be connect to any line whose first word matches with last word.
Here is what I have come up so far, but failing when I add Edges.
#include <map>
#include <string>
#include <deque>
#include <list>
#include <iostream>
#include <stack>
#include <fstream>
#include <vector>
class GraphNode {
public:
GraphNode(std::string name) {
std::vector<std::string> words;
std::string::size_type lastPos = name.find_first_not_of(' ', 0);
std::string::size_type pos = name.find_first_of(' ', lastPos);
while (std::string::npos != pos || std::string::npos != lastPos){
words.push_back(name.substr(lastPos, pos - lastPos));
lastPos = name.find_first_not_of(' ', pos);
pos = name.find_first_of(' ', lastPos);
}
first = words[0];
middle = " ";
for ( int i = 1; i < (int)words.size() - 1; i++) {
middle = words[i] + " ";
}
last = words[words.size() - 1 ];
}
~GraphNode() {};
std::string first;
std::string middle;
std::string last;
};
struct GraphNodeCompare {
bool operator() (const GraphNode& lhs, const GraphNode& rhs) {
return lhs.last < rhs.last;
}
};
class Graph {
public:
Graph() {}
~Graph() {}
typedef std::map <GraphNode, std::list<GraphNode>, GraphNodeCompare > GraphType;
void AddVertex ( GraphNode vertexID );
void AddEdge ( GraphNode vertexLeft, GraphNode vertexRight);
std::list<GraphNode> GetVertices(GraphNode vertexID);
friend std::ostream& operator << (std::ostream& os, const Graph& dt);
private:
GraphType m_graph;
protected:
};
void Graph::AddVertex(GraphNode vertexID) {
GraphType::const_iterator iter = m_graph.find(vertexID);
if ( iter == m_graph.end()) {
std::list<GraphNode> list;
m_graph[vertexID] = list;
}
}
void Graph::AddEdge( GraphNode vertexLeft, GraphNode vertexRight) {
AddVertex(vertexLeft);
AddVertex(vertexRight);
m_graph[vertexLeft].push_back(vertexRight);
m_graph[vertexRight].push_back(vertexLeft);
}
std::list<GraphNode> Graph::GetVertices(GraphNode vertexID) {
GraphType::const_iterator iter = m_graph.find(vertexID);
std::list<GraphNode> list;
if ( iter != m_graph.end()){
return m_graph[vertexID];
}
return list;
}
std::ostream& operator << (std::ostream& os, const Graph& graph) {
std::cout << "---------------------------------------------" << std::endl;
std::map<GraphNode, std::list<GraphNode>, GraphNodeCompare >::const_iterator iter;
for ( iter = graph.m_graph.begin(); iter != graph.m_graph.end(); ++iter) {
std::cout << iter->first.first << iter->first.middle << iter->first.last << " : " ;
std::list<GraphNode> list = iter->second;
std::list<GraphNode>::const_iterator iter1;
for ( iter1 = list.begin(); iter1 != list.end(); ++iter1) {
std::cout << iter1->first << iter1->middle << iter1->last << '\t' ;
}
std::cout << std::endl;
}
std::cout << "---------------------------------------------" << std::endl;
return os;
}
int main( int argc, char **argv) {
Graph *pGraph = new Graph();
std::ifstream dataFile("D:\\personal\\data\\datas3.txt");
if ( dataFile.peek() == EOF ) {
return -1;
}
if (dataFile.is_open()) {
while (! dataFile.eof() ) {
std::string line;
std::getline (dataFile,line);
GraphNode node(line);
pGraph->AddVertex(node);
std::list<GraphNode> vertices = pGraph->GetVertices(node);
for ( std::list<GraphNode>::iterator itr = vertices.begin(); itr != vertices.end(); ++itr) {
pGraph->AddEdge( node, *itr);
}
//std::cout << line << std::endl;
}
}
dataFile.close();
//std::cout << *pGraph;
delete pGraph;
}
I can suggest this tiny, non object-oriented implementation. Works fine for you problem:
#include <iostream>
#include <sstream>
#include <fstream>
#include <vector>
#include <string>
typedef std::vector< std::string > Node;
typedef std::pair< int, int > Edge;
// Node stuff
std::string firstWord ( const Node& node ) { return *node.begin(); }
std::string lastWord ( const Node& node ) { return *node.rbegin(); }
void addWord ( Node& node, std::string s ) { node.push_back( s ); }
bool isNotEmpty( const Node& node ) { return !node.empty(); }
bool precedes( const Node& a, const Node& b ) { return lastWord( a ) == firstWord( b ); }
struct Graph
{
void addNode ( const Node& node ) { nodes.push_back( node ); }
void addEdge ( const int& from, const int& to ) { edges.push_back( Edge( from, to ) ); }
std::vector< Edge > edges;
std::vector< Node > nodes;
};
std::ostream& operator << ( std::ostream& out, const Graph& graph )
{
int esize = graph.edges.size();
for( int i = 0; i < esize; ++i )
{
int index1 = graph.edges[ i ].first, index2 = graph.edges[ i ].second;
for( int j = 0; j < graph.nodes[ index1 ].size(); ++j )
out << graph.nodes[ index1 ][ j ] << ' ';
out << "----> ";
for( int j = 0; j < graph.nodes[ index2 ].size(); ++j )
out << graph.nodes[ index2 ][ j ] << ' ';
out << std::endl;
}
return out;
}
int main ()
{
Graph graph;
std::ifstream inputFile( "input.txt" );
std::string s;
// reading from file and constructing graph vertices
if( inputFile.is_open() )
while( !inputFile.eof() )
{
std::getline( inputFile, s );
std::stringstream ss( s );
Node node;
while( ss >> s )
addWord( node, s );
if( isNotEmpty( node ) )
graph.addNode( node );
}
inputFile.close();
// constructing graph edges
std::vector< Node > nodes ( graph.nodes );
int sz = nodes.size();
for( int i = 0; i < sz; ++i )
for( int j = 0; j < sz; ++j )
if( precedes( nodes[ i ], nodes[ j ] ) )
graph.addEdge( i, j );
// let's see what we got
std::cout << graph;
return 0;
}
Also, as #spraff says, if you want to use a well-designed graph library, have a look at Boost.
Have you considered one of the excellent Boost libraries?