directed weighted multigraph SSSP

master
Evan Hemsley 2019-10-23 22:33:19 -07:00
parent 1740679052
commit 9e8bc14a40
4 changed files with 405 additions and 7 deletions

View File

@ -34,7 +34,13 @@ namespace MoonTools.Core.Graph
public IEnumerable<int> Weights(TNode v, TNode u)
{
CheckNodes(v, u);
return edges[(v, u)].Select(id => weights[id]);
return edges[(v, u)].Select(id => Weight(id));
}
public int Weight(Guid id)
{
if (!IDToEdge.ContainsKey(id)) { throw new ArgumentException($"Edge with id {id} does not exist in the graph"); }
return weights[id];
}
private IEnumerable<Guid> ReconstructPath(PooledDictionary<TNode, Guid> cameFrom, TNode currentNode)
@ -115,5 +121,149 @@ namespace MoonTools.Core.Graph
yield break;
}
private IEnumerable<Guid> ShortestPath(TNode start, TNode end, Func<TNode, IEnumerable<(TNode, Guid, int)>> SSSPAlgorithm)
{
CheckNodes(start, end);
var cameFrom = new PooledDictionary<TNode, Guid>(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, Guid, 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);
var distance = new PooledDictionary<TNode, int>(ClearMode.Always);
var previousEdgeIDs = new PooledDictionary<TNode, Guid>(ClearMode.Always);
foreach (var node in Nodes)
{
distance[node] = int.MaxValue;
}
distance[source] = 0;
var q = Nodes.ToPooledList();
while (q.Count > 0)
{
var node = q.MinBy(n => distance[n]).First();
q.Remove(node);
if (distance[node] == int.MaxValue) { break; }
foreach (var neighbor in Neighbors(node))
{
foreach (var edgeID in EdgeIDs(node, neighbor))
{
var weight = Weight(edgeID);
var alt = distance[node] + weight;
if (alt < distance[neighbor])
{
distance[neighbor] = alt;
previousEdgeIDs[neighbor] = edgeID;
}
}
}
}
foreach (var node in Nodes)
{
if (previousEdgeIDs.ContainsKey(node) && distance.ContainsKey(node))
{
yield return (node, previousEdgeIDs[node], distance[node]);
}
}
distance.Dispose();
previousEdgeIDs.Dispose();
}
public IEnumerable<Guid> DijkstraShortestPath(TNode start, TNode end)
{
return ShortestPath(start, end, DijkstraSingleSourceShortestPath);
}
public IEnumerable<(TNode, Guid, int)> BellmanFordSingleSourceShortestPath(TNode source)
{
CheckNodes(source);
var distance = new PooledDictionary<TNode, int>(ClearMode.Always);
var previous = new PooledDictionary<TNode, Guid>(ClearMode.Always);
foreach (var node in Nodes)
{
distance[node] = int.MaxValue;
}
distance[source] = 0;
for (int i = 0; i < Order; i++)
{
foreach (var edgeID in IDToEdge.Keys)
{
var weight = Weight(edgeID);
var (v, u) = IDToEdge[edgeID];
if (distance[v] + weight < distance[u])
{
distance[u] = distance[v] + weight;
previous[u] = edgeID;
}
}
}
foreach (var edgeID in IDToEdge.Keys)
{
var (v, u) = IDToEdge[edgeID];
foreach (var weight in Weights(v, u))
{
if (distance[v] + weight < 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<Guid> BellmanFordShortestPath(TNode start, TNode end)
{
return ShortestPath(start, end, BellmanFordSingleSourceShortestPath);
}
}
}

View File

@ -47,13 +47,25 @@ namespace MoonTools.Core.Graph
return edges.ContainsKey((v, u));
}
private void CheckID(Guid id)
{
if (!Exists(id)) { throw new ArgumentException($"Edge {id} does not exist in the graph."); }
}
public bool Exists(Guid id)
{
return IDToEdge.ContainsKey(id);
}
public (TNode, TNode) EdgeNodes(Guid id)
{
CheckID(id);
return IDToEdge[id];
}
public TEdgeData EdgeData(Guid id)
{
if (!edgeToEdgeData.ContainsKey(id))
{
throw new ArgumentException($"Edge {id} does not exist in the graph.");
}
CheckID(id);
return edgeToEdgeData[id];
}
}

View File

@ -4,7 +4,7 @@ A GC-friendly graph theory library for C# intended for use with games.
## Usage
`Graph` implements the following graph structures:
`Graph` implements various algorithms on the following graph structures:
* Directed
* Directed Weighted

View File

@ -252,5 +252,241 @@ namespace Tests
// have to call Count() because otherwise the lazy evaluation wont trigger
myGraph.Invoking(x => x.AStarShortestPath('a', 'z', (x, y) => 1).Count()).Should().Throw<System.ArgumentException>();
}
[Test]
public void DijsktraSingleSourceShortestPath()
{
var run = new MoveTypeEdgeData { moveType = MoveType.Run };
var jump = new MoveTypeEdgeData { moveType = MoveType.Jump };
var wallJump = new MoveTypeEdgeData { moveType = MoveType.WallJump };
var myGraph = new DirectedWeightedMultiGraph<char, MoveTypeEdgeData>();
myGraph.AddNodes('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h');
var edgeA = myGraph.AddEdge('a', 'b', 2, run);
var edgeB = myGraph.AddEdge('a', 'c', 1, jump);
var edgeC = myGraph.AddEdge('b', 'd', 2, jump);
var edgeD = myGraph.AddEdge('b', 'e', 1, run);
var edgeE = myGraph.AddEdge('d', 'f', 2, run);
var edgeF = myGraph.AddEdge('c', 'g', 2, run);
var edgeG = myGraph.AddEdge('d', 'h', 3, wallJump);
myGraph.AddEdges(
('a', 'c', 3, run),
('a', 'e', 4, wallJump),
('b', 'd', 5, run),
('c', 'g', 4, jump),
('c', 'h', 11, run),
('d', 'c', 3, jump),
('e', 'f', 5, run),
('f', 'd', 2, run),
('f', 'h', 6, wallJump),
('g', 'h', 7, run),
('h', 'f', 1, jump),
('a', 'a', 3, jump) // cheeky lil self-edge
);
myGraph
.DijkstraSingleSourceShortestPath('a')
.Should()
.Contain(('b', edgeA, 2)).And
.Contain(('c', edgeB, 1)).And
.Contain(('d', edgeC, 4)).And
.Contain(('e', edgeD, 3)).And
.Contain(('f', edgeE, 6)).And
.Contain(('g', edgeF, 3)).And
.Contain(('h', edgeG, 7)).And
.HaveCount(7);
// have to call Count() because otherwise the lazy evaluation wont trigger
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 DirectedWeightedMultiGraph<char, MoveTypeEdgeData>();
myGraph.AddNodes('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h');
var edgeA = myGraph.AddEdge('a', 'b', 2, run);
var edgeB = myGraph.AddEdge('a', 'c', 1, jump);
var edgeC = myGraph.AddEdge('b', 'd', 2, jump);
var edgeD = myGraph.AddEdge('b', 'e', 1, run);
var edgeE = myGraph.AddEdge('d', 'f', 2, run);
var edgeF = myGraph.AddEdge('c', 'g', 2, run);
var edgeG = myGraph.AddEdge('d', 'h', 3, wallJump);
myGraph.AddEdges(
('a', 'c', 3, run),
('a', 'e', 4, wallJump),
('b', 'd', 5, run),
('c', 'g', 4, jump),
('c', 'h', 11, run),
('d', 'c', 3, jump),
('e', 'f', 5, run),
('f', 'd', 2, run),
('f', 'h', 6, wallJump),
('g', 'h', 7, run),
('h', 'f', 1, jump),
('a', 'a', 3, jump) // cheeky lil self-edge
);
myGraph
.DijkstraShortestPath('a', 'h')
.Select(edgeID => myGraph.EdgeData(edgeID))
.Should()
.ContainInOrder(
run, jump, wallJump
)
.And
.HaveCount(3);
myGraph.Invoking(x => x.DijkstraShortestPath('a', 'z').Count()).Should().Throw<System.ArgumentException>();
}
[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 DirectedWeightedMultiGraph<char, MoveTypeEdgeData>();
myGraph.AddNodes('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h');
var edgeA = myGraph.AddEdge('a', 'b', 2, run);
var edgeB = myGraph.AddEdge('a', 'c', 1, jump);
var edgeC = myGraph.AddEdge('b', 'd', 2, jump);
var edgeD = myGraph.AddEdge('b', 'e', 1, run);
var edgeE = myGraph.AddEdge('d', 'f', 2, run);
var edgeF = myGraph.AddEdge('c', 'g', 2, run);
var edgeG = myGraph.AddEdge('d', 'h', 3, wallJump);
myGraph.AddEdges(
('a', 'c', 3, run),
('a', 'e', 4, wallJump),
('b', 'd', 5, run),
('c', 'g', 4, jump),
('c', 'h', 11, run),
('d', 'c', 3, jump),
('e', 'f', 5, run),
('f', 'd', 2, run),
('f', 'h', 6, wallJump),
('g', 'h', 7, run),
('h', 'f', 1, jump),
('a', 'a', 3, jump) // cheeky lil self-edge
);
myGraph
.BellmanFordSingleSourceShortestPath('a')
.Should()
.Contain(('b', edgeA, 2)).And
.Contain(('c', edgeB, 1)).And
.Contain(('d', edgeC, 4)).And
.Contain(('e', edgeD, 3)).And
.Contain(('f', edgeE, 6)).And
.Contain(('g', edgeF, 3)).And
.Contain(('h', edgeG, 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 BellmanFordSingleSourceShortestPathNegative()
{
var run = new MoveTypeEdgeData { moveType = MoveType.Run };
var jump = new MoveTypeEdgeData { moveType = MoveType.Jump };
var wallJump = new MoveTypeEdgeData { moveType = MoveType.WallJump };
var myGraph = new DirectedWeightedMultiGraph<char, MoveTypeEdgeData>();
myGraph.AddNodes('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h');
var edgeA = myGraph.AddEdge('a', 'b', 2, run);
var edgeB = myGraph.AddEdge('a', 'c', 1, jump);
var edgeC = myGraph.AddEdge('b', 'd', -1, jump);
var edgeD = myGraph.AddEdge('b', 'e', 1, run);
var edgeE = myGraph.AddEdge('d', 'f', 2, run);
var edgeF = myGraph.AddEdge('c', 'g', 2, run);
var edgeG = myGraph.AddEdge('d', 'h', 3, wallJump);
myGraph.AddEdges(
('a', 'c', 3, run),
('a', 'e', 4, wallJump),
('b', 'd', 5, run),
('c', 'g', 4, jump),
('c', 'h', 11, run),
('d', 'c', 3, jump),
('e', 'f', 5, run),
('f', 'd', 2, run),
('f', 'h', 6, wallJump),
('g', 'h', 7, run),
('h', 'f', 1, jump),
('a', 'a', 3, jump) // cheeky lil self-edge
);
myGraph
.BellmanFordSingleSourceShortestPath('a')
.Should()
.Contain(('b', edgeA, 2)).And
.Contain(('c', edgeB, 1)).And
.Contain(('d', edgeC, 1)).And
.Contain(('e', edgeD, 3)).And
.Contain(('f', edgeE, 3)).And
.Contain(('g', edgeF, 3)).And
.Contain(('h', edgeG, 4)).And
.HaveCount(7);
}
[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 DirectedWeightedMultiGraph<char, MoveTypeEdgeData>();
myGraph.AddNodes('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h');
var edgeA = myGraph.AddEdge('a', 'b', 2, run);
var edgeB = myGraph.AddEdge('a', 'c', 1, jump);
var edgeC = myGraph.AddEdge('b', 'd', 2, jump);
var edgeD = myGraph.AddEdge('b', 'e', 1, run);
var edgeE = myGraph.AddEdge('d', 'f', 2, run);
var edgeF = myGraph.AddEdge('c', 'g', 2, run);
var edgeG = myGraph.AddEdge('d', 'h', 3, wallJump);
myGraph.AddEdges(
('a', 'c', 3, run),
('a', 'e', 4, wallJump),
('b', 'd', 5, run),
('c', 'g', 4, jump),
('c', 'h', 11, run),
('d', 'c', 3, jump),
('e', 'f', 5, run),
('f', 'd', 2, run),
('f', 'h', 6, wallJump),
('g', 'h', 7, run),
('h', 'f', 1, jump),
('a', 'a', 3, jump) // cheeky lil self-edge
);
myGraph
.BellmanFordShortestPath('a', 'h')
.Select(edgeID => myGraph.EdgeData(edgeID))
.Should()
.ContainInOrder(
run, jump, wallJump
)
.And
.HaveCount(3);
myGraph.Invoking(x => x.BellmanFordShortestPath('a', 'z').Count()).Should().Throw<System.ArgumentException>();
}
}
}