Graph
Data Structure · Core
Step through it: Depth-First Search pattern
start at 7
Step 1 of 6
Goal: visit every node. Depth-first search picks a direction and goes as deep as it can before turning back, like exploring a maze by always taking the next corridor.
Step 2 of 6
From the root, 7, go down the left side to 9.
Step 3 of 6
Keep going down to 5. It has no children: a dead end.
Step 4 of 6
Back up to 9 and try its other child, 2. Another dead end.
Step 5 of 6
Back up to the root and take the last branch: 11. Recursion remembers where to return to (a stack does the same). On a graph also keep a visited list so you never loop. This is depth-first search.
Step 6 of 6
Your turn.
In computer science, a graph is an abstract data type that is meant to implement the undirected graph and directed graph concepts from mathematics, specifically the field of graph theory
A graph data structure consists of a finite (and possibly mutable) set of vertices or nodes or points, together with a set of unordered pairs of these vertices for an undirected graph or a set of ordered pairs for a directed graph. These pairs are known as edges, arcs, or lines for an undirected graph and as arrows, directed edges, directed arcs, or directed lines for a directed graph. The vertices may be part of the graph structure, or may be external entities represented by integer indices or references.

Made with okso.app
Code
export default class Graph {
/**
* @param {boolean} isDirected
*/
constructor(isDirected = false) {
this.vertices = {};
this.edges = {};
this.isDirected = isDirected;
}
/**
* @param {GraphVertex} newVertex
* @returns {Graph}
*/
addVertex(newVertex) {
const key = newVertex.getKey();
if (this.vertices[key]) {
throw new Error('Vertex has already been added before');
}
this.vertices[key] = newVertex;
return this;
}
/**
* @param {string} vertexKey
* @returns GraphVertex
*/
getVertexByKey(vertexKey) {
return this.vertices[vertexKey];
}
/**
* @param {GraphVertex} vertex
* @returns {GraphVertex[]}
*/
getNeighbors(vertex) {
return vertex.getNeighbors();
}
/**
* @return {GraphVertex[]}
*/
getAllVertices() {
return Object.values(this.vertices);
}
/**
* @return {GraphEdge[]}
*/
getAllEdges() {
return Object.values(this.edges);
}
/**
* @param {GraphEdge} edge
* @returns {Graph}
*/
addEdge(edge) {
// Try to find and end start vertices.
let startVertex = this.getVertexByKey(edge.startVertex.getKey());
let endVertex = this.getVertexByKey(edge.endVertex.getKey());
// Insert start vertex if it wasn't inserted.
if (!startVertex) {
this.addVertex(edge.startVertex);
startVertex = this.getVertexByKey(edge.startVertex.getKey());
}
// Insert end vertex if it wasn't inserted.
if (!endVertex) {
this.addVertex(edge.endVertex);
endVertex = this.getVertexByKey(edge.endVertex.getKey());
}
// Check if edge has been already added.
if (this.edges[edge.getKey()]) {
throw new Error('Edge has already been added before');
} else {
this.edges[edge.getKey()] = edge;
}
// Add edge to the vertices.
if (this.isDirected) {
// If graph IS directed then add the edge only to start vertex.
startVertex.addEdge(edge);
} else {
// If graph ISN'T directed then add the edge to both vertices.
startVertex.addEdge(edge);
endVertex.addEdge(edge);
}
return this;
}
/**
* @param {GraphEdge} edge
*/
deleteEdge(edge) {
// Delete edge from the list of edges.
if (this.edges[edge.getKey()]) {
delete this.edges[edge.getKey()];
} else {
throw new Error('Edge not found in graph');
}
// Try to find and end start vertices and delete edge from them.
const startVertex = this.getVertexByKey(edge.startVertex.getKey());
const endVertex = this.getVertexByKey(edge.endVertex.getKey());
startVertex.deleteEdge(edge);
endVertex.deleteEdge(edge);
}
/**
* @param {GraphVertex} startVertex
* @param {GraphVertex} endVertex
* @return {(GraphEdge|null)}
*/
findEdge(startVertex, endVertex) {
const vertex = this.getVertexByKey(startVertex.getKey());
if (!vertex) {
return null;
}
return vertex.findEdge(endVertex);
}
/**
* @return {number}
*/
getWeight() {
return this.getAllEdges().reduce((weight, graphEdge) => {
return weight + graphEdge.weight;
}, 0);
}
/**
* Reverse all the edges in directed graph.
* @return {Graph}
*/
reverse() {
/** @param {GraphEdge} edge */
const reversedEdges = [];
this.getAllEdges().forEach((edge) => {
// Delete straight edge from graph and from vertices.
this.deleteEdge(edge);
// Reverse the edge.
edge.reverse();
// Add reversed edge to the list of reversed edges.
reversedEdges.push(edge);
});
reversedEdges.forEach((edge) => {
// Add reversed edge to the graph.
this.addEdge(edge);
});
return this;
}
/**
* @return {object}
*/
getVerticesIndices() {
const verticesIndices = {};
this.getAllVertices().forEach((vertex, index) => {
verticesIndices[vertex.getKey()] = index;
});
return verticesIndices;
}
/**
* @return {*[][]}
*/
getAdjacencyMatrix() {
const vertices = this.getAllVertices();
const verticesIndices = this.getVerticesIndices();
// Init matrix with infinities meaning that there is no ways of
// getting from one vertex to another yet.
const adjacencyMatrix = Array(vertices.length).fill(null).map(() => {
return Array(vertices.length).fill(Infinity);
});
// Fill the columns.
vertices.forEach((vertex, vertexIndex) => {
vertex.getNeighbors().forEach((neighbor) => {
const neighborIndex = verticesIndices[neighbor.getKey()];
adjacencyMatrix[vertexIndex][neighborIndex] = this.findEdge(vertex, neighbor).weight;
});
});
return adjacencyMatrix;
}
/**
* @return {string}
*/
toString() {
return Object.keys(this.vertices).toString();
}
}export default class GraphEdge {
/**
* @param {GraphVertex} startVertex
* @param {GraphVertex} endVertex
* @param {number} [weight=0]
* @param key
*/
constructor(startVertex, endVertex, weight = 0, key = null) {
this.startVertex = startVertex;
this.endVertex = endVertex;
this.weight = weight;
// A custom key (if provided) is a stable identity that is kept on reverse.
// Auto-generated keys are recomputed so they always reflect the direction.
this.customKey = key;
this.key = key;
}
getKey() {
if (this.key) {
return this.key;
}
const startVertexKey = this.startVertex.getKey();
const endVertexKey = this.endVertex.getKey();
this.key = `${startVertexKey}_${endVertexKey}`;
return this.key;
}
/**
* @return {GraphEdge}
*/
reverse() {
const tmp = this.startVertex;
this.startVertex = this.endVertex;
this.endVertex = tmp;
// Invalidate the auto-generated key so getKey() recomputes it for the new
// direction. A custom key represents a stable identity and is preserved.
if (this.customKey === null) {
this.key = null;
}
return this;
}
/**
* @return {string}
*/
toString() {
return this.getKey().toString();
}
}import LinkedList from '../linked-list/LinkedList';
export default class GraphVertex {
/**
* @param {*} value
*/
constructor(value) {
if (value === undefined) {
throw new Error('Graph vertex must have a value');
}
/**
* @param {GraphEdge} edgeA
* @param {GraphEdge} edgeB
*/
const edgeComparator = (edgeA, edgeB) => {
if (edgeA.getKey() === edgeB.getKey()) {
return 0;
}
return edgeA.getKey() < edgeB.getKey() ? -1 : 1;
};
// Normally you would store string value like vertex name.
// But generally it may be any object as well
this.value = value;
this.edges = new LinkedList(edgeComparator);
}
/**
* @param {GraphEdge} edge
* @returns {GraphVertex}
*/
addEdge(edge) {
this.edges.append(edge);
return this;
}
/**
* @param {GraphEdge} edge
*/
deleteEdge(edge) {
this.edges.delete(edge);
}
/**
* @returns {GraphVertex[]}
*/
getNeighbors() {
const edges = this.edges.toArray();
/** @param {LinkedListNode} node */
const neighborsConverter = (node) => {
return node.value.startVertex === this ? node.value.endVertex : node.value.startVertex;
};
// Return either start or end vertex.
// For undirected graphs it is possible that current vertex will be the end one.
return edges.map(neighborsConverter);
}
/**
* @return {GraphEdge[]}
*/
getEdges() {
return this.edges.toArray().map((linkedListNode) => linkedListNode.value);
}
/**
* @return {number}
*/
getDegree() {
return this.edges.toArray().length;
}
/**
* @param {GraphEdge} requiredEdge
* @returns {boolean}
*/
hasEdge(requiredEdge) {
const edgeNode = this.edges.find({
callback: (edge) => edge === requiredEdge,
});
return !!edgeNode;
}
/**
* @param {GraphVertex} vertex
* @returns {boolean}
*/
hasNeighbor(vertex) {
const vertexNode = this.edges.find({
callback: (edge) => edge.startVertex === vertex || edge.endVertex === vertex,
});
return !!vertexNode;
}
/**
* @param {GraphVertex} vertex
* @returns {(GraphEdge|null)}
*/
findEdge(vertex) {
const edgeFinder = (edge) => {
return edge.startVertex === vertex || edge.endVertex === vertex;
};
const edge = this.edges.find({ callback: edgeFinder });
return edge ? edge.value : null;
}
/**
* @returns {string}
*/
getKey() {
return this.value;
}
/**
* @return {GraphVertex}
*/
deleteAllEdges() {
this.getEdges().forEach((edge) => this.deleteEdge(edge));
return this;
}
/**
* @param {function} [callback]
* @returns {string}
*/
toString(callback) {
return callback ? callback(this.value).toString() : this.value.toString();
}
}References
Practice
Depth-First Search
- Maximum Depth of Binary TreeEasy
- Path SumEasy
- Diameter of Binary TreeEasy
- Balanced Binary TreeEasy
- Find if Path Exists in GraphEasy
- Path Sum IIMedium
- Path Sum IIIMedium
- Clone GraphMedium
- Number of ProvincesMedium
- All Paths From Source to TargetMedium
- Course ScheduleMedium
- Pacific Atlantic Water FlowMedium
- Count Good Nodes in Binary TreeMedium
- Sum Root to Leaf NumbersMedium
- Reconstruct ItineraryMedium
- Is Graph Bipartite?Medium
- Binary Tree Maximum Path SumHard
- Longest Increasing Path in a MatrixHard
Spotted a mistake or something unclear? Report it on GitHub.