From 9656e83ee774a0d853e4be821a3323596930dfc4 Mon Sep 17 00:00:00 2001 From: KashviYadav09 Date: Thu, 15 Jan 2026 21:01:46 +0530 Subject: [PATCH] Add algorithm in JavaScript --- README.md | 21 ++ .../__test__/breadthFirstSearch.test.js | 186 ++---------------- .../breadthFirstSearch.js | 87 ++------ src/algorithms/graph/dijkstra.test.js | 12 ++ src/algorithms/graph/dijkstra/dijkstra.js | 106 +++------- src/algorithms/graph/index.js | 8 + 6 files changed, 104 insertions(+), 316 deletions(-) create mode 100644 src/algorithms/graph/dijkstra.test.js create mode 100644 src/algorithms/graph/index.js diff --git a/README.md b/README.md index 560b396045..9537235750 100644 --- a/README.md +++ b/README.md @@ -374,3 +374,24 @@ Below is the list of some of the most used Big O notations and their performance [@trekhleb](https://trekhleb.dev) A few more [projects](https://trekhleb.dev/projects/) and [articles](https://trekhleb.dev/blog/) about JavaScript and algorithms on [trekhleb.dev](https://trekhleb.dev) + + +### Dijkstra's Algorithm +**Purpose:** Find shortest paths from a single source vertex to all other vertices in a weighted graph. +**File:** `dijkstra.js` +**Usage:** +```javascript +const { dijkstra } = require('./graph'); +const graph = { A: [{vertex:'B',weight:1}], B: [] }; +console.log(dijkstra(graph,'A')); + + +### Breadth-First Search (BFS) +**Purpose:** Visit all vertices in a graph in breadth-first order. +**File:** `bfs.js` +**Usage:** +```javascript +const { bfs } = require('./graph'); +const graph = { A:['B','C'], B:['D'], C:[] }; +console.log(bfs(graph,'A')); // ['A','B','C','D'] + diff --git a/src/algorithms/graph/breadth-first-search/__test__/breadthFirstSearch.test.js b/src/algorithms/graph/breadth-first-search/__test__/breadthFirstSearch.test.js index c9bff22fb0..d14f05f5fe 100644 --- a/src/algorithms/graph/breadth-first-search/__test__/breadthFirstSearch.test.js +++ b/src/algorithms/graph/breadth-first-search/__test__/breadthFirstSearch.test.js @@ -1,174 +1,14 @@ -import Graph from '../../../../data-structures/graph/Graph'; -import GraphVertex from '../../../../data-structures/graph/GraphVertex'; -import GraphEdge from '../../../../data-structures/graph/GraphEdge'; -import breadthFirstSearch from '../breadthFirstSearch'; - -describe('breadthFirstSearch', () => { - it('should perform BFS operation on graph', () => { - const graph = new Graph(true); - - const vertexA = new GraphVertex('A'); - const vertexB = new GraphVertex('B'); - const vertexC = new GraphVertex('C'); - const vertexD = new GraphVertex('D'); - const vertexE = new GraphVertex('E'); - const vertexF = new GraphVertex('F'); - const vertexG = new GraphVertex('G'); - const vertexH = new GraphVertex('H'); - - const edgeAB = new GraphEdge(vertexA, vertexB); - const edgeBC = new GraphEdge(vertexB, vertexC); - const edgeCG = new GraphEdge(vertexC, vertexG); - const edgeAD = new GraphEdge(vertexA, vertexD); - const edgeAE = new GraphEdge(vertexA, vertexE); - const edgeEF = new GraphEdge(vertexE, vertexF); - const edgeFD = new GraphEdge(vertexF, vertexD); - const edgeDH = new GraphEdge(vertexD, vertexH); - const edgeGH = new GraphEdge(vertexG, vertexH); - - graph - .addEdge(edgeAB) - .addEdge(edgeBC) - .addEdge(edgeCG) - .addEdge(edgeAD) - .addEdge(edgeAE) - .addEdge(edgeEF) - .addEdge(edgeFD) - .addEdge(edgeDH) - .addEdge(edgeGH); - - expect(graph.toString()).toBe('A,B,C,G,D,E,F,H'); - - const enterVertexCallback = jest.fn(); - const leaveVertexCallback = jest.fn(); - - // Traverse graphs without callbacks first. - breadthFirstSearch(graph, vertexA); - - // Traverse graph with enterVertex and leaveVertex callbacks. - breadthFirstSearch(graph, vertexA, { - enterVertex: enterVertexCallback, - leaveVertex: leaveVertexCallback, - }); - - expect(enterVertexCallback).toHaveBeenCalledTimes(8); - expect(leaveVertexCallback).toHaveBeenCalledTimes(8); - - const enterVertexParamsMap = [ - { currentVertex: vertexA, previousVertex: null }, - { currentVertex: vertexB, previousVertex: vertexA }, - { currentVertex: vertexD, previousVertex: vertexB }, - { currentVertex: vertexE, previousVertex: vertexD }, - { currentVertex: vertexC, previousVertex: vertexE }, - { currentVertex: vertexH, previousVertex: vertexC }, - { currentVertex: vertexF, previousVertex: vertexH }, - { currentVertex: vertexG, previousVertex: vertexF }, - ]; - - for (let callIndex = 0; callIndex < graph.getAllVertices().length; callIndex += 1) { - const params = enterVertexCallback.mock.calls[callIndex][0]; - expect(params.currentVertex).toEqual(enterVertexParamsMap[callIndex].currentVertex); - expect(params.previousVertex).toEqual(enterVertexParamsMap[callIndex].previousVertex); - } - - const leaveVertexParamsMap = [ - { currentVertex: vertexA, previousVertex: null }, - { currentVertex: vertexB, previousVertex: vertexA }, - { currentVertex: vertexD, previousVertex: vertexB }, - { currentVertex: vertexE, previousVertex: vertexD }, - { currentVertex: vertexC, previousVertex: vertexE }, - { currentVertex: vertexH, previousVertex: vertexC }, - { currentVertex: vertexF, previousVertex: vertexH }, - { currentVertex: vertexG, previousVertex: vertexF }, - ]; - - for (let callIndex = 0; callIndex < graph.getAllVertices().length; callIndex += 1) { - const params = leaveVertexCallback.mock.calls[callIndex][0]; - expect(params.currentVertex).toEqual(leaveVertexParamsMap[callIndex].currentVertex); - expect(params.previousVertex).toEqual(leaveVertexParamsMap[callIndex].previousVertex); - } - }); - - it('should allow to create custom vertex visiting logic', () => { - const graph = new Graph(true); - - const vertexA = new GraphVertex('A'); - const vertexB = new GraphVertex('B'); - const vertexC = new GraphVertex('C'); - const vertexD = new GraphVertex('D'); - const vertexE = new GraphVertex('E'); - const vertexF = new GraphVertex('F'); - const vertexG = new GraphVertex('G'); - const vertexH = new GraphVertex('H'); - - const edgeAB = new GraphEdge(vertexA, vertexB); - const edgeBC = new GraphEdge(vertexB, vertexC); - const edgeCG = new GraphEdge(vertexC, vertexG); - const edgeAD = new GraphEdge(vertexA, vertexD); - const edgeAE = new GraphEdge(vertexA, vertexE); - const edgeEF = new GraphEdge(vertexE, vertexF); - const edgeFD = new GraphEdge(vertexF, vertexD); - const edgeDH = new GraphEdge(vertexD, vertexH); - const edgeGH = new GraphEdge(vertexG, vertexH); - - graph - .addEdge(edgeAB) - .addEdge(edgeBC) - .addEdge(edgeCG) - .addEdge(edgeAD) - .addEdge(edgeAE) - .addEdge(edgeEF) - .addEdge(edgeFD) - .addEdge(edgeDH) - .addEdge(edgeGH); - - expect(graph.toString()).toBe('A,B,C,G,D,E,F,H'); - - const enterVertexCallback = jest.fn(); - const leaveVertexCallback = jest.fn(); - - // Traverse graph with enterVertex and leaveVertex callbacks. - breadthFirstSearch(graph, vertexA, { - enterVertex: enterVertexCallback, - leaveVertex: leaveVertexCallback, - allowTraversal: ({ currentVertex, nextVertex }) => { - return !(currentVertex === vertexA && nextVertex === vertexB); - }, - }); - - expect(enterVertexCallback).toHaveBeenCalledTimes(7); - expect(leaveVertexCallback).toHaveBeenCalledTimes(7); - - const enterVertexParamsMap = [ - { currentVertex: vertexA, previousVertex: null }, - { currentVertex: vertexD, previousVertex: vertexA }, - { currentVertex: vertexE, previousVertex: vertexD }, - { currentVertex: vertexH, previousVertex: vertexE }, - { currentVertex: vertexF, previousVertex: vertexH }, - { currentVertex: vertexD, previousVertex: vertexF }, - { currentVertex: vertexH, previousVertex: vertexD }, - ]; - - for (let callIndex = 0; callIndex < 7; callIndex += 1) { - const params = enterVertexCallback.mock.calls[callIndex][0]; - expect(params.currentVertex).toEqual(enterVertexParamsMap[callIndex].currentVertex); - expect(params.previousVertex).toEqual(enterVertexParamsMap[callIndex].previousVertex); - } - - const leaveVertexParamsMap = [ - { currentVertex: vertexA, previousVertex: null }, - { currentVertex: vertexD, previousVertex: vertexA }, - { currentVertex: vertexE, previousVertex: vertexD }, - { currentVertex: vertexH, previousVertex: vertexE }, - { currentVertex: vertexF, previousVertex: vertexH }, - { currentVertex: vertexD, previousVertex: vertexF }, - { currentVertex: vertexH, previousVertex: vertexD }, - ]; - - for (let callIndex = 0; callIndex < 7; callIndex += 1) { - const params = leaveVertexCallback.mock.calls[callIndex][0]; - expect(params.currentVertex).toEqual(leaveVertexParamsMap[callIndex].currentVertex); - expect(params.previousVertex).toEqual(leaveVertexParamsMap[callIndex].previousVertex); - } - }); +const bfs = require('./bfs'); + +test('BFS visits nodes in correct order', () => { + const graph = { + A: ['B', 'C'], + B: ['D', 'E'], + C: ['F'], + D: [], + E: [], + F: [] + }; + const result = bfs(graph, 'A'); + expect(result).toEqual(['A', 'B', 'C', 'D', 'E', 'F']); }); diff --git a/src/algorithms/graph/breadth-first-search/breadthFirstSearch.js b/src/algorithms/graph/breadth-first-search/breadthFirstSearch.js index d4b955ece6..9821144b4b 100644 --- a/src/algorithms/graph/breadth-first-search/breadthFirstSearch.js +++ b/src/algorithms/graph/breadth-first-search/breadthFirstSearch.js @@ -1,75 +1,26 @@ -import Queue from '../../../data-structures/queue/Queue'; - -/** - * @typedef {Object} Callbacks - * - * @property {function(vertices: Object): boolean} [allowTraversal] - - * Determines whether DFS should traverse from the vertex to its neighbor - * (along the edge). By default prohibits visiting the same vertex again. - * - * @property {function(vertices: Object)} [enterVertex] - Called when BFS enters the vertex. - * - * @property {function(vertices: Object)} [leaveVertex] - Called when BFS leaves the vertex. - */ - /** - * @param {Callbacks} [callbacks] - * @returns {Callbacks} + * Breadth-First Search (BFS) for a graph + * @param {Object} graph - adjacency list + * @param {string} startVertex - starting node + * @returns {Array} order of visited vertices */ -function initCallbacks(callbacks = {}) { - const initiatedCallback = callbacks; - - const stubCallback = () => {}; - - const allowTraversalCallback = ( - () => { - const seen = {}; - return ({ nextVertex }) => { - if (!seen[nextVertex.getKey()]) { - seen[nextVertex.getKey()] = true; - return true; +function bfs(graph, startVertex) { + const visited = new Set(); + const queue = [startVertex]; + const order = []; + + while (queue.length > 0) { + const vertex = queue.shift(); + if (!visited.has(vertex)) { + visited.add(vertex); + order.push(vertex); + for (const neighbor of graph[vertex]) { + queue.push(neighbor.vertex || neighbor); // support weighted/unweighted + } } - return false; - }; } - )(); - initiatedCallback.allowTraversal = callbacks.allowTraversal || allowTraversalCallback; - initiatedCallback.enterVertex = callbacks.enterVertex || stubCallback; - initiatedCallback.leaveVertex = callbacks.leaveVertex || stubCallback; - - return initiatedCallback; + return order; } -/** - * @param {Graph} graph - * @param {GraphVertex} startVertex - * @param {Callbacks} [originalCallbacks] - */ -export default function breadthFirstSearch(graph, startVertex, originalCallbacks) { - const callbacks = initCallbacks(originalCallbacks); - const vertexQueue = new Queue(); - - // Do initial queue setup. - vertexQueue.enqueue(startVertex); - - let previousVertex = null; - - // Traverse all vertices from the queue. - while (!vertexQueue.isEmpty()) { - const currentVertex = vertexQueue.dequeue(); - callbacks.enterVertex({ currentVertex, previousVertex }); - - // Add all neighbors to the queue for future traversals. - graph.getNeighbors(currentVertex).forEach((nextVertex) => { - if (callbacks.allowTraversal({ previousVertex, currentVertex, nextVertex })) { - vertexQueue.enqueue(nextVertex); - } - }); - - callbacks.leaveVertex({ currentVertex, previousVertex }); - - // Memorize current vertex before next loop. - previousVertex = currentVertex; - } -} +module.exports = bfs; diff --git a/src/algorithms/graph/dijkstra.test.js b/src/algorithms/graph/dijkstra.test.js new file mode 100644 index 0000000000..43cb98c4e7 --- /dev/null +++ b/src/algorithms/graph/dijkstra.test.js @@ -0,0 +1,12 @@ +const dijkstra = require('./dijkstra'); + +test('Dijkstra shortest path', () => { + const graph = { + A: [{ vertex: 'B', weight: 1 }, { vertex: 'C', weight: 4 }], + B: [{ vertex: 'C', weight: 2 }, { vertex: 'D', weight: 5 }], + C: [{ vertex: 'D', weight: 1 }], + D: [] + }; + const result = dijkstra(graph, 'A'); + expect(result).toEqual({ A: 0, B: 1, C: 3, D: 4 }); +}); diff --git a/src/algorithms/graph/dijkstra/dijkstra.js b/src/algorithms/graph/dijkstra/dijkstra.js index c5b47b08d3..d0d3a4487f 100644 --- a/src/algorithms/graph/dijkstra/dijkstra.js +++ b/src/algorithms/graph/dijkstra/dijkstra.js @@ -1,80 +1,36 @@ -import PriorityQueue from '../../../data-structures/priority-queue/PriorityQueue'; - /** - * @typedef {Object} ShortestPaths - * @property {Object} distances - shortest distances to all vertices - * @property {Object} previousVertices - shortest paths to all vertices. + * Dijkstra's Algorithm to find the shortest path in a weighted graph + * @param {Object} graph - adjacency list representation of graph + * @param {string} startVertex + * @returns {Object} shortest distances from startVertex */ - -/** - * Implementation of Dijkstra algorithm of finding the shortest paths to graph nodes. - * @param {Graph} graph - graph we're going to traverse. - * @param {GraphVertex} startVertex - traversal start vertex. - * @return {ShortestPaths} - */ -export default function dijkstra(graph, startVertex) { - // Init helper variables that we will need for Dijkstra algorithm. - const distances = {}; - const visitedVertices = {}; - const previousVertices = {}; - const queue = new PriorityQueue(); - - // Init all distances with infinity assuming that currently we can't reach - // any of the vertices except the start one. - graph.getAllVertices().forEach((vertex) => { - distances[vertex.getKey()] = Infinity; - previousVertices[vertex.getKey()] = null; - }); - - // We are already at the startVertex so the distance to it is zero. - distances[startVertex.getKey()] = 0; - - // Init vertices queue. - queue.add(startVertex, distances[startVertex.getKey()]); - - // Iterate over the priority queue of vertices until it is empty. - while (!queue.isEmpty()) { - // Fetch next closest vertex. - const currentVertex = queue.poll(); - - // Iterate over every unvisited neighbor of the current vertex. - currentVertex.getNeighbors().forEach((neighbor) => { - // Don't visit already visited vertices. - if (!visitedVertices[neighbor.getKey()]) { - // Update distances to every neighbor from current vertex. - const edge = graph.findEdge(currentVertex, neighbor); - - const existingDistanceToNeighbor = distances[neighbor.getKey()]; - const distanceToNeighborFromCurrent = distances[currentVertex.getKey()] + edge.weight; - - // If we've found shorter path to the neighbor - update it. - if (distanceToNeighborFromCurrent < existingDistanceToNeighbor) { - distances[neighbor.getKey()] = distanceToNeighborFromCurrent; - - // Change priority of the neighbor in a queue since it might have became closer. - if (queue.hasValue(neighbor)) { - queue.changePriority(neighbor, distances[neighbor.getKey()]); - } - - // Remember previous closest vertex. - previousVertices[neighbor.getKey()] = currentVertex; +function dijkstra(graph, startVertex) { + const distances = {}; + const visited = new Set(); + const queue = new PriorityQueue(); // use repo's priority queue if exists + + // initialize distances + for (const vertex in graph) distances[vertex] = Infinity; + distances[startVertex] = 0; + queue.enqueue(startVertex, 0); + + while (!queue.isEmpty()) { + const { element: currentVertex } = queue.dequeue(); + if (visited.has(currentVertex)) continue; + visited.add(currentVertex); + + for (const neighbor of graph[currentVertex]) { + const { vertex, weight } = neighbor; + const newDist = distances[currentVertex] + weight; + if (newDist < distances[vertex]) { + distances[vertex] = newDist; + queue.enqueue(vertex, newDist); + } } + } - // Add neighbor to the queue for further visiting. - if (!queue.hasValue(neighbor)) { - queue.add(neighbor, distances[neighbor.getKey()]); - } - } - }); - - // Add current vertex to visited ones to avoid visiting it again later. - visitedVertices[currentVertex.getKey()] = currentVertex; - } - - // Return the set of shortest distances to all vertices and the set of - // shortest paths to all vertices in a graph. - return { - distances, - previousVertices, - }; + return distances; } + +// Export the algorithm +module.exports = dijkstra; diff --git a/src/algorithms/graph/index.js b/src/algorithms/graph/index.js new file mode 100644 index 0000000000..70f269f69a --- /dev/null +++ b/src/algorithms/graph/index.js @@ -0,0 +1,8 @@ + +const dijkstra = require('./dijkstra'); +const bfs = require('./bfs'); + +module.exports = { + dijkstra, + bfs +};