Introduction: Charting Your Own Course
Welcome to Chapter 23! So far, we’ve explored the fascinating world of graphs, understanding how they represent interconnected data. We’ve seen nodes, edges, and different ways to traverse them. Now, it’s time to put that knowledge into action with a super practical and engaging project: building your very own Route Finder!
Imagine you’re developing a simple navigation app. How does it figure out the best way to get from point A to point B? The answer, often, lies in graph theory and powerful algorithms like Dijkstra’s. In this chapter, we’ll model a small network of “cities” and “roads” as a graph, then implement a classic algorithm to discover the shortest path between any two points. This isn’t just a theoretical exercise; it’s the fundamental concept behind GPS systems, network routing protocols, and even social media friend recommendations.
By the end of this project, you’ll have a solid understanding of how to:
- Represent real-world scenarios (like maps) using graph data structures.
- Implement Dijkstra’s algorithm to find the shortest path in a weighted graph.
- Utilize a Priority Queue to optimize pathfinding.
- See the immediate, tangible results of your DSA knowledge in a practical application.
This chapter assumes you’re comfortable with basic TypeScript syntax, have Node.js set up (we’ll quickly recap), and have a foundational understanding of what graphs are and how they can be represented (e.g., using an adjacency list). If you need a refresher on graphs or priority queues, feel free to revisit previous chapters! Let’s get started on our journey to becoming route-finding masters!
Core Concepts: Navigating with Dijkstra’s Algorithm
Before we dive into coding, let’s solidify the core concepts that will power our route finder.
The Challenge of Route Finding
Think about navigating a city. You have different locations (intersections, landmarks) and ways to get between them (roads). Each road might have a different “cost” – perhaps it’s longer, has more traffic, or is a toll road. Our goal is to find the cheapest or shortest way from one location to another.
Graphs for Maps
This is where graphs shine! We can easily map our city scenario to a graph:
- Nodes (Vertices): Represent locations, intersections, or cities.
- Edges: Represent the roads connecting these locations.
- Weights: The “cost” associated with traversing an edge, like distance, time, or traffic.
This creates a weighted graph, where each edge has a numerical value indicating the “cost” of using that connection.
Introducing Dijkstra’s Algorithm
To find the shortest path in such a graph, we turn to Dijkstra’s Algorithm.
- What it does: Dijkstra’s algorithm finds the shortest paths from a single source node to all other nodes in a weighted graph with non-negative edge weights.
- Why it’s important: It’s the backbone of many real-world applications:
- GPS Navigation: Finding the fastest route between two points.
- Network Routing: Determining the most efficient path for data packets across the internet.
- Logistics & Delivery: Optimizing delivery routes for packages.
- How it works (high-level):
- It starts at a specified
startNode. - It maintains a record of the shortest distance found so far to every other node, initially setting all to “infinity” except the
startNode(which is 0). - It also keeps track of the
previousNodein the shortest path to reconstruct the final route. - It repeatedly selects the unvisited node with the smallest known distance from the
startNode. - For this selected node, it examines all its neighbors. If a shorter path to a neighbor is found by going through the current node, it updates that neighbor’s distance and
previousNode. - This process continues until all reachable nodes have been visited or the
endNodeis reached.
- It starts at a specified
Dijkstra’s is a greedy algorithm because at each step, it makes the locally optimal choice (picking the unvisited node with the smallest distance) in the hope that this will lead to a globally optimal solution (the shortest path). This works because edge weights are non-negative.
The Role of a Priority Queue
A crucial component for an efficient Dijkstra’s implementation is a Priority Queue.
- Why we need it: When Dijkstra’s algorithm needs to pick the “unvisited node with the smallest known distance,” a regular array or list would require iterating through all unvisited nodes, which can be slow. A Priority Queue allows us to extract the element with the highest (or lowest, in our case) priority (the smallest distance) very quickly, typically in O(log N) time.
- How it helps: It stores pairs of
[distance, node], always ensuring thatdequeue()returns the node that is closest to ourstartNodeamong all currently considered nodes.
Visualizing Our City Network
Let’s imagine a small network of cities we want to model.
In this diagram:
City A,City B, etc., are our nodes.- The arrows represent roads (edges). For simplicity, we’ll assume roads are bidirectional unless specified.
- The numbers on the arrows are the “weights” or distances between cities.
Our goal will be to find the shortest path, for example, from City A to City F.
Step-by-Step Implementation: Building Our Route Finder
Let’s get our hands dirty and start coding! We’ll build this project incrementally.
Project Setup (Quick Review)
If you haven’t already, let’s create a new project directory and set up TypeScript.
Create Project Folder: Open your terminal or command prompt and run:
mkdir route-finder cd route-finderInitialize Node.js Project:
npm init -yThis creates a
package.jsonfile.Install TypeScript and Node.js Type Definitions:
npm install --save-dev typescript@^5.3.3 @types/node@^20.11.17- We’re installing
typescript(version 5.3.3 as of our knowledge cut-off) as a development dependency. @types/nodeprovides type definitions for Node.js APIs, allowing TypeScript to understand Node.js modules. This is crucial for working with Node.js in a TypeScript project.
- We’re installing
Initialize TypeScript Configuration:
npx tsc --initThis command generates a
tsconfig.jsonfile, which configures the TypeScript compiler.Adjust
tsconfig.json: Open thetsconfig.jsonfile and make a few adjustments for our project structure. Find and uncomment/modify these lines:// tsconfig.json { "compilerOptions": { // ... (other options) "target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ "module": "commonjs", /* Specify what module code is generated. */ "outDir": "./dist", /* Specify an output folder for all emitted files. */ "rootDir": "./src", /* Specify the root folder within your source files. */ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ "strict": true, /* Enable all strict type-checking options. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, "include": ["src/**/*.ts"], // Ensure only files in src are compiled "exclude": ["node_modules"] // Exclude node_modules from compilation }target: "es2022": Tells TypeScript to compile our code to JavaScript that supports modern features.module: "commonjs": Specifies the module system for Node.js.outDir: "./dist": Our compiled JavaScript files will go into adistfolder.rootDir: "./src": Our TypeScript source files will be in asrcfolder.esModuleInterop: Important for working with CommonJS modules in Node.js.strict: Enables a wide range of type-checking validation rules. Always a good practice!
Create
srcDirectory:mkdir srcThis is where all our TypeScript code will live.
1. Defining Our Graph
First, let’s create a Graph class that can represent our city network. We’ll use an adjacency list to store connections and their weights.
Create a file src/graph.ts:
// src/graph.ts
/**
* Represents a weighted graph using an adjacency list.
* The adjacency list maps a node to another Map, where the inner Map
* stores its neighbors and the weight of the edge to that neighbor.
* Example: Map<"A", Map<"B", 4>> means an edge from A to B with weight 4.
*/
class Graph {
// adjacencyList: Map<NodeName, Map<NeighborNodeName, Weight>>
private adjacencyList: Map<string, Map<string, number>>;
constructor() {
// Initialize an empty map to store our graph's connections.
this.adjacencyList = new Map();
}
/**
* Adds a new node (vertex) to the graph.
* @param node The name of the node to add.
*/
addNode(node: string): void {
// If the node doesn't already exist, add it to the adjacency list
// with an empty map for its neighbors.
if (!this.adjacencyList.has(node)) {
this.adjacencyList.set(node, new Map());
}
}
/**
* Adds a weighted, undirected edge between two nodes.
* For a route finder, roads are typically two-way.
* @param node1 The first node.
* @param node2 The second node.
* @param weight The weight (e.g., distance) of the edge.
*/
addEdge(node1: string, node2: string, weight: number): void {
// Ensure both nodes exist in the graph. If not, add them.
this.addNode(node1);
this.addNode(node2);
// Add the edge from node1 to node2 with the given weight.
// We retrieve the map of neighbors for node1 and set node2 as a neighbor.
this.adjacencyList.get(node1)!.set(node2, weight);
// Since it's an undirected graph (two-way road),
// also add the edge from node2 to node1 with the same weight.
this.adjacencyList.get(node2)!.set(node1, weight);
}
/**
* Retrieves the neighbors and their weights for a given node.
* @param node The node to get neighbors for.
* @returns A Map of neighbor nodes to their edge weights, or undefined if the node doesn't exist.
*/
getNeighbors(node: string): Map<string, number> | undefined {
return this.adjacencyList.get(node);
}
/**
* Gets all nodes currently in the graph.
* @returns An iterable of all node names.
*/
getNodes(): IterableIterator<string> {
return this.adjacencyList.keys();
}
}
export { Graph };
Explanation:
- We use a
Map<string, Map<string, number>>for ouradjacencyList. The outerMapkeys are our nodes (e.g., “City A”). The value for each node is anotherMap. This innerMapstores its neighbors (e.g., “City B”) and thenumberrepresenting the edge weight (e.g., 4). This is an efficient way to represent weighted connections. - The
addNodemethod ensures that every city we talk about exists in our graph structure. addEdgeis designed for undirected graphs (two-way roads). When we add a road fromnode1tonode2with aweight, we automatically add the reverse road fromnode2tonode1with the sameweight. This makes our route finding symmetrical.getNeighborsandgetNodesare helper methods that will be useful when implementing Dijkstra’s.
2. Implementing a Simple Priority Queue
As discussed, a Priority Queue is essential for Dijkstra’s efficiency. For this project, we’ll implement a basic min-priority queue that stores [distance, node] pairs. While a full binary heap is the most performant, a simpler array-based implementation that ensures correct priority will suffice for our learning purposes and smaller graphs. We’ll manually keep it sorted.
Create a file src/priorityQueue.ts:
// src/priorityQueue.ts
/**
* A basic Min-Priority Queue implementation to store [priority, value] pairs.
* It's optimized for small number of elements, inserting new elements in sorted order.
* For very large graphs, a binary heap would be more efficient (O(log N) operations).
*/
class MinPriorityQueue<T> {
// Each item in the queue is a tuple: [priority, value]
private values: Array<[number, T]>;
constructor() {
this.values = [];
}
/**
* Adds an item to the queue with a given priority.
* It inserts the item while maintaining sorted order (lowest priority first).
* @param value The value to add (e.g., a node name).
* @param priority The priority of the value (e.g., distance).
*/
enqueue(value: T, priority: number): void {
this.values.push([priority, value]);
// A simple way to maintain priority: sort the array after each enqueue.
// For larger queues, a heap-based implementation is much faster.
this.values.sort((a, b) => a[0] - b[0]);
}
/**
* Removes and returns the item with the lowest priority (smallest number).
* @returns The value with the lowest priority, or undefined if the queue is empty.
*/
dequeue(): T | undefined {
// Remove the first element, which has the lowest priority due to sorting.
const item = this.values.shift();
return item ? item[1] : undefined;
}
/**
* Checks if the priority queue is empty.
* @returns True if the queue is empty, false otherwise.
*/
isEmpty(): boolean {
return this.values.length === 0;
}
/**
* Returns the number of items in the queue.
* @returns The size of the queue.
*/
size(): number {
return this.values.length;
}
}
export { MinPriorityQueue };
Explanation:
- Our
MinPriorityQueuestores elements as[priority, value]tuples. For Dijkstra’s,prioritywill be the distance andvaluewill be the node name. - The
enqueuemethod adds a new item and then sorts the entirevaluesarray to ensure the element with the smallest priority is always at the front. Important Note: While simple,Array.prototype.sort()makesenqueuean O(N log N) operation. A proper binary heap would make bothenqueueanddequeueO(log N), which is much more efficient for larger graphs. We’re using this simplified version to keep the focus on Dijkstra’s logic itself, but be aware of the performance implications in a production system. dequeuesimply removes and returns the first element, which is guaranteed to have the lowest priority.
3. Dijkstra’s Algorithm in Action
Now for the main event! We’ll implement Dijkstra’s algorithm to find the shortest path. This will be a standalone function that takes our Graph and startNode/endNode.
Create a file src/dijkstra.ts:
// src/dijkstra.ts
import { Graph } from './graph';
import { MinPriorityQueue } from './priorityQueue';
/**
* Implements Dijkstra's algorithm to find the shortest path between two nodes
* in a weighted graph with non-negative edge weights.
* @param graph The graph to search within.
* @param startNode The starting node for the path.
* @param endNode The target node for the path.
* @returns An array of node names representing the shortest path, or null if no path exists.
*/
function dijkstra(graph: Graph, startNode: string, endNode: string): string[] | null {
// 1. Initialize data structures
// distances: Stores the shortest distance found so far from startNode to every other node.
const distances: Map<string, number> = new Map();
// previousNodes: Stores the predecessor node in the shortest path found so far.
const previousNodes: Map<string, string | null> = new Map();
// pq: Our priority queue to efficiently get the next node to visit.
const pq = new MinPriorityQueue<string>();
// Set initial distances and previous nodes for all nodes in the graph.
// All nodes start with an "infinite" distance, except the startNode itself.
for (const node of graph.getNodes()) {
if (node === startNode) {
distances.set(node, 0);
pq.enqueue(node, 0); // Add the start node to the priority queue with distance 0.
} else {
distances.set(node, Infinity);
}
previousNodes.set(node, null); // No previous node initially.
}
// Main loop of Dijkstra's algorithm
// Continues as long as there are nodes to process in the priority queue.
while (!pq.isEmpty()) {
// Get the node with the smallest distance from the priority queue.
// This is our 'current' node.
const currentNode = pq.dequeue();
// If currentNode is undefined (queue was empty or error), or we've reached the endNode,
// we can potentially stop early if we only care about the path to endNode.
if (currentNode === undefined || distances.get(currentNode)! === Infinity) {
// No path to remaining nodes or queue unexpectedly empty.
break;
}
// If we reached the end node, we've found the shortest path.
// We can reconstruct and return it immediately.
if (currentNode === endNode) {
break; // Stop processing, we found our target!
}
// Explore neighbors of the currentNode
const neighbors = graph.getNeighbors(currentNode);
if (neighbors) { // Ensure the node has neighbors
for (const [neighbor, weight] of neighbors.entries()) {
// Calculate the distance to the neighbor through the currentNode.
const newDistance = distances.get(currentNode)! + weight;
// If this new path to the neighbor is shorter than any previously found path:
if (newDistance < distances.get(neighbor)!) {
// Update the shortest distance to this neighbor.
distances.set(neighbor, newDistance);
// Record that we came to this neighbor via currentNode.
previousNodes.set(neighbor, currentNode);
// Add the neighbor to the priority queue with its new, shorter distance.
pq.enqueue(neighbor, newDistance);
}
}
}
}
// 2. Reconstruct the path from endNode back to startNode
const path: string[] = [];
let current = endNode;
// If the endNode was never reached (its distance is still Infinity or no previous node),
// then there's no path.
if (distances.get(endNode) === Infinity) {
return null;
}
// Traverse backwards from endNode using previousNodes to build the path.
while (current !== null) {
path.unshift(current); // Add to the beginning of the array to get correct order.
current = previousNodes.get(current)!; // Move to the previous node.
}
return path;
}
export { dijkstra };
Explanation of Dijkstra’s Algorithm:
Initialization:
distances: AMapto store the shortest distance found so far fromstartNodeto every other node. Initially,startNodeis 0, and all others areInfinity.previousNodes: AMapto reconstruct the path. For each node, it stores the node that came before it in the shortest path fromstartNode.pq: OurMinPriorityQueueis initialized with thestartNodeand its distance (0).
Main Loop (
while (!pq.isEmpty())):- The loop continues as long as there are nodes to process in our priority queue.
currentNode = pq.dequeue(): We extract the node with the smallest known distance. This is the “greedy” step – we always explore the closest unvisited node first.- Early Exit: If
currentNodeisendNode, we’ve found the shortest path to our target, so we can stop. - Neighbor Exploration: We iterate through all
neighborsof thecurrentNode. - Distance Calculation: For each
neighbor, we calculatenewDistanceby adding thecurrentNode’s distance fromstartNodeto theweightof the edge connectingcurrentNodetoneighbor. - Relaxation: If
newDistanceis shorter than thedistancecurrently stored for thatneighbor, it means we’ve found a better path toneighborthroughcurrentNode. We then:- Update
distances.set(neighbor, newDistance). - Update
previousNodes.set(neighbor, currentNode). pq.enqueue(neighbor, newDistance): Add theneighborto the priority queue with its newly updated (shorter) distance. This ensures it will be considered for exploration later.
- Update
Path Reconstruction:
- After the loop, if
endNode’s distance is stillInfinity, it meansendNodewas unreachable. - Otherwise, we start from
endNodeand trace back usingpreviousNodesuntil we reachstartNode(wherepreviousNodes.get(current)becomesnull). path.unshift(current)adds nodes to the beginning of the array, ensuring the path is in the correct order fromstartNodetoendNode.
- After the loop, if
4. Putting It All Together: The Main Application
Finally, let’s create our main application file to use our Graph and dijkstra function.
Create a file src/index.ts:
// src/index.ts
import { Graph } from './graph';
import { dijkstra } from './dijkstra';
function main() {
console.log("Welcome to our TypeScript Route Finder!");
// 1. Create a new graph instance
const cityMap = new Graph();
// 2. Add cities (nodes) to our map
cityMap.addNode("City A");
cityMap.addNode("City B");
cityMap.addNode("City C");
cityMap.addNode("City D");
cityMap.addNode("City E");
cityMap.addNode("City F");
// 3. Add roads (edges) with distances (weights)
// Based on our Mermaid diagram:
cityMap.addEdge("City A", "City B", 4);
cityMap.addEdge("City A", "City C", 2);
cityMap.addEdge("City B", "City D", 5);
cityMap.addEdge("City C", "City D", 1);
cityMap.addEdge("City C", "City E", 8);
cityMap.addEdge("City D", "City E", 3);
cityMap.addEdge("City D", "City F", 6);
cityMap.addEdge("City E", "City F", 2);
// 4. Define our start and end points
const startCity = "City A";
const endCity = "City F";
console.log(`\nFinding the shortest route from ${startCity} to ${endCity}...`);
// 5. Run Dijkstra's algorithm
const shortestPath = dijkstra(cityMap, startCity, endCity);
// 6. Display the results
if (shortestPath) {
console.log(`Shortest path found: ${shortestPath.join(" -> ")}`);
// Calculate total distance (optional, but good to verify)
let totalDistance = 0;
for (let i = 0; i < shortestPath.length - 1; i++) {
const current = shortestPath[i];
const next = shortestPath[i + 1];
const weight = cityMap.getNeighbors(current)?.get(next);
if (weight !== undefined) {
totalDistance += weight;
} else {
console.warn(`Warning: Edge between ${current} and ${next} not found for distance calculation.`);
}
}
console.log(`Total distance: ${totalDistance}`);
} else {
console.log(`No path found from ${startCity} to ${endCity}.`);
}
console.log("\n--- End of Route Finder ---");
}
// Call the main function to run our application
main();
Explanation:
- We import our
Graphanddijkstrafunctions. - In
main(), we first create anew Graph(). - Then, we add all the cities from our example diagram using
addNode. - Next, we add the roads between them with their respective distances using
addEdge. Remember,addEdgeautomatically creates bidirectional connections. - We define our
startCityandendCity. - We call
dijkstrawith ourcityMap,startCity, andendCity. - Finally, we check if a path was found and print it out, along with the total calculated distance.
Running Your Route Finder
Now, let’s compile and run our TypeScript project!
Compile TypeScript: In your terminal, from the
route-finderdirectory:npx tscThis command will use your
tsconfig.jsonto compile all.tsfiles insrcinto.jsfiles in thedistdirectory.Run the Compiled JavaScript:
node dist/index.js
You should see output similar to this:
Welcome to our TypeScript Route Finder!
Finding the shortest route from City A to City F...
Shortest path found: City A -> City C -> City D -> City E -> City F
Total distance: 8
--- End of Route Finder ---
Congratulations! You’ve successfully built a working route finder using Dijkstra’s algorithm. The path A -> C -> D -> E -> F with a total distance of 8 is indeed the shortest path in our example graph.
Mini-Challenge: Expand Your Map!
It’s your turn to play city planner!
Challenge:
- Add a new city: Introduce a
City Gto yourcityMapinsrc/index.ts. - Add new roads: Create at least two new roads connecting
City Gto existing cities (e.g.,City FandCity B) with different weights. - Find a new route: Try finding a shortest path from
City AtoCity G, or fromCity GtoCity C. - Observe: How does adding new nodes and edges impact the possible routes and their lengths?
Hint: Remember to add City G using cityMap.addNode("City G") and then connect it using cityMap.addEdge(...). Don’t forget to recompile (npx tsc) and rerun (node dist/index.js) after making changes!
What to observe/learn: This exercise reinforces how graph structures are dynamic and how algorithms like Dijkstra’s adapt to changes, always finding the optimal path based on the current graph state. It also highlights the importance of correctly modeling your problem domain (the cities and roads) with your graph data structure.
Common Pitfalls & Troubleshooting
Even experienced developers can run into issues. Here are some common pitfalls when working with graphs and Dijkstra’s:
Negative Edge Weights: Dijkstra’s algorithm is designed for graphs with non-negative edge weights. If your graph contains negative cycles or even single negative edges, Dijkstra’s might produce incorrect results or get stuck in an infinite loop if not handled carefully. For graphs with negative weights, algorithms like Bellman-Ford or SPFA are needed.
- Troubleshooting: Double-check your
addEdgecalls to ensure allweightvalues are positive. If you encounter negative weights in a real-world scenario, consider if Dijkstra’s is the right algorithm or if you can transform the weights (e.g., by adding a large constant to all weights, but this changes the problem).
- Troubleshooting: Double-check your
Incorrect Graph Representation (Disconnected Nodes/Edges): If you forget to add a node, or an edge, or if your edges are accidentally one-way when they should be two-way (undirected), Dijkstra’s might report no path found (
null) even if one visually exists.- Troubleshooting:
- Visual Inspection: For small graphs, draw them out (like our Mermaid diagram) and trace your
addNodeandaddEdgecalls. console.logGraph: Add a temporary method to yourGraphclass toconsole.logitsadjacencyListto verify its structure.- Directionality: Ensure your
addEdgecorrectly handles whether roads are one-way or two-way. Our implementation assumes two-way.
- Visual Inspection: For small graphs, draw them out (like our Mermaid diagram) and trace your
- Troubleshooting:
Priority Queue Implementation Errors: A faulty priority queue can completely break Dijkstra’s. If it doesn’t always return the element with the truly lowest priority, Dijkstra’s greedy choice fails.
- Troubleshooting:
- Test Priority Queue Independently: Before integrating, test your
MinPriorityQueuewith simpleenqueue/dequeuesequences to ensure it behaves as expected (e.g.,pq.enqueue("A", 10); pq.enqueue("B", 5); pq.dequeue()should return “B”). - Complexity vs. Correctness: Remember our simple
sort()-basedenqueueis O(N log N). For larger graphs, if performance is an issue, a heap-based PQ (O(log N)) is essential.
- Test Priority Queue Independently: Before integrating, test your
- Troubleshooting:
Path Reconstruction Bugs: Sometimes the distances are correct, but the path comes out wrong (e.g., in reverse, or missing nodes).
- Troubleshooting:
previousNodesCheck:console.log(previousNodes)after the main loop to see if the back-pointers are set correctly.unshiftvs.push: Ensure you’re usingpath.unshift(current)to add elements to the beginning of the path array, orpath.push(current)and thenpath.reverse()at the end.
- Troubleshooting:
Summary
Phew! You’ve just completed a significant hands-on project, building a functional route finder from scratch. Let’s recap what we’ve accomplished:
- Graph Representation: You learned to effectively model real-world networks, like a city map, using a
Graphdata structure with an adjacency list, incorporating weighted edges. - Dijkstra’s Algorithm: You gained a deep understanding of Dijkstra’s algorithm, its greedy nature, and its step-by-step process for finding the shortest paths in weighted, non-negative graphs.
- Priority Queue Importance: You saw how a
MinPriorityQueueis a critical component for optimizing Dijkstra’s, allowing it to efficiently select the next closest node to explore. - Practical Application: You implemented a tangible application that demonstrates the power of graph algorithms in navigation, network routing, and many other areas of computer science.
- TypeScript in Action: You applied your TypeScript skills to create well-typed, organized code for complex algorithms.
This project is a fantastic milestone in your DSA journey. It bridges the gap between theoretical knowledge and practical implementation, giving you a powerful tool and a deeper appreciation for how these concepts drive modern software.
What’s Next?
In the upcoming chapters, we’ll continue to explore more advanced graph algorithms, such as other shortest path algorithms (like Bellman-Ford for negative weights), Minimum Spanning Trees (Prim’s and Kruskal’s), and flow networks. You’ll see how graphs are foundational to many advanced computing problems. Keep practicing, keep building, and remember that every line of code you write deepens your understanding!
References
- Node.js v24.13.0 LTS Documentation
- TypeScript Handbook - Get Started
- Dijkstra’s Algorithm - Wikipedia
- Graph (abstract data type) - Wikipedia
- Priority Queue - Wikipedia
This page is AI-assisted and reviewed. It references official documentation and recognized resources where relevant.