Graphs
Terminology
node
A point or vertice on the graph. (Also stated as V)
edge
The connection between two nodes. (Also stated as E)
connected
When every node has a path to another node.
cycle
When you start at , follow the links, and end back at . (Can't be only 2 nodes)
acyclic
A graph that contains no cycles.
directed
When there is a direction to the connections. (This helps making asymetric connections, Ex.: but )
undirected
Not directed. (The default) (Ex.: has the same weight as
weighted
The edges have a weight associated with them.
dag
Directed, acyclic graph.
Representing Graphs
Adjacent List
A list, where each main index of the list points to a node of the graph, describing its connections to the other nodes.
// Default one as Above
type WeightedAdjList = {
to: number;
weight: number;
}[][]
// An adaptation to include `seen`
interface WeightedAdjNode {
seen?: boolean;
edges: {
to: number;
weight: number;
}[]
}
type WeightedAdjListWithSeen = WeightedAdjNode[];Traversing/Searching with BFS
BFSfunction bfs(graph: WeightedAdjListWithSeen, source: number, needle: number): boolean {
const queue: number[] = [source];
while (queue.length) {
const currNode = queue.shift();
if (currNode === needle) return true;
graph[currNode].seen = true;
for (let neighbor of graph[currNode].edges) {
if (!graph[neighbor.to]?.seen) queue.push(neighbor.to);
}
}
return false;
}Traversing/Searching with DFS
DFSEasier to preserve the path.
function dfs(graph: WeightedAdjListWithSeen, source: number, needle: number): boolean {
const stack: number[] = [source];
while (stack.length) {
const currNode = stack.pop();
if (currNode === needle) return true;
graph[currNode].seen = true;
for (let neighbor of graph[currNode].edges) {
if (!graph[neighbor.to]?.seen) stack.push(neighbor.to);
}
}
return false;
}Adjacent Matrix
A matrix, where each main index of the first array points to a node of the graph, describing in a second array the weights to the other nodes or zero if there is no connection.
Uses more memory, since it represents non-existent connections with zero.
Traversing/Searching with BFS
BFStype WeightedAdjMatrix = number[][];
function bfs(graph: WeightedAdjMatrix, source: number, needle: number): boolean {
const queue: number[] = [source];
const seen: boolean[] = new Array(graph.length).fill(false);
while (queue.length) {
const currNode = queue.shift();
if (currNode === needle) return true;
seen[currNode] = true;
for (let neighbor=0; neighbor < graph[currNode].length; neighbor++) {
if (graph[currNode][neighbor] !== 0 && !seen[neighbor]) queue.push(neighbor);
}
}
return false;
}Dijkstra's Shortest Path
An algorithm to find shortest path, meaning find the edge connections with smallest weight.
Array Solution
A way to make it run on would be to use a minHeap to hold the weights.
Getting the next lowest unseen would be , and there would be no reason for the seen array since the seen nodes would be removed from the heap.
function hasUnvisited(seen: boolean[], weights: number[]): boolean {
return seen.some((v, nodeI) => !v && weights[nodeI] < Infinity);
}
function getLowestUnvisited(seen: boolean[], weights: number[]): number {
let idx = -1;
let lowestDistance = Infinity;
for (let i = 0; i < seen.length; i++) {
if (seen[i]) continue;
if (lowestDistance > weights[i]) {
lowestDistance = weights[i];
idx = i;
}
}
return idx;
}
function dijkstra(graph: WeightedAdjList, source: number, target: number): number[] {
const seen = new Array(graph.length).fill(false);
const weights = new Array(graph.length).fill(Infinity);
const path = new Array(graph.length).fill(-1);
weights[source] = 0;
while (hasUnvisited(seen, weights)) {
const curr = getLowestUnvisited(seen, weights);
seen[curr] = true;
for (let edge of graph[curr]) {
if (seen[edge.to]) continue;
const carriedWeight = weights[curr] + edge.weight;
if (carriedWeight < weights[edge.to]) {
weights[edge.to] = carriedWeight;
path[edge.to] = curr;
}
}
}
const bestPath: number[] = [];
let bestCurr = target;
while (path[bestCurr] !== -1) {
bestPath.push(bestCurr);
bestCurr = path[bestCurr];
}
bestPath.push(source);
return bestPath.reverse();
}The way this specific algorithm works is
Will start from the
sourcenode (Ex.:Node 0), and check how much it costs to go to each of the unseen neighbors (Node 1andNode 2).After this check it will save:
In
weightshow much it costs to get toNode 1 = 1andNode 2 = 5.In
pathit will have that the best way yet to get toNode 1andNode 2is fromNode 0.
Then it will check the next
unseennode that has the lowestweightinweights. (So it will try to stay on the best path). In this example it will check nowNode 1, and its unseen neighbors.After the check:
In
weightsit will have that to get toNode 2 = 7andNode 3 = 1. But we already know that there is a better path forNode 2which is fromNode 0, so reachingNode 2fromNode 1is ignored.In
pathwe have now that reachingNode 1 & 2should be fromNode 0and reachingNode 3should be fromNode 1.
Step 2 is repeated until there are no more unseen nodes. The algorithm will prioritize paths with lowest
weightand will override them if a better one is found.At the end it just reconstruct the
pathto return it.
Heap Solution
Last updated