various undirected graph methods

master
Evan Hemsley 2019-10-23 14:15:17 -07:00
parent 0b66236d78
commit 15baed3cfe
4 changed files with 476 additions and 22 deletions

View File

@ -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>();

100
Graph/UndirectedGraph.cs Normal file
View File

@ -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)));
}
}
}

View File

@ -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]

134
test/UndirectedGraph.cs Normal file
View File

@ -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();
}
}
}