Graphs
DotNet.AdvancedCollections provides two complete graph implementations, each optimized for different use cases.
Graph Implementations
1. Graph<TVertex, TEdge> (Adjacency List)
A graph implementation using an adjacency list representation. Best for sparse graphs (few edges relative to vertices).
Key Features:
- ? Memory efficient: O(V + E) space complexity
- ? Fast neighbor iteration: O(degree)
- ? Ideal for graph traversal algorithms (BFS, DFS)
- ? Collection initializer support
- ? Support for directed and undirected graphs
When to use:
- Your graph is sparse (E < V�/4)
- You frequently iterate over neighbors
- Memory usage is a concern
- You're implementing traversal algorithms
2. AdjacencyMatrixGraph<TVertex, TEdge>
A graph implementation using an adjacency matrix with bidirectional vertex-to-index mapping. Best for dense graphs or frequent edge lookups.
Key Features:
- ? Ultra-fast edge checking: O(1)
- ? Fast edge addition/removal: O(1)
- ? Ideal for algorithms requiring frequent edge queries
- ? Auto-resizing matrix when capacity is exceeded
When to use:
- Your graph is dense (E > V�/3)
- You frequently check if edges exist
- You know the approximate vertex count in advance
- Edge lookup performance is critical
Performance Comparison
| Operation | Adjacency List | Adjacency Matrix |
|---|---|---|
| Add Vertex | O(1) | O(1) amortized* |
| Add Edge | O(1) | O(1) |
| Has Edge | O(degree) | O(1) ? |
| Get Neighbors | O(degree) ? | O(V) |
| Remove Vertex | O(V + E) | O(V) |
| Remove Edge | O(degree) | O(1) |
| Space | O(V + E) ?? | O(V�) |
*Requires matrix resize when capacity is exceeded
Graph<TVertex, TEdge> (Adjacency List)
Creating Graphs
using DotNet.AdvancedCollections.Graph;
// Create a graph with vertex type char and edge weight type int
var graph = new Graph<char, int>();
// Add vertices
graph.AddVertex('A');
graph.AddVertex('B');
graph.AddVertex('C');
// Or use collection initializer
var graph2 = new Graph<char, int>
{
new Vertex<char, int>('A'),
new Vertex<char, int>('B'),
new Vertex<char, int>('C')
};
Vertices
var graph = new Graph<string, int>();
// Add vertices
graph.AddVertex("A");
graph.AddVertex("B");
graph.AddVertex("C");
// Check if a vertex exists
bool exists = graph.HasVertex("A"); // true
// Remove vertex
graph.RemoveVertex("C");
// Iterate over vertices
foreach (var vertex in graph)
{
Console.WriteLine(vertex.VertexName);
}
Edges
var graph = new Graph<string, int>();
graph.AddVertex("A");
graph.AddVertex("B");
graph.AddVertex("C");
// Add edges with cost/weight
graph.AddEdge("A", "B", 10);
graph.AddEdge("B", "C", 5);
graph.AddEdge("A", "C", 15);
// Check if an edge exists
bool hasEdge = graph.HasEdge("A", "B"); // true
// Remove edge
bool removed = graph.RemoveEdge("A", "B");
// Get the cost/weight of an edge
var cost = graph.GetCost("A", "C"); // 15
Adjacencies
var graph = new Graph<char, int>();
graph.AddVertex('A');
graph.AddVertex('B');
graph.AddVertex('C');
graph.AddEdge('A', 'B', 10);
graph.AddEdge('A', 'C', 5);
// Get predecessors (vertices that point to this vertex)
var predecessors = graph.Predecessors('A');
foreach (var vertex in predecessors)
{
Console.WriteLine(vertex.VertexName);
}
// Get successors (vertices that this vertex points to)
var successors = graph.Successors('A');
foreach (var vertex in successors)
{
Console.WriteLine(vertex.VertexName);
}
Vertex<TVertex, TEdge>
Represents a vertex in the graph.
using DotNet.AdvancedCollections.Graph;
var vertex = new Vertex<char, int>('A');
char name = vertex.VertexName;
Edge<TVertex, TEdge>
Represents an edge between two vertices.
using DotNet.AdvancedCollections.Graph;
var source = new Vertex<string, int>("A");
var destination = new Vertex<string, int>("B");
var edge = new Edge<string, int>(source, destination, 10);
var from = edge.From;
var to = edge.To;
int cost = edge.Cost;
Exceptions
The library provides graph-specific exceptions:
ExistentVertexException: Thrown when attempting to add a vertex that already existsNonExistentVertexException: Thrown when attempting to operate on a non-existent vertex
var graph = new Graph<string, int>();
try
{
graph.AddVertex("A");
graph.AddVertex("A"); // Throws ExistentVertexException
}
catch (ExistentVertexException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
try
{
// Throws NonExistentVertexException if "Z" doesn't exist
graph.AddEdge("A", "Z", 10);
}
catch (NonExistentVertexException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
Practical Example
using DotNet.AdvancedCollections.Graph;
// Create a social network graph
var socialNetwork = new Graph<string, int>
{
new Vertex<string, int>("Alice"),
new Vertex<string, int>("Bob"),
new Vertex<string, int>("Charlie"),
new Vertex<string, int>("Diana")
};
// Add connections (friendships) with closeness score
socialNetwork.AddEdge("Alice", "Bob", 10);
socialNetwork.AddEdge("Alice", "Charlie", 8);
socialNetwork.AddEdge("Bob", "Diana", 5);
socialNetwork.AddEdge("Charlie", "Diana", 7);
// Find Alice's friends (successors)
var aliceFriends = socialNetwork.Successors("Alice");
Console.WriteLine($"Alice has {aliceFriends.Count()} friends");
foreach (var friend in aliceFriends)
{
Console.WriteLine(friend.VertexName);
}
// Check if two people are friends
bool areFriends = socialNetwork.HasEdge("Bob", "Charlie");
Console.WriteLine($"Bob and Charlie are friends: {areFriends}");
AdjacencyMatrixGraph<TVertex, TEdge>
The adjacency matrix implementation uses a 2D matrix for edge storage with bidirectional vertex-to-index mapping.
Creating an Adjacency Matrix Graph
using DotNet.AdvancedCollections.Graph;
// Create with default capacity (10)
var graph = new AdjacencyMatrixGraph<char, int>();
// Create with specific initial capacity
var largeGraph = new AdjacencyMatrixGraph<string, double>(initialCapacity: 100);
Adding Vertices
var graph = new AdjacencyMatrixGraph<string, int>();
// Add vertices
graph.AddVertex("A");
graph.AddVertex("B");
graph.AddVertex("C");
// Check vertex count
int count = graph.VertexCount(); // 3
// Check if vertex exists
bool exists = graph.HasVertex("A"); // true
Adding and Checking Edges
var graph = new AdjacencyMatrixGraph<string, int>(initialCapacity: 10);
graph.AddVertex("A");
graph.AddVertex("B");
graph.AddVertex("C");
// Add edges with costs/weights
graph.AddEdge("A", "B", 10);
graph.AddEdge("B", "C", 5);
graph.AddEdge("A", "C", 15);
// Check edge existence - O(1) operation!
if (graph.HasEdge("A", "B"))
{
Console.WriteLine("A is connected to B");
}
// Get edge count
int edgeCount = graph.EdgeCount(); // 3
Getting Neighbors and Edges
var graph = new AdjacencyMatrixGraph<char, int>();
graph.AddVertex('A');
graph.AddVertex('B');
graph.AddVertex('C');
graph.AddEdge('A', 'B', 10);
graph.AddEdge('A', 'C', 5);
// Get all neighbors
foreach (var neighbor in graph.GetNeighbors('A'))
{
Console.WriteLine($"Neighbor: {neighbor}"); // B, C
}
// Get all edges from a vertex
foreach (var edge in graph.GetEdges('A'))
{
Console.WriteLine($"Edge: {edge.Predecessor} -> {edge.Sucessor}, Cost: {edge.Cost}");
}
// Get vertex degree (number of outgoing edges)
int degree = graph.Degree('A'); // 2
Removing Vertices and Edges
var graph = new AdjacencyMatrixGraph<string, int>();
graph.AddVertex("A");
graph.AddVertex("B");
graph.AddVertex("C");
graph.AddEdge("A", "B", 10);
graph.AddEdge("B", "C", 5);
// Remove edge - O(1) operation!
bool removed = graph.RemoveEdge("A", "B"); // true
// Remove vertex (also removes all connected edges)
bool vertexRemoved = graph.RemoveVertex("C"); // true
Getting All Vertices
var graph = new AdjacencyMatrixGraph<int, int>();
graph.AddVertex(1);
graph.AddVertex(2);
graph.AddVertex(3);
// Iterate over all vertices
foreach (var vertex in graph.GetVertices())
{
Console.WriteLine($"Vertex: {vertex}");
}
Practical Example: Route Network
using DotNet.AdvancedCollections.Graph;
// Create a city route network
var cityNetwork = new AdjacencyMatrixGraph<string, int>(initialCapacity: 50);
// Add cities
cityNetwork.AddVertex("New York");
cityNetwork.AddVertex("Los Angeles");
cityNetwork.AddVertex("Chicago");
cityNetwork.AddVertex("Houston");
cityNetwork.AddVertex("Phoenix");
// Add routes with distances (km)
cityNetwork.AddEdge("New York", "Chicago", 1145);
cityNetwork.AddEdge("New York", "Houston", 2294);
cityNetwork.AddEdge("Los Angeles", "Phoenix", 600);
cityNetwork.AddEdge("Chicago", "Houston", 1523);
cityNetwork.AddEdge("Houston", "Phoenix", 1645);
// Check if there's a direct route - O(1)!
if (cityNetwork.HasEdge("New York", "Chicago"))
{
Console.WriteLine("There is a direct route from New York to Chicago");
}
// Find all cities with direct routes from Houston
Console.WriteLine("Cities reachable from Houston:");
foreach (var city in cityNetwork.GetNeighbors("Houston"))
{
Console.WriteLine($" - {city}");
}
// Get degree (number of direct routes)
int routes = cityNetwork.Degree("Houston");
Console.WriteLine($"Houston has {routes} direct routes");
Choosing the Right Implementation
Use Graph (Adjacency List) when:
- ? Graph is sparse (few edges: E < V�/4)
- ? You iterate neighbors frequently
- ? Memory is limited
- ? Implementing traversal algorithms (BFS, DFS)
- ? Vertex count changes frequently
Example use cases:
- Social networks (few friends relative to total users)
- Web page links (few links per page)
- Dependency graphs
- File system structures
Use AdjacencyMatrixGraph when:
- ? Graph is dense (many edges: E > V�/3)
- ? You check edge existence very frequently
- ? You know the vertex count in advance
- ? Edge lookup speed is critical
- ? Implementing matrix-based algorithms
Example use cases:
- Complete or near-complete graphs
- Small dense networks (e.g., tournament brackets)
- Flight route networks (dense connections)
- Algorithms like Floyd-Warshall
Quick Decision Formula
double density = (double)edgeCount / (vertexCount * vertexCount);
if (density > 0.3)
{
// Dense graph -> Use AdjacencyMatrixGraph
var graph = new AdjacencyMatrixGraph<TVertex, TEdge>(vertexCount);
}
else
{
// Sparse graph -> Use Graph
var graph = new Graph<TVertex, TEdge>();
}
Common Types
Vertex<TVertex, TEdge>
Represents a vertex in the adjacency list graph.
var vertex = new Vertex<char, int>('A');
char name = vertex.VertexName;
Edge<TVertex, TEdge>
Represents an edge between two vertices (used by both implementations).
// The Edge constructor takes: predecessor, successor, cost
var edge = new Edge<string, int>("A", "B", 10);
string from = edge.Predecessor; // "A"
string to = edge.Sucessor; // "B"
int cost = edge.Cost; // 10
Exceptions
Both graph implementations use the same exception types:
ExistentVertexException: Thrown when attempting to add a duplicate vertexNonExistentVertexException: Thrown when operating on a non-existent vertex
var graph = new AdjacencyMatrixGraph<string, int>();
try
{
graph.AddVertex("A");
graph.AddVertex("A"); // Would throw in adjacency list, returns silently in matrix
}
catch (ExistentVertexException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
try
{
// Throws if vertex doesn't exist
graph.AddEdge("A", "NonExistent", 10);
}
catch (ArgumentException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
Best Practices
Pre-allocate Capacity (Matrix)
// Good: Pre-allocate if you know the size
var graph = new AdjacencyMatrixGraph<int, int>(initialCapacity: 1000);
// Add all vertices first
for (int i = 0; i < 1000; i++)
{
graph.AddVertex(i);
}
// Then add edges
// This avoids matrix resizing
Batch Operations
// Good: Add vertices first, then edges
var graph = new AdjacencyMatrixGraph<string, int>(100);
// Add all vertices
foreach (var city in cities)
{
graph.AddVertex(city);
}
// Then add all edges
foreach (var route in routes)
{
graph.AddEdge(route.From, route.To, route.Distance);
}
Cache Degree if Used Frequently
// If you need degree frequently, cache it
var degreeCache = new Dictionary<TVertex, int>();
foreach (var vertex in graph.GetVertices())
{
degreeCache[vertex] = graph.Degree(vertex);
}