more SSSP algos for directed weighted
parent
5b1c05819e
commit
93e129ae7c
|
@ -113,8 +113,38 @@ namespace MoonTools.Core.Graph
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IEnumerable<(TNode, TNode)> ShortestPath(TNode start, TNode end, Func<TNode, IEnumerable<(TNode, TNode, int)>> SSSPAlgorithm)
|
||||||
|
{
|
||||||
|
CheckNodes(start, end);
|
||||||
|
|
||||||
|
var cameFrom = new PooledDictionary<TNode, TNode>(ClearMode.Always);
|
||||||
|
var reachable = new PooledSet<TNode>(ClearMode.Always);
|
||||||
|
|
||||||
|
foreach (var (node, previous, weight) in SSSPAlgorithm(start))
|
||||||
|
{
|
||||||
|
cameFrom[node] = previous;
|
||||||
|
reachable.Add(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!reachable.Contains(end))
|
||||||
|
{
|
||||||
|
cameFrom.Dispose();
|
||||||
|
reachable.Dispose();
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var edge in ReconstructPath(cameFrom, end).Reverse())
|
||||||
|
{
|
||||||
|
yield return edge;
|
||||||
|
}
|
||||||
|
|
||||||
|
cameFrom.Dispose();
|
||||||
|
reachable.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerable<(TNode, TNode, int)> DijkstraSingleSourceShortestPath(TNode source)
|
public IEnumerable<(TNode, TNode, int)> DijkstraSingleSourceShortestPath(TNode source)
|
||||||
{
|
{
|
||||||
|
if (weights.Values.Any(w => w < 0)) { throw new NegativeWeightException("Dijkstra cannot be used on a graph with negative edge weights. Try Bellman-Ford"); }
|
||||||
CheckNodes(source);
|
CheckNodes(source);
|
||||||
|
|
||||||
var distance = new PooledDictionary<TNode, int>(ClearMode.Always);
|
var distance = new PooledDictionary<TNode, int>(ClearMode.Always);
|
||||||
|
@ -148,7 +178,7 @@ namespace MoonTools.Core.Graph
|
||||||
|
|
||||||
foreach (var node in Nodes)
|
foreach (var node in Nodes)
|
||||||
{
|
{
|
||||||
if (!node.Equals(source))
|
if (previous.ContainsKey(node) && distance.ContainsKey(node))
|
||||||
{
|
{
|
||||||
yield return (node, previous[node], distance[node]);
|
yield return (node, previous[node], distance[node]);
|
||||||
}
|
}
|
||||||
|
@ -157,5 +187,62 @@ namespace MoonTools.Core.Graph
|
||||||
distance.Dispose();
|
distance.Dispose();
|
||||||
previous.Dispose();
|
previous.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<(TNode, TNode)> DijkstraShortestPath(TNode start, TNode end)
|
||||||
|
{
|
||||||
|
return ShortestPath(start, end, DijkstraSingleSourceShortestPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<(TNode, TNode, int)> BellmanFordSingleSourceShortestPath(TNode source)
|
||||||
|
{
|
||||||
|
CheckNodes(source);
|
||||||
|
|
||||||
|
var distance = new PooledDictionary<TNode, int>(ClearMode.Always);
|
||||||
|
var previous = new PooledDictionary<TNode, TNode>(ClearMode.Always);
|
||||||
|
|
||||||
|
foreach (var node in Nodes)
|
||||||
|
{
|
||||||
|
distance[node] = int.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
distance[source] = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < Order; i++)
|
||||||
|
{
|
||||||
|
foreach (var (v, u) in Edges)
|
||||||
|
{
|
||||||
|
var weight = Weight(v, u);
|
||||||
|
if (distance[v] + weight < distance[u])
|
||||||
|
{
|
||||||
|
distance[u] = distance[v] + weight;
|
||||||
|
previous[u] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (v, u) in Edges)
|
||||||
|
{
|
||||||
|
if (distance[v] + Weight(v, u) < distance[u])
|
||||||
|
{
|
||||||
|
throw new NegativeCycleException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var node in Nodes)
|
||||||
|
{
|
||||||
|
if (previous.ContainsKey(node) && distance.ContainsKey(node))
|
||||||
|
{
|
||||||
|
yield return (node, previous[node], distance[node]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
distance.Dispose();
|
||||||
|
previous.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<(TNode, TNode)> BellmanFordShortestPath(TNode start, TNode end)
|
||||||
|
{
|
||||||
|
return ShortestPath(start, end, BellmanFordSingleSourceShortestPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace MoonTools.Core.Graph
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public class NegativeCycleException : Exception
|
||||||
|
{
|
||||||
|
public NegativeCycleException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public NegativeCycleException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public NegativeCycleException(string message, Exception innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected NegativeCycleException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace MoonTools.Core.Graph
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public class NegativeWeightException : Exception
|
||||||
|
{
|
||||||
|
public NegativeWeightException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public NegativeWeightException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public NegativeWeightException(string message, Exception innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected NegativeWeightException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -264,5 +264,273 @@ namespace Tests
|
||||||
// have to call Count() because otherwise the lazy evaluation wont trigger
|
// have to call Count() because otherwise the lazy evaluation wont trigger
|
||||||
myGraph.Invoking(x => x.DijkstraSingleSourceShortestPath('z').Count()).Should().Throw<System.ArgumentException>();
|
myGraph.Invoking(x => x.DijkstraSingleSourceShortestPath('z').Count()).Should().Throw<System.ArgumentException>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void DijkstraShortestPath()
|
||||||
|
{
|
||||||
|
var run = new MoveTypeEdgeData { moveType = MoveType.Run };
|
||||||
|
var jump = new MoveTypeEdgeData { moveType = MoveType.Jump };
|
||||||
|
var wallJump = new MoveTypeEdgeData { moveType = MoveType.WallJump };
|
||||||
|
|
||||||
|
var myGraph = new DirectedWeightedGraph<char, MoveTypeEdgeData>();
|
||||||
|
myGraph.AddNodes('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h');
|
||||||
|
myGraph.AddEdges(
|
||||||
|
('a', 'b', 2, run),
|
||||||
|
('a', 'c', 3, run),
|
||||||
|
('a', 'e', 4, wallJump),
|
||||||
|
('b', 'd', 2, jump),
|
||||||
|
('b', 'e', 1, run),
|
||||||
|
('c', 'g', 4, jump),
|
||||||
|
('c', 'h', 11, run),
|
||||||
|
('d', 'c', 3, jump),
|
||||||
|
('d', 'f', 2, run),
|
||||||
|
('d', 'h', 3, wallJump),
|
||||||
|
('e', 'f', 5, run),
|
||||||
|
('f', 'd', 2, run),
|
||||||
|
('f', 'h', 6, wallJump),
|
||||||
|
('g', 'h', 7, run),
|
||||||
|
('h', 'f', 1, jump)
|
||||||
|
);
|
||||||
|
|
||||||
|
myGraph
|
||||||
|
.DijkstraShortestPath('a', 'h')
|
||||||
|
.Select(pair => myGraph.EdgeData(pair.Item1, pair.Item2))
|
||||||
|
.Should()
|
||||||
|
.ContainInOrder(
|
||||||
|
run, jump, wallJump
|
||||||
|
)
|
||||||
|
.And
|
||||||
|
.HaveCount(3);
|
||||||
|
|
||||||
|
// have to call Count() because otherwise the lazy evaluation wont trigger
|
||||||
|
myGraph.Invoking(x => x.DijkstraShortestPath('a', 'z').Count()).Should().Throw<System.ArgumentException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void DijkstraNotReachable()
|
||||||
|
{
|
||||||
|
var run = new MoveTypeEdgeData { moveType = MoveType.Run };
|
||||||
|
var jump = new MoveTypeEdgeData { moveType = MoveType.Jump };
|
||||||
|
var wallJump = new MoveTypeEdgeData { moveType = MoveType.WallJump };
|
||||||
|
|
||||||
|
var myGraph = new DirectedWeightedGraph<char, MoveTypeEdgeData>();
|
||||||
|
myGraph.AddNodes('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h');
|
||||||
|
myGraph.AddEdges(
|
||||||
|
('a', 'b', 2, run),
|
||||||
|
('a', 'c', 3, run),
|
||||||
|
('a', 'e', 4, wallJump),
|
||||||
|
('b', 'd', 2, jump),
|
||||||
|
('b', 'e', 1, run),
|
||||||
|
('c', 'g', 4, jump),
|
||||||
|
('c', 'h', 11, run),
|
||||||
|
('d', 'c', 3, jump),
|
||||||
|
('d', 'h', 3, wallJump),
|
||||||
|
('f', 'd', 2, run),
|
||||||
|
('f', 'h', 6, wallJump),
|
||||||
|
('g', 'h', 7, run)
|
||||||
|
);
|
||||||
|
|
||||||
|
myGraph
|
||||||
|
.DijkstraShortestPath('a', 'f')
|
||||||
|
.Should()
|
||||||
|
.BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void DijkstraNegativeWeight()
|
||||||
|
{
|
||||||
|
var run = new MoveTypeEdgeData { moveType = MoveType.Run };
|
||||||
|
var jump = new MoveTypeEdgeData { moveType = MoveType.Jump };
|
||||||
|
var wallJump = new MoveTypeEdgeData { moveType = MoveType.WallJump };
|
||||||
|
|
||||||
|
var myGraph = new DirectedWeightedGraph<char, MoveTypeEdgeData>();
|
||||||
|
myGraph.AddNodes('a', 'b', 'c', 'd');
|
||||||
|
myGraph.AddEdges(
|
||||||
|
('a', 'b', -2, run),
|
||||||
|
('a', 'c', 3, run),
|
||||||
|
('b', 'd', 2, jump),
|
||||||
|
('d', 'c', -3, jump)
|
||||||
|
);
|
||||||
|
|
||||||
|
myGraph
|
||||||
|
.Invoking(x => x.DijkstraShortestPath('a', 'd').Count())
|
||||||
|
.Should()
|
||||||
|
.Throw<NegativeWeightException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void BellmanFordSingleSourceShortestPath()
|
||||||
|
{
|
||||||
|
var run = new MoveTypeEdgeData { moveType = MoveType.Run };
|
||||||
|
var jump = new MoveTypeEdgeData { moveType = MoveType.Jump };
|
||||||
|
var wallJump = new MoveTypeEdgeData { moveType = MoveType.WallJump };
|
||||||
|
|
||||||
|
var myGraph = new DirectedWeightedGraph<char, MoveTypeEdgeData>();
|
||||||
|
myGraph.AddNodes('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h');
|
||||||
|
myGraph.AddEdges(
|
||||||
|
('a', 'b', 2, run),
|
||||||
|
('a', 'c', 3, run),
|
||||||
|
('a', 'e', 4, wallJump),
|
||||||
|
('b', 'd', 2, jump),
|
||||||
|
('b', 'e', 1, run),
|
||||||
|
('c', 'g', 4, jump),
|
||||||
|
('c', 'h', 11, run),
|
||||||
|
('d', 'c', 3, jump),
|
||||||
|
('d', 'f', 2, run),
|
||||||
|
('d', 'h', 3, wallJump),
|
||||||
|
('e', 'f', 5, run),
|
||||||
|
('f', 'd', 2, run),
|
||||||
|
('f', 'h', 6, wallJump),
|
||||||
|
('g', 'h', 7, run),
|
||||||
|
('h', 'f', 1, jump)
|
||||||
|
);
|
||||||
|
|
||||||
|
myGraph
|
||||||
|
.BellmanFordSingleSourceShortestPath('a')
|
||||||
|
.Should()
|
||||||
|
.Contain(('b', 'a', 2)).And
|
||||||
|
.Contain(('c', 'a', 3)).And
|
||||||
|
.Contain(('d', 'b', 4)).And
|
||||||
|
.Contain(('e', 'b', 3)).And
|
||||||
|
.Contain(('f', 'd', 6)).And
|
||||||
|
.Contain(('g', 'c', 7)).And
|
||||||
|
.Contain(('h', 'd', 7)).And
|
||||||
|
.HaveCount(7);
|
||||||
|
|
||||||
|
// have to call Count() because otherwise the lazy evaluation wont trigger
|
||||||
|
myGraph.Invoking(x => x.BellmanFordSingleSourceShortestPath('z').Count()).Should().Throw<System.ArgumentException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void BellmanFordSingleSourceShortestPathWithNegative()
|
||||||
|
{
|
||||||
|
var run = new MoveTypeEdgeData { moveType = MoveType.Run };
|
||||||
|
var jump = new MoveTypeEdgeData { moveType = MoveType.Jump };
|
||||||
|
var wallJump = new MoveTypeEdgeData { moveType = MoveType.WallJump };
|
||||||
|
|
||||||
|
var myGraph = new DirectedWeightedGraph<char, MoveTypeEdgeData>();
|
||||||
|
myGraph.AddNodes('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h');
|
||||||
|
myGraph.AddEdges(
|
||||||
|
('a', 'b', 2, run),
|
||||||
|
('a', 'c', 3, run),
|
||||||
|
('a', 'e', 4, wallJump),
|
||||||
|
('b', 'd', -1, jump),
|
||||||
|
('b', 'e', 1, run),
|
||||||
|
('c', 'g', 4, jump),
|
||||||
|
('c', 'h', 11, run),
|
||||||
|
('d', 'c', 3, jump),
|
||||||
|
('d', 'f', 2, run),
|
||||||
|
('d', 'h', 3, wallJump),
|
||||||
|
('e', 'f', 5, run),
|
||||||
|
('f', 'd', 2, run),
|
||||||
|
('f', 'h', 6, wallJump),
|
||||||
|
('g', 'h', 7, run),
|
||||||
|
('h', 'f', 1, jump)
|
||||||
|
);
|
||||||
|
|
||||||
|
myGraph
|
||||||
|
.BellmanFordSingleSourceShortestPath('a')
|
||||||
|
.Should()
|
||||||
|
.Contain(('b', 'a', 2)).And
|
||||||
|
.Contain(('c', 'a', 3)).And
|
||||||
|
.Contain(('d', 'b', 1)).And
|
||||||
|
.Contain(('e', 'b', 3)).And
|
||||||
|
.Contain(('f', 'd', 3)).And
|
||||||
|
.Contain(('g', 'c', 7)).And
|
||||||
|
.Contain(('h', 'd', 4)).And
|
||||||
|
.HaveCount(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void BellmanFordSingleSourceShortestPathNegativeCycle()
|
||||||
|
{
|
||||||
|
var run = new MoveTypeEdgeData { moveType = MoveType.Run };
|
||||||
|
var jump = new MoveTypeEdgeData { moveType = MoveType.Jump };
|
||||||
|
var wallJump = new MoveTypeEdgeData { moveType = MoveType.WallJump };
|
||||||
|
|
||||||
|
var myGraph = new DirectedWeightedGraph<char, MoveTypeEdgeData>();
|
||||||
|
myGraph.AddNodes('a', 'b', 'c');
|
||||||
|
myGraph.AddEdges(
|
||||||
|
('a', 'b', -2, run),
|
||||||
|
('b', 'c', -3, run),
|
||||||
|
('c', 'a', -1, jump)
|
||||||
|
);
|
||||||
|
|
||||||
|
myGraph
|
||||||
|
.Invoking(x => x.BellmanFordSingleSourceShortestPath('a').Count())
|
||||||
|
.Should().
|
||||||
|
Throw<NegativeCycleException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void BellmanFordShortestPath()
|
||||||
|
{
|
||||||
|
var run = new MoveTypeEdgeData { moveType = MoveType.Run };
|
||||||
|
var jump = new MoveTypeEdgeData { moveType = MoveType.Jump };
|
||||||
|
var wallJump = new MoveTypeEdgeData { moveType = MoveType.WallJump };
|
||||||
|
|
||||||
|
var myGraph = new DirectedWeightedGraph<char, MoveTypeEdgeData>();
|
||||||
|
myGraph.AddNodes('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h');
|
||||||
|
myGraph.AddEdges(
|
||||||
|
('a', 'b', 2, run),
|
||||||
|
('a', 'c', 3, run),
|
||||||
|
('a', 'e', 4, wallJump),
|
||||||
|
('b', 'd', 2, jump),
|
||||||
|
('b', 'e', 1, run),
|
||||||
|
('c', 'g', 4, jump),
|
||||||
|
('c', 'h', 11, run),
|
||||||
|
('d', 'c', 3, jump),
|
||||||
|
('d', 'f', 2, run),
|
||||||
|
('d', 'h', 3, wallJump),
|
||||||
|
('e', 'f', 5, run),
|
||||||
|
('f', 'd', 2, run),
|
||||||
|
('f', 'h', 6, wallJump),
|
||||||
|
('g', 'h', 7, run),
|
||||||
|
('h', 'f', 1, jump)
|
||||||
|
);
|
||||||
|
|
||||||
|
myGraph
|
||||||
|
.BellmanFordShortestPath('a', 'h')
|
||||||
|
.Select(pair => myGraph.EdgeData(pair.Item1, pair.Item2))
|
||||||
|
.Should()
|
||||||
|
.ContainInOrder(
|
||||||
|
run, jump, wallJump
|
||||||
|
)
|
||||||
|
.And
|
||||||
|
.HaveCount(3);
|
||||||
|
|
||||||
|
// have to call Count() because otherwise the lazy evaluation wont trigger
|
||||||
|
myGraph.Invoking(x => x.BellmanFordShortestPath('a', 'z').Count()).Should().Throw<System.ArgumentException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void BellmanFordNotReachable()
|
||||||
|
{
|
||||||
|
var run = new MoveTypeEdgeData { moveType = MoveType.Run };
|
||||||
|
var jump = new MoveTypeEdgeData { moveType = MoveType.Jump };
|
||||||
|
var wallJump = new MoveTypeEdgeData { moveType = MoveType.WallJump };
|
||||||
|
|
||||||
|
var myGraph = new DirectedWeightedGraph<char, MoveTypeEdgeData>();
|
||||||
|
myGraph.AddNodes('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h');
|
||||||
|
myGraph.AddEdges(
|
||||||
|
('a', 'b', 2, run),
|
||||||
|
('a', 'c', 3, run),
|
||||||
|
('a', 'e', 4, wallJump),
|
||||||
|
('b', 'd', 2, jump),
|
||||||
|
('b', 'e', 1, run),
|
||||||
|
('c', 'g', 4, jump),
|
||||||
|
('c', 'h', 11, run),
|
||||||
|
('d', 'c', 3, jump),
|
||||||
|
('d', 'h', 3, wallJump),
|
||||||
|
('f', 'd', 2, run),
|
||||||
|
('f', 'h', 6, wallJump),
|
||||||
|
('g', 'h', 7, run)
|
||||||
|
);
|
||||||
|
|
||||||
|
myGraph
|
||||||
|
.BellmanFordShortestPath('a', 'f')
|
||||||
|
.Should()
|
||||||
|
.BeEmpty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue