various undirected graph methods
parent
0b66236d78
commit
15baed3cfe
|
@ -33,6 +33,9 @@ namespace MoonTools.Core.Graph
|
|||
public IEnumerable<TNode> Nodes => nodes;
|
||||
public IEnumerable<(TNode, TNode)> Edges => edges;
|
||||
|
||||
public int Order => nodes.Count;
|
||||
public int Size => edges.Count;
|
||||
|
||||
public void AddNode(TNode node)
|
||||
{
|
||||
if (!Exists(node))
|
||||
|
@ -71,6 +74,12 @@ namespace MoonTools.Core.Graph
|
|||
}
|
||||
}
|
||||
|
||||
public int Degree(TNode node)
|
||||
{
|
||||
CheckNodes(node);
|
||||
return neighbors[node].Count;
|
||||
}
|
||||
|
||||
readonly List<(TNode, TNode)> edgesToRemove = new List<(TNode, TNode)>();
|
||||
|
||||
public void RemoveNode(TNode node)
|
||||
|
@ -96,7 +105,7 @@ namespace MoonTools.Core.Graph
|
|||
neighbors.Remove(node);
|
||||
}
|
||||
|
||||
public void AddEdge(TNode v, TNode u, TEdgeData edgeData)
|
||||
public virtual void AddEdge(TNode v, TNode u, TEdgeData edgeData)
|
||||
{
|
||||
CheckNodes(v, u);
|
||||
|
||||
|
@ -105,7 +114,7 @@ namespace MoonTools.Core.Graph
|
|||
edgesToEdgeData.Add((v, u), edgeData);
|
||||
}
|
||||
|
||||
public void AddEdges(params (TNode, TNode, TEdgeData)[] edges)
|
||||
public virtual void AddEdges(params (TNode, TNode, TEdgeData)[] edges)
|
||||
{
|
||||
foreach (var edge in edges)
|
||||
{
|
||||
|
@ -119,10 +128,11 @@ namespace MoonTools.Core.Graph
|
|||
if (!Exists(v, u)) { throw new ArgumentException($"Edge between vertex {v} and vertex {u} does not exist in the graph"); }
|
||||
}
|
||||
|
||||
public void RemoveEdge(TNode v, TNode u)
|
||||
public virtual void RemoveEdge(TNode v, TNode u)
|
||||
{
|
||||
CheckEdge(v, u);
|
||||
neighbors[v].Remove(u);
|
||||
edges.Remove((v, u));
|
||||
edgesToEdgeData.Remove((v, u));
|
||||
}
|
||||
|
||||
|
@ -139,40 +149,167 @@ namespace MoonTools.Core.Graph
|
|||
return neighbors[node];
|
||||
}
|
||||
|
||||
readonly HashSet<TNode> discovered = new HashSet<TNode>();
|
||||
readonly Dictionary<TNode, uint> dfsOutput = new Dictionary<TNode, uint>();
|
||||
readonly Stack<TNode> dfsStack = new Stack<TNode>();
|
||||
readonly HashSet<TNode> dfsDiscovered = new HashSet<TNode>();
|
||||
|
||||
public IEnumerable<(TNode, uint)> NodeDFS()
|
||||
public IEnumerable<TNode> PreorderNodeDFS()
|
||||
{
|
||||
discovered.Clear();
|
||||
dfsOutput.Clear();
|
||||
uint time = 0;
|
||||
dfsStack.Clear();
|
||||
dfsDiscovered.Clear();
|
||||
|
||||
foreach (var node in Nodes)
|
||||
{
|
||||
if (!dfsDiscovered.Contains(node))
|
||||
{
|
||||
dfsStack.Push(node);
|
||||
while (dfsStack.Count > 0)
|
||||
{
|
||||
var current = dfsStack.Pop();
|
||||
if (!dfsDiscovered.Contains(current))
|
||||
{
|
||||
dfsDiscovered.Add(current);
|
||||
yield return current;
|
||||
foreach (var neighbor in Neighbors(current))
|
||||
{
|
||||
dfsStack.Push(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// public IEnumerable<TNode> PostorderNodeDFS()
|
||||
// {
|
||||
// dfsStack.Clear();
|
||||
// dfsDiscovered.Clear();
|
||||
|
||||
// foreach (var node in Nodes)
|
||||
// {
|
||||
// if (!dfsDiscovered.Contains(node))
|
||||
// {
|
||||
// dfsStack.Push(node);
|
||||
// while (dfsStack.Count > 0)
|
||||
// {
|
||||
// var current = dfsStack.Pop();
|
||||
// if (!dfsDiscovered.Contains(current))
|
||||
// {
|
||||
// dfsDiscovered.Add(current);
|
||||
// foreach (var neighbor in Neighbors(current))
|
||||
// {
|
||||
// dfsStack.Push(neighbor);
|
||||
// }
|
||||
// yield return current;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
List<TNode> postorderOutput = new List<TNode>();
|
||||
|
||||
public IEnumerable<TNode> PostorderNodeDFS()
|
||||
{
|
||||
dfsDiscovered.Clear();
|
||||
postorderOutput.Clear();
|
||||
|
||||
void dfsHelper(TNode v) // refactor this to remove closure
|
||||
{
|
||||
discovered.Add(v);
|
||||
dfsDiscovered.Add(v);
|
||||
|
||||
foreach (var neighbor in Neighbors(v))
|
||||
{
|
||||
if (!discovered.Contains(neighbor))
|
||||
if (!dfsDiscovered.Contains(neighbor))
|
||||
{
|
||||
dfsHelper(neighbor);
|
||||
}
|
||||
}
|
||||
|
||||
time++;
|
||||
dfsOutput[v] = time;
|
||||
postorderOutput.Add(v);
|
||||
}
|
||||
|
||||
foreach (var node in Nodes)
|
||||
{
|
||||
if (!discovered.Contains(node))
|
||||
if (!dfsDiscovered.Contains(node))
|
||||
{
|
||||
dfsHelper(node);
|
||||
}
|
||||
}
|
||||
|
||||
return dfsOutput.Select(entry => (entry.Key, entry.Value));
|
||||
return postorderOutput;
|
||||
}
|
||||
|
||||
readonly Queue<TNode> bfsQueue = new Queue<TNode>();
|
||||
readonly HashSet<TNode> bfsDiscovered = new HashSet<TNode>();
|
||||
|
||||
public IEnumerable<TNode> NodeBFS()
|
||||
{
|
||||
bfsQueue.Clear();
|
||||
bfsDiscovered.Clear();
|
||||
|
||||
foreach (var node in Nodes)
|
||||
{
|
||||
if (!bfsDiscovered.Contains(node))
|
||||
{
|
||||
bfsQueue.Enqueue(node);
|
||||
while (bfsQueue.Count > 0)
|
||||
{
|
||||
var current = bfsQueue.Dequeue();
|
||||
foreach (var neighbor in Neighbors(current))
|
||||
{
|
||||
if (!bfsDiscovered.Contains(neighbor))
|
||||
{
|
||||
bfsDiscovered.Add(neighbor);
|
||||
bfsQueue.Enqueue(neighbor);
|
||||
yield return neighbor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hoo boy this is bad for the GC
|
||||
public IEnumerable<TNode> LexicographicBFS()
|
||||
{
|
||||
var sets = new List<List<TNode>>();
|
||||
sets.Add(Nodes.ToList());
|
||||
|
||||
while (sets.Count > 0)
|
||||
{
|
||||
var firstSet = sets[0];
|
||||
var node = firstSet[0];
|
||||
firstSet.RemoveAt(0);
|
||||
if (firstSet.Count == 0) { sets.RemoveAt(0); }
|
||||
|
||||
yield return node;
|
||||
|
||||
var replaced = new List<List<TNode>>();
|
||||
|
||||
foreach (var neighbor in Neighbors(node))
|
||||
{
|
||||
if (sets.Any(set => set.Contains(neighbor)))
|
||||
{
|
||||
var s = sets.Find(set => set.Contains(neighbor));
|
||||
var sIndex = sets.IndexOf(s);
|
||||
List<TNode> t;
|
||||
if (replaced.Contains(s))
|
||||
{
|
||||
t = sets[sIndex - 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
t = new List<TNode>();
|
||||
sets.Insert(sIndex, t);
|
||||
replaced.Add(s);
|
||||
}
|
||||
|
||||
s.Remove(neighbor);
|
||||
t.Add(neighbor);
|
||||
if (s.Count == 0) { sets.Remove(s); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Cyclic()
|
||||
|
@ -182,7 +319,7 @@ namespace MoonTools.Core.Graph
|
|||
|
||||
public IEnumerable<TNode> TopologicalSort()
|
||||
{
|
||||
return NodeDFS().OrderByDescending(entry => entry.Item2).Select(entry => entry.Item1);
|
||||
return PostorderNodeDFS().Reverse();
|
||||
}
|
||||
|
||||
readonly Dictionary<TNode, uint> preorder = new Dictionary<TNode, uint>();
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MoonTools.Core.Graph
|
||||
{
|
||||
public class UndirectedGraph<TNode, TEdgeData> : DirectedGraph<TNode, TEdgeData> where TNode : System.IEquatable<TNode>
|
||||
{
|
||||
enum Color { White, Gray, Black }
|
||||
|
||||
new public int Size => edges.Count / 2;
|
||||
|
||||
public bool Complete => Size == (Order * (Order - 1) / 2);
|
||||
|
||||
public bool Chordal
|
||||
{
|
||||
get
|
||||
{
|
||||
var lexicographicOrder = LexicographicBFS();
|
||||
return lexicographicOrder
|
||||
.Select((node, index) => (node, index))
|
||||
.All(pair =>
|
||||
{
|
||||
var (node, index) = pair;
|
||||
var successors = lexicographicOrder.Skip(index - 1).Take(nodes.Count - index - 1);
|
||||
return Clique(Neighbors(node).Intersect(successors).Union(Enumerable.Repeat(node, 1)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public bool Bipartite
|
||||
{
|
||||
get
|
||||
{
|
||||
var colors = new Dictionary<TNode, Color>();
|
||||
var d = new Dictionary<TNode, int>();
|
||||
var partition = new Dictionary<TNode, int>();
|
||||
|
||||
foreach (var node in Nodes)
|
||||
{
|
||||
colors[node] = Color.White;
|
||||
d[node] = int.MaxValue;
|
||||
partition[node] = 0;
|
||||
}
|
||||
|
||||
var start = Nodes.First();
|
||||
colors[start] = Color.Gray;
|
||||
partition[start] = 1;
|
||||
d[start] = 0;
|
||||
|
||||
var stack = new Stack<TNode>();
|
||||
stack.Push(start);
|
||||
|
||||
while (stack.Count > 0)
|
||||
{
|
||||
var node = stack.Pop();
|
||||
foreach (var neighbor in Neighbors(node))
|
||||
{
|
||||
if (partition[neighbor] == partition[node]) { return false; }
|
||||
if (colors[neighbor] == Color.White)
|
||||
{
|
||||
colors[neighbor] = Color.Gray;
|
||||
d[neighbor] = d[node] + 1;
|
||||
partition[neighbor] = 3 - partition[node];
|
||||
stack.Push(neighbor);
|
||||
}
|
||||
}
|
||||
stack.Pop();
|
||||
colors[node] = Color.Black;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void AddEdge(TNode v, TNode u, TEdgeData edgeData)
|
||||
{
|
||||
base.AddEdge(v, u, edgeData);
|
||||
base.AddEdge(u, v, edgeData);
|
||||
}
|
||||
|
||||
public override void AddEdges(params (TNode, TNode, TEdgeData)[] edges)
|
||||
{
|
||||
foreach (var edge in edges)
|
||||
{
|
||||
AddEdge(edge.Item1, edge.Item2, edge.Item3);
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveEdge(TNode v, TNode u)
|
||||
{
|
||||
base.RemoveEdge(v, u);
|
||||
base.RemoveEdge(u, v);
|
||||
}
|
||||
|
||||
public bool Clique(IEnumerable<TNode> nodeList)
|
||||
{
|
||||
return nodeList.All(node => nodeList.All(other => Neighbors(node).Contains(other) || node.Equals(other)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -61,6 +61,47 @@ namespace Tests
|
|||
Assert.That(myGraph.Neighbors(1), Does.Not.Contain(4));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Order()
|
||||
{
|
||||
var myGraph = new DirectedGraph<int, EdgeData>();
|
||||
myGraph.AddNodes(1, 2, 3, 4);
|
||||
|
||||
myGraph.Order.Should().Be(4);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Size()
|
||||
{
|
||||
var myGraph = new DirectedGraph<int, EdgeData>();
|
||||
myGraph.AddNodes(1, 2, 3, 4);
|
||||
myGraph.AddEdges(
|
||||
(1, 2, dummyEdgeData),
|
||||
(2, 3, dummyEdgeData),
|
||||
(2, 4, dummyEdgeData),
|
||||
(3, 4, dummyEdgeData)
|
||||
);
|
||||
|
||||
myGraph.Size.Should().Be(4);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Degree()
|
||||
{
|
||||
var myGraph = new DirectedGraph<int, EdgeData>();
|
||||
myGraph.AddNodes(1, 2, 3, 4);
|
||||
myGraph.AddEdges(
|
||||
(1, 2, dummyEdgeData),
|
||||
(2, 3, dummyEdgeData),
|
||||
(2, 4, dummyEdgeData),
|
||||
(3, 4, dummyEdgeData)
|
||||
);
|
||||
|
||||
myGraph.Degree(1).Should().Be(1);
|
||||
myGraph.Degree(2).Should().Be(2);
|
||||
myGraph.Degree(3).Should().Be(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RemoveEdge()
|
||||
{
|
||||
|
@ -106,15 +147,57 @@ namespace Tests
|
|||
myGraph.AddEdges(
|
||||
('a', 'b', dummyEdgeData),
|
||||
('a', 'c', dummyEdgeData),
|
||||
('b', 'd', dummyEdgeData)
|
||||
('b', 'd', dummyEdgeData),
|
||||
('c', 'd', dummyEdgeData)
|
||||
);
|
||||
|
||||
var result = myGraph.NodeDFS();
|
||||
var result = myGraph.PreorderNodeDFS().ToList();
|
||||
|
||||
result.Should().Contain(('d', 1));
|
||||
result.Should().Contain(('b', 2));
|
||||
result.Should().Contain(('c', 3));
|
||||
result.Should().Contain(('a', 4));
|
||||
var indexA = result.IndexOf('a');
|
||||
var indexB = result.IndexOf('b');
|
||||
var indexC = result.IndexOf('c');
|
||||
var indexD = result.IndexOf('d');
|
||||
|
||||
Assert.That(indexA < indexB && indexA < indexC);
|
||||
Assert.That(indexB < indexD || indexC < indexD);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NodeBFS()
|
||||
{
|
||||
var myGraph = new DirectedGraph<char, EdgeData>();
|
||||
myGraph.AddNodes('a', 'b', 'c', 'd', 'e');
|
||||
myGraph.AddEdges(
|
||||
('a', 'b', dummyEdgeData),
|
||||
('a', 'c', dummyEdgeData),
|
||||
('b', 'd', dummyEdgeData),
|
||||
('c', 'e', dummyEdgeData)
|
||||
);
|
||||
|
||||
var result = myGraph.NodeBFS().ToList();
|
||||
|
||||
result.IndexOf('a').Should().BeLessThan(result.IndexOf('b'));
|
||||
result.IndexOf('a').Should().BeLessThan(result.IndexOf('c'));
|
||||
result.IndexOf('b').Should().BeLessThan(result.IndexOf('d'));
|
||||
result.IndexOf('c').Should().BeLessThan(result.IndexOf('e'));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LexicographicBFS()
|
||||
{
|
||||
var graph = new DirectedGraph<char, EdgeData>();
|
||||
graph.AddNodes('a', 'b', 'c', 'd', 'e');
|
||||
graph.AddEdges(
|
||||
('a', 'b', dummyEdgeData),
|
||||
('a', 'c', dummyEdgeData),
|
||||
('b', 'c', dummyEdgeData),
|
||||
('b', 'd', dummyEdgeData),
|
||||
('b', 'e', dummyEdgeData),
|
||||
('c', 'd', dummyEdgeData),
|
||||
('d', 'e', dummyEdgeData)
|
||||
);
|
||||
|
||||
graph.LexicographicBFS().Should().ContainInOrder(new char[] { 'a', 'b', 'c', 'd', 'e' });
|
||||
}
|
||||
|
||||
[Test]
|
|
@ -0,0 +1,134 @@
|
|||
using NUnit.Framework;
|
||||
using FluentAssertions;
|
||||
|
||||
using MoonTools.Core.Graph;
|
||||
|
||||
namespace Tests
|
||||
{
|
||||
public class UndirectedGraphTests
|
||||
{
|
||||
EdgeData dummyEdgeData;
|
||||
|
||||
[Test]
|
||||
public void Size()
|
||||
{
|
||||
var graph = new UndirectedGraph<char, EdgeData>();
|
||||
graph.AddNodes('a', 'b', 'c', 'd', 'e');
|
||||
graph.AddEdges(
|
||||
('a', 'b', dummyEdgeData),
|
||||
('a', 'c', dummyEdgeData),
|
||||
('a', 'd', dummyEdgeData),
|
||||
('b', 'c', dummyEdgeData),
|
||||
('b', 'd', dummyEdgeData),
|
||||
('c', 'd', dummyEdgeData),
|
||||
('c', 'e', dummyEdgeData)
|
||||
);
|
||||
|
||||
graph.Size.Should().Be(7);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Degree()
|
||||
{
|
||||
var graph = new UndirectedGraph<char, EdgeData>();
|
||||
graph.AddNodes('a', 'b', 'c', 'd', 'e');
|
||||
graph.AddEdges(
|
||||
('a', 'b', dummyEdgeData),
|
||||
('a', 'c', dummyEdgeData),
|
||||
('a', 'd', dummyEdgeData),
|
||||
('b', 'c', dummyEdgeData),
|
||||
('b', 'd', dummyEdgeData),
|
||||
('c', 'd', dummyEdgeData),
|
||||
('c', 'e', dummyEdgeData)
|
||||
);
|
||||
|
||||
graph.Degree('a').Should().Be(3);
|
||||
graph.Degree('b').Should().Be(3);
|
||||
graph.Degree('c').Should().Be(4);
|
||||
graph.Degree('d').Should().Be(3);
|
||||
graph.Degree('e').Should().Be(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Clique()
|
||||
{
|
||||
var graph = new UndirectedGraph<char, EdgeData>();
|
||||
graph.AddNodes('a', 'b', 'c', 'd', 'e');
|
||||
graph.AddEdges(
|
||||
('a', 'b', dummyEdgeData),
|
||||
('a', 'c', dummyEdgeData),
|
||||
('a', 'd', dummyEdgeData),
|
||||
('b', 'c', dummyEdgeData),
|
||||
('b', 'd', dummyEdgeData),
|
||||
('c', 'd', dummyEdgeData),
|
||||
('c', 'e', dummyEdgeData)
|
||||
);
|
||||
|
||||
graph.Clique(new char[] { 'a', 'b', 'c', 'd' }).Should().BeTrue();
|
||||
graph.Clique(new char[] { 'a', 'b', 'c', 'd', 'e' }).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Chordal()
|
||||
{
|
||||
var graph = new UndirectedGraph<char, EdgeData>();
|
||||
graph.AddNodes('a', 'b', 'c', 'd', 'e');
|
||||
graph.AddEdges(
|
||||
('a', 'b', dummyEdgeData),
|
||||
('a', 'c', dummyEdgeData),
|
||||
('b', 'c', dummyEdgeData),
|
||||
('b', 'd', dummyEdgeData),
|
||||
('b', 'e', dummyEdgeData),
|
||||
('c', 'd', dummyEdgeData),
|
||||
('d', 'e', dummyEdgeData)
|
||||
);
|
||||
|
||||
graph.Chordal.Should().BeTrue();
|
||||
|
||||
graph.AddNode('f');
|
||||
graph.AddEdge('a', 'f', dummyEdgeData);
|
||||
|
||||
graph.Chordal.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Complete()
|
||||
{
|
||||
var graph = new UndirectedGraph<char, EdgeData>();
|
||||
graph.AddNodes('a', 'b', 'c', 'd');
|
||||
graph.AddEdges(
|
||||
('a', 'b', dummyEdgeData),
|
||||
('a', 'c', dummyEdgeData),
|
||||
('a', 'd', dummyEdgeData),
|
||||
('b', 'c', dummyEdgeData),
|
||||
('b', 'd', dummyEdgeData),
|
||||
('c', 'd', dummyEdgeData)
|
||||
);
|
||||
|
||||
graph.Complete.Should().BeTrue();
|
||||
|
||||
graph.RemoveEdge('b', 'c');
|
||||
|
||||
graph.Complete.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Bipartite()
|
||||
{
|
||||
var graph = new UndirectedGraph<char, EdgeData>();
|
||||
graph.AddNodes('a', 'b', 'c', 'd', 'e');
|
||||
graph.AddEdges(
|
||||
('a', 'b', dummyEdgeData),
|
||||
('a', 'c', dummyEdgeData),
|
||||
('b', 'd', dummyEdgeData),
|
||||
('c', 'e', dummyEdgeData)
|
||||
);
|
||||
|
||||
graph.Bipartite.Should().BeTrue();
|
||||
|
||||
graph.AddEdge('a', 'e', dummyEdgeData);
|
||||
|
||||
graph.Bipartite.Should().BeFalse();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue