// Tranche 2 : QuikGraph 2.5.0 (moteur .NET natif) sur la MEME instance que la tranche 1.
#r "nuget: QuikGraph, 2.5.0"
using QuikGraph;
using QuikGraph.Algorithms.Search;
using QuikGraph.Algorithms.ShortestPath;
using QuikGraph.Algorithms.Observers;
// Graphe France reconstruit comme graphe oriente bidirectionnel : chaque route
// non-orientee devient deux arcs opposes, memes 13 villes, memes poids (km).
STaggedEdge<string,double> QE(string a, string b, double w) => new(a, b, w);
var qEdges = new List<STaggedEdge<string,double>>();
var seen = new HashSet<(string,string)>();
foreach (var (city, neighbors) in franceGraph)
foreach (var (nb, dist) in neighbors)
if (seen.Add((city, nb))) { qEdges.Add(QE(city, nb, dist)); qEdges.Add(QE(nb, city, dist)); }
var qGraph = qEdges.ToBidirectionalGraph<string, STaggedEdge<string,double>>();
Console.WriteLine($"Graphe QuikGraph : {qGraph.VertexCount} villes, {qGraph.EdgeCount} arcs (bidirectionnel)");
// Parcours BFS via le moteur : ordre de decouverte + arbre de parente (TreeEdge).
static (string[] order, Dictionary<string,string> parent) QuikBFS(IBidirectionalGraph<string, STaggedEdge<string,double>> g, string root)
{
var bfs = new BreadthFirstSearchAlgorithm<string, STaggedEdge<string,double>>(g);
var order = new List<string>();
var parent = new Dictionary<string,string>();
bfs.DiscoverVertex += v => { order.Add(v); parent.TryAdd(v, null); };
bfs.TreeEdge += e => parent[e.Target] = e.Source;
bfs.Compute(root);
return (order.ToArray(), parent);
}
// Parcours DFS via le moteur (meme contrat d'evenements).
static (string[] order, Dictionary<string,string> parent) QuikDFS(IBidirectionalGraph<string, STaggedEdge<string,double>> g, string root)
{
var dfs = new DepthFirstSearchAlgorithm<string, STaggedEdge<string,double>>(g);
var order = new List<string>();
var parent = new Dictionary<string,string>();
dfs.DiscoverVertex += v => { order.Add(v); parent.TryAdd(v, null); };
dfs.TreeEdge += e => parent[e.Target] = e.Source;
dfs.Compute(root);
return (order.ToArray(), parent);
}
// UCS via le moteur : Dijkstra (UCS = Dijkstra sur graphe a couts >= 0), distance + retrace du
// chemin via l'observateur canonique VertexPredecessorRecorderObserver (API Attach + TryGetPath).
static (double cost, string[] path) QuikDijkstraPath(IBidirectionalGraph<string, STaggedEdge<string,double>> g, string root, string goal)
{
var dij = new DijkstraShortestPathAlgorithm<string, STaggedEdge<string,double>>(g, e => e.Tag);
var rec = new VertexPredecessorRecorderObserver<string, STaggedEdge<string,double>>();
rec.Attach(dij);
dij.Compute(root);
var d = dij.GetDistance(goal);
if (double.IsInfinity(d)) return (double.PositiveInfinity, Array.Empty<string>());
if (goal == root) return (d, new[] { root });
if (!rec.TryGetPath(goal, out var aretes)) return (d, Array.Empty<string>());
var path = new[] { aretes.First().Source }.Concat(aretes.Select(a => a.Target)).ToArray();
return (d, path);
}
// Chemin depuis l'arbre de parente BFS/DFS, cout du chemin dans le graphe QuikGraph.
static string[] RetracePath(string root, string goal, Dictionary<string,string> parent)
{
var path = new List<string>();
for (var v = goal; v != null; v = parent.TryGetValue(v, out var p) ? p : null) {
path.Insert(0, v);
if (v == root) break;
}
return path.ToArray();
}
static double PathCost(IBidirectionalGraph<string, STaggedEdge<string,double>> g, string[] path)
{
double c = 0;
for (int i = 0; i + 1 < path.Length; i++)
c += g.OutEdges(path[i]).First(e => e.Target == path[i + 1]).Tag;
return c;
}
// Comparaison chiffree sur la MEME instance : les 5 trajets de la section 6.
Console.WriteLine();
Console.WriteLine("Comparaison chiffree : tranche 1 (from-scratch) vs tranche 2 (QuikGraph)");
Console.WriteLine("Meme instance : les 5 trajets de la section 6, memes poids (km).");
Console.WriteLine(new string('=', 116));
foreach (var (start, goal) in testCases) {
var problem = new GraphProblem(start, goal, franceGraph);
var rBFS = BFS(problem);
var rDFS = DFS(problem);
var rUCS = UCS(problem);
var (_, parentB) = QuikBFS(qGraph, start);
var (_, parentD) = QuikDFS(qGraph, start);
var bPath = RetracePath(start, goal, parentB);
var dPath = RetracePath(start, goal, parentD);
var (uCost, uPath) = QuikDijkstraPath(qGraph, start, goal);
var bCost = PathCost(qGraph, bPath);
var dCost = PathCost(qGraph, dPath);
string Fmt(string[] p) => p.Length == 0 ? "NON TROUVE" : string.Join(" -> ", p);
string Trim(string s) => s.Length > 44 ? s.Substring(0, 41) + "..." : s;
bool parBFS = rBFS.Found && bPath.Length > 0 && rBFS.Path.SequenceEqual(bPath);
bool parDFS = rDFS.Found && dPath.Length > 0 && rDFS.Path.SequenceEqual(dPath);
bool parUCS = rUCS.Found && Math.Abs(rUCS.Cost - uCost) < 1e-6;
Console.WriteLine($"\n{start} -> {goal}");
Console.WriteLine(new string('-', 116));
Console.WriteLine($"{"Algo",-5} {"Tranche 1 (from-scratch)",-46} {"Tranche 2 (QuikGraph)",-46} {"Cout T1",-8} {"Cout T2",-8} {"Parite",-7}");
Console.WriteLine(new string('-', 116));
Console.WriteLine($"{"BFS",-5} {Trim(Fmt(rBFS.Path.ToArray())),-46} {Trim(Fmt(bPath)),-46} {rBFS.Cost.ToString("F0", fr),-8} {bCost.ToString("F0", fr),-8} {(parBFS ? "OK" : "ECART"),-7}");
Console.WriteLine($"{"DFS",-5} {Trim(Fmt(rDFS.Path.ToArray())),-46} {Trim(Fmt(dPath)),-46} {rDFS.Cost.ToString("F0", fr),-8} {dCost.ToString("F0", fr),-8} {(parDFS ? "OK" : "ECART"),-7}");
Console.WriteLine($"{"UCS",-5} {Trim(Fmt(rUCS.Path.ToArray())),-46} {Trim(Fmt(uPath)),-46} {rUCS.Cost.ToString("F0", fr),-8} {uCost.ToString("F0", fr),-8} {(parUCS ? "OK" : "ECART"),-7}");
}