For a class we needed to implement an undirected unweighted graph that:
- Maintains a distance matrix that can be printed
- Supports the computation of the graph's diameter (uses dist matrix)
- Prints the number of connected components and their included vertices
With the above being said, I opted for an adjacency matrix to represent the graph, as I already have to use a distance matrix so why not (though the change from matrix => list should be fairly trivial). I also implemented
- DFS from a given node
- BFS from a given node
- Shortest path. Returns the shortest number of edges between vertices
v1
andv2
if such a path exists and-1
otherwise.
To implement the shortest path
I used a modified BFS that would increment a variable called distance
that indicates how far the node at the front of the queue is away from the node we started at. Got the idea from a similar algorithm which solves the problem "Given a binary tree return a vector of vectors where each nested vector contains the values of each node at a particular level". Found here.
The main goal of this implementation, and this post for that matter, is to determine whether the logic in my methods are reasonable, specifically with BFS/DFS. I'd like to know if I'm missing anything or if there's some simplification I had not considered. Note the implementation does not really have error handling for invalid input, it is just to practice and experiment with!
Graph.h
Note the reason I'm using two dimensional bool
/int
arrays is because I didn't feel the overhead from std::vector was necessary, though in production it would be a good choice
class Graph {
private:
int numVertices;
bool **adjacencyMatrix;
int **distanceMatrix;
bool distanceMatrixComputed;
void initAdjacencyMatrix();
void initDistanceMatrix();
bool computeDistanceMatrix();
std::unordered_map<int, int> bfsWithDistance(int);
void dfsHelper(int, std::vector<int>&, std::unordered_set<int>&);
public:
Graph(int);
void addEdge(int, int);
int shortestPath(int, int);
int getDiameter();
std::vector<int> bfs(int);
std::vector<int> dfs(int);
void printAdjacencyMatrix();
void printDistanceMatrix();
void printComponents();
~Graph();
};
Graph.cpp
In my actual implementation I define
d MIN
and MAX
to avoid pulling in <algorithm>
but they didn't format here properly so I removed them from this post
#include "Graph.h"
Graph::Graph(int inNumVertices): numVertices(MAX(inNumVertices, 0)), distanceMatrixComputed(false) {
this->initAdjacencyMatrix();
this->initDistanceMatrix();
}
/**
* Allocate memory for adjacency matrix
*/
void Graph::initAdjacencyMatrix() {
this->adjacencyMatrix = new bool*[this->numVertices];
for (int i = 0; i < this->numVertices; ++i) {
this->adjacencyMatrix[i] = new bool[this->numVertices];
}
}
/**
* Allocate memory for distance matrix
*/
void Graph::initDistanceMatrix() {
this->distanceMatrix = new int*[this->numVertices];
for (int i = 0; i < this->numVertices; ++i) {
this->distanceMatrix[i] = new int[this->numVertices];
for (int j = 0; j < this->numVertices; ++j) {
this->distanceMatrix[i][j] = -1;
}
}
}
/**
* Since this graph implementation is undirected,
* our adjacency matrix must remain symmetrical.
*/
void Graph::addEdge(int i, int j) {
this->adjacencyMatrix[i][j] = true;
this->adjacencyMatrix[j][i] = true;
this->distanceMatrixComputed = false;
}
std::vector<int> Graph::dfs(int vertex) {
std::vector<int> returnVec;
std::unordered_set<int> visited;
dfsHelper(vertex, returnVec, visited);
return returnVec;
}
void Graph::dfsHelper(int vertex, std::vector<int> &vec, std::unordered_set<int> &visited) {
if (visited.find(vertex) != visited.end()) return;
vec.push_back(vertex);
visited.insert(vertex);
for (int j = 0; j < this->numVertices; ++j) {
if (this->adjacencyMatrix[vertex][j]) {
dfsHelper(j, vec, visited);
}
}
}
std::vector<int> Graph::bfs(int vertex) {
std::vector<int> returnVec;
std::unordered_set<int> visited;
std::queue<int> q;
q.push(vertex);
while (!q.empty()) {
if (visited.find(q.front()) != visited.end()) {
q.pop();
continue;
}
returnVec.push_back(q.front());
// Push all of q.front()'s children
for (int j = 0; j < this->numVertices; ++j) {
if (this->adjacencyMatrix[q.front()][j]) {
q.push(j);
}
}
// Visit q.front()
visited.insert(q.front());
q.pop();
}
return returnVec;
}
std::unordered_map<int, int> Graph::bfsWithDistance(int vertex) {
std::unordered_map<int, int> visited;
std::queue<int> q;
q.push(vertex);
int count, distance = 0;
while (!q.empty()) {
if (visited.find(q.front()) != visited.end()) {
q.pop();
continue;
}
count = q.size();
while (count) {
// Push all of q.front()'s children
for (int j = 0; j < this->numVertices; ++j) {
if (this->adjacencyMatrix[q.front()][j]) {
q.push(j);
}
}
// Visit q.front()
// This works nicely because insert will
// not update an already existing value
visited.insert({q.front(), distance});
q.pop();
count--;
}
distance++;
}
return visited;
}
bool Graph::computeDistanceMatrix() {
std::unordered_map<int, int> visited;
for (int i = 0; i < this->numVertices; ++i) {
visited = bfsWithDistance(i);
for (auto it : visited) {
this->distanceMatrix[i][it.first] = it.second;
}
}
this->distanceMatrixComputed = true;
return (visited.size() == this->numVertices);
}
int Graph::shortestPath(int v1, int v2) {
std::unordered_map<int, int> component = bfsWithDistance(v1);
std::unordered_map<int, int>::const_iterator it = component.find(v2);
return (it != component.end()) ? it->second : -1;
}
int Graph::getDiameter() {
bool isConnected = this->computeDistanceMatrix();
if (!isConnected) return -1;
int diameter = 0;
for (int i = 0; i < this->numVertices; ++i) {
for (int j = 0; j < this->numVertices; ++j) {
diameter = MAX(diameter, this->distanceMatrix[i][j]);
}
}
return diameter;
}
void Graph::printComponents() {
if (!this->distanceMatrixComputed) this->computeDistanceMatrix();
std::vector<std::unordered_map<int, int> > connectedComponents;
std::unordered_map<int, int> allVisited, component;
// Gather connected components
for (int i = 0; i < this->numVertices; ++i) {
// Component with root i is its own component if we've never seen it before
if (allVisited.find(i) == allVisited.end()) {
component = bfsWithDistance(i);
connectedComponents.push_back(component);
allVisited.insert(component.begin(), component.end());
}
}
// Print all connected components
std::cout << "The graph has " << connectedComponents.size() << " connected components" << '\n';
for (int i = 0; i < connectedComponents.size(); ++i) {
std::cout << "Connected component " << i + 1 << '\n';
for (auto it = connectedComponents[i].begin(); it != connectedComponents[i].end(); ++it) {
std::cout << it->first << " -> ";
}
std::cout << '\n';
}
}
void Graph::printAdjacencyMatrix() {
std::cout << "Adjacency matrix:" << '\n';
for (int i = 0; i < this->numVertices; ++i) {
for (int j = 0; j < this->numVertices; ++j) {
std::cout << this->adjacencyMatrix[i][j] << " ";
}
std::cout << '\n';
}
std::cout << '\n';
}
void Graph::printDistanceMatrix() {
std::cout << "Distance matrix:" << '\n';
for (int i = 0; i < this->numVertices; ++i) {
for (int j = 0; j < this->numVertices; ++j) {
std::cout << this->distanceMatrix[i][j] << " ";
}
std::cout << '\n';
}
std::cout << '\n';
}
Graph::~Graph() {
for (int i = 0; i < this->numVertices; ++i) {
delete[] this->adjacencyMatrix[i];
delete[] this->distanceMatrix[i];
}
delete[] this->adjacencyMatrix;
delete[] this->distanceMatrix;
}
// Print all connected components
. Please double check. \$\endgroup\$Ctrl-K
. \$\endgroup\$<
there. Better resubmit. \$\endgroup\$