diff --git a/lib/algorithms/graph/bfs.ts b/lib/algorithms/graph/bfs.ts new file mode 100644 index 0000000..0e3e5f4 --- /dev/null +++ b/lib/algorithms/graph/bfs.ts @@ -0,0 +1,108 @@ +import { AlgorithmVisualization, GraphStep } from "../../types"; +import { createVisualization } from "../utils"; + +export function bfs( + array: number[], + target: number = 0 +): AlgorithmVisualization { + // Ignore the input array and use a predefined adjacency list for graph visualization + // The target parameter will be used as the starting vertex, but ensure it's valid + // Create a sample graph as an adjacency list first + const adjacencyList: number[][] = [ + [1, 2], // Vertex 0 connected to 1, 2 + [0, 3, 4], // Vertex 1 connected to 0, 3, 4 + [0, 5], // Vertex 2 connected to 0, 5 + [1], // Vertex 3 connected to 1 + [1, 5], // Vertex 4 connected to 1, 5 + [2, 4], // Vertex 5 connected to 2, 4 + ]; + + // Ensure startVertex is valid (0 to 5) + const startVertex = target >= 0 && target < adjacencyList.length ? target : 0; + + const steps: GraphStep[] = []; + const visited: number[] = []; + const queue: number[] = [startVertex]; + const path: number[] = []; + + // Initial state + steps.push({ + adjacencyList: adjacencyList.map((row) => [...row]), // Deep copy + current: -1, + visited: [...visited], + stack: [...queue], // Using stack field to represent queue in visualization + path: [...path], + }); + + // Mark the start vertex as visited before starting the main loop + visited.push(startVertex); + + while (queue.length > 0) { + // Get the next vertex from the queue + const current = queue.shift()!; + + // Add current to path + path.push(current); + + // Add step for current vertex visit + steps.push({ + adjacencyList: adjacencyList.map((row) => [...row]), + current, + visited: [...visited], + stack: [...queue], // Using stack field to represent queue in visualization + path: [...path], + }); + + // Get all adjacent vertices + const neighbors = [...adjacencyList[current]]; + + // For each unvisited neighbor + for (const neighbor of neighbors) { + if (!visited.includes(neighbor)) { + // Mark as visited and add to queue + visited.push(neighbor); + queue.push(neighbor); + + // Add step showing queue update + steps.push({ + adjacencyList: adjacencyList.map((row) => [...row]), + current, + visited: [...visited], + stack: [...queue], // Using stack field to represent queue in visualization + path: [...path], + }); + } + } + } + + // Final state + steps.push({ + adjacencyList: adjacencyList.map((row) => [...row]), + current: -1, + visited: [...visited], + stack: [], // Queue is empty now + path: [...path], + }); + + return createVisualization("bfs", steps, { + timeComplexity: "O(V + E)", // Vertices + Edges + spaceComplexity: "O(V)", // Vertices for queue and visited set + reference: "https://en.wikipedia.org/wiki/Breadth-first_search", + pseudoCode: [ + "procedure BFS(G, start_v)", + " let Q be a queue", + " Q.enqueue(start_v)", + " mark start_v as visited", + " while Q is not empty do", + " v = Q.dequeue()", + " for all neighbors w of v do", + " if w is not visited then", + " mark w as visited", + " Q.enqueue(w)", + " end if", + " end for", + " end while", + "end procedure", + ], + }); +} diff --git a/lib/algorithms/graph/dijkstra.ts b/lib/algorithms/graph/dijkstra.ts new file mode 100644 index 0000000..98f9a48 --- /dev/null +++ b/lib/algorithms/graph/dijkstra.ts @@ -0,0 +1,159 @@ +import { AlgorithmVisualization, GraphStep } from "../../types"; +import { createVisualization } from "../utils"; + +export function dijkstra( + array: number[], + source: number = 0 +): AlgorithmVisualization { + // Create a weighted graph using an adjacency matrix where [i][j] is the weight of edge from i to j + // Using Infinity to represent no connection between vertices + const graph: number[][] = [ + [0, 4, 2, Infinity, Infinity, Infinity], + [4, 0, 1, 5, 2, Infinity], + [2, 1, 0, Infinity, 3, 6], + [Infinity, 5, Infinity, 0, 2, Infinity], + [Infinity, 2, 3, 2, 0, 1], + [Infinity, Infinity, 6, Infinity, 1, 0], + ]; + + // Ensure source vertex is valid (0 to 5) + const startVertex = source >= 0 && source < graph.length ? source : 0; + + // Number of vertices + const V = graph.length; + + // Create adjacency list representation for visualization + const adjacencyList: number[][] = graph.map((row) => + row.reduce((neighbors, weight, j) => { + if (weight !== Infinity && weight !== 0) { + neighbors.push(j); + } + return neighbors; + }, [] as number[]) + ); + + const steps: GraphStep[] = []; + + // Distance array to store shortest distance from source to i + const dist: number[] = Array(V).fill(Infinity); + dist[startVertex] = 0; + + // Visited array to keep track of vertices included in the shortest path tree + const visited: number[] = []; + + // Path to store the actual path taken + const path: number[] = []; + + // Queue to visualize the current frontier (for visual purposes) + const queue: number[] = [startVertex]; + + // Initial state + steps.push({ + adjacencyList: adjacencyList.map((row) => [...row]), + current: -1, + visited: [...visited], + stack: [...queue], // Use stack to represent the priority queue visually + path: [...path], + }); + + // Find shortest path for all vertices + for (let count = 0; count < V; count++) { + // Find the minimum distance vertex from the set of vertices not yet visited + let minDist = Infinity; + let minIndex = -1; + + for (let v = 0; v < V; v++) { + if (!visited.includes(v) && dist[v] <= minDist) { + minDist = dist[v]; + minIndex = v; + } + } + + // If no valid vertex is found, break + if (minIndex === -1) break; + + // Add the selected vertex to the visited set + visited.push(minIndex); + + // Add to path + path.push(minIndex); + + // Add a step showing the current vertex being visited + steps.push({ + adjacencyList: adjacencyList.map((row) => [...row]), + current: minIndex, + visited: [...visited], + stack: [...queue], + path: [...path], + }); + + // Update the distance value of adjacent vertices of the picked vertex + for (let v = 0; v < V; v++) { + // Update dist[v] only if: + // 1. There's an edge from minIndex to v + // 2. v is not in visited + // 3. The total path distance is smaller than the current dist[v] + if ( + !visited.includes(v) && + graph[minIndex][v] !== 0 && + graph[minIndex][v] !== Infinity && + dist[minIndex] !== Infinity && + dist[minIndex] + graph[minIndex][v] < dist[v] + ) { + dist[v] = dist[minIndex] + graph[minIndex][v]; + + // Add unvisited neighbor to queue for visualization + if (!queue.includes(v)) { + queue.push(v); + } + + // Add a step showing the distance update + steps.push({ + adjacencyList: adjacencyList.map((row) => [...row]), + current: minIndex, + visited: [...visited], + stack: [...queue], + path: [...path], + }); + } + } + + // Remove current vertex from queue for visualization + const currentIndex = queue.indexOf(minIndex); + if (currentIndex !== -1) { + queue.splice(currentIndex, 1); + } + } + + // Final state + steps.push({ + adjacencyList: adjacencyList.map((row) => [...row]), + current: -1, + visited: [...visited], + stack: [], + path: [...path], + }); + + return createVisualization("dijkstra", steps, { + timeComplexity: "O(V²)", // For simple implementation, can be O(E + V log V) with a priority queue + spaceComplexity: "O(V)", // For distance array and visited set + reference: "https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm", + pseudoCode: [ + "procedure Dijkstra(G, source)", + " for each vertex v in G:", + " dist[v] := Infinity", + " visited[v] := false", + " dist[source] := 0", + "", + " for i from 0 to |V| - 1:", + " u := extract vertex with min dist from unvisited", + " visited[u] := true", + "", + " for each neighbor v of u:", + " if !visited[v] and dist[u] + weight(u,v) < dist[v]:", + " dist[v] := dist[u] + weight(u,v)", + " return dist", + "end procedure", + ], + }); +} diff --git a/lib/algorithms/graph/topologicalSort.ts b/lib/algorithms/graph/topologicalSort.ts new file mode 100644 index 0000000..da2edb1 --- /dev/null +++ b/lib/algorithms/graph/topologicalSort.ts @@ -0,0 +1,132 @@ +import { AlgorithmVisualization, GraphStep } from "../../types"; +import { createVisualization } from "../utils"; + +export function topologicalSort( + array: number[], + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _target: number = 0 +): AlgorithmVisualization { + // Create a directed acyclic graph (DAG) for topological sorting + // For visualization purposes, we'll create a specific DAG example + // where vertices might represent tasks and edges represent dependencies + + // Adjacency list representation of a directed graph + const adjacencyList: number[][] = [ + [1, 2], // Vertex 0 has edges to 1, 2 + [3], // Vertex 1 has edge to 3 + [3, 4], // Vertex 2 has edges to 3, 4 + [5], // Vertex 3 has edge to 5 + [5], // Vertex 4 has edge to 5 + [], // Vertex 5 has no outgoing edges + ]; + + const steps: GraphStep[] = []; + const visited: number[] = []; + const stack: number[] = []; // For topological sort result + const path: number[] = []; // Track the DFS path + const tempVisited: number[] = []; // For cycle detection + + // Initial state + steps.push({ + adjacencyList: adjacencyList.map((row) => [...row]), + current: -1, + visited: [...visited], + stack: [...stack], + path: [...path], + }); + + // Helper function to perform DFS with visualization + function dfsVisit(vertex: number) { + // Mark as temporarily visited for cycle detection + tempVisited.push(vertex); + + // Add to current path + path.push(vertex); + + // Add step to show current vertex being visited + steps.push({ + adjacencyList: adjacencyList.map((row) => [...row]), + current: vertex, + visited: [...visited], + stack: [...stack], + path: [...path], + }); + + // Visit all unvisited neighbors + for (const neighbor of adjacencyList[vertex]) { + // Skip if already completely visited + if (visited.includes(neighbor)) continue; + + // If temporarily visited, we have a cycle (which breaks topological sort) + // For this visualization, we'll just skip it + if (tempVisited.includes(neighbor)) continue; + + // Recursively visit the neighbor + dfsVisit(neighbor); + } + + // Remove from temporary visited set + const tempIndex = tempVisited.indexOf(vertex); + if (tempIndex !== -1) { + tempVisited.splice(tempIndex, 1); + } + + // Mark as completely visited + visited.push(vertex); + + // Add to the result stack (pre-pending to get correct order) + stack.unshift(vertex); + + // Add step to show vertex being added to result + steps.push({ + adjacencyList: adjacencyList.map((row) => [...row]), + current: vertex, + visited: [...visited], + stack: [...stack], + path: [...path], + }); + } + + // Perform topological sort using DFS + for (let i = 0; i < adjacencyList.length; i++) { + if (!visited.includes(i) && !tempVisited.includes(i)) { + dfsVisit(i); + } + } + + // Final state - show the topological ordering + steps.push({ + adjacencyList: adjacencyList.map((row) => [...row]), + current: -1, + visited: [...visited], + stack: [...stack], // This now contains the topological ordering + path: [...path], + }); + + return createVisualization("topologicalSort", steps, { + timeComplexity: "O(V + E)", // V = vertices, E = edges + spaceComplexity: "O(V)", // For the visited and stack arrays + reference: "https://en.wikipedia.org/wiki/Topological_sorting", + pseudoCode: [ + "procedure TopologicalSort(G):", + " L := Empty list that will contain the sorted elements", + " S := Set of all nodes with no incoming edge", + "", + " while S is not empty do", + " remove a node n from S", + " add n to L", + " for each node m with an edge e from n to m do", + " remove edge e from the graph", + " if m has no other incoming edges then", + " insert m into S", + " end for", + " end while", + "", + " if graph has edges then", + " return error (graph has at least one cycle)", + " else", + " return L (a topologically sorted order)", + "end procedure", + ], + }); +} diff --git a/lib/algorithms/index.ts b/lib/algorithms/index.ts index d933bb8..20ed55b 100644 --- a/lib/algorithms/index.ts +++ b/lib/algorithms/index.ts @@ -8,6 +8,9 @@ import { heapSort } from "./sorting/heapSort"; import { linearSearch } from "./searching/linearSearch"; import { binarySearch } from "./searching/binarySearch"; import { dfs } from "./graph/dfs"; +import { bfs } from "./graph/bfs"; +import { dijkstra } from "./graph/dijkstra"; +import { topologicalSort } from "./graph/topologicalSort"; // Define types for different algorithm categories type SortingOrSearchFunction = ( @@ -30,6 +33,9 @@ const algorithms: Record = { linearSearch, binarySearch, dfs, + bfs, + dijkstra, + topologicalSort, }; // Get algorithm function by name @@ -50,4 +56,7 @@ export { linearSearch, binarySearch, dfs, + bfs, + dijkstra, + topologicalSort, }; diff --git a/lib/algorithms/metadata.ts b/lib/algorithms/metadata.ts index 63937c0..5cd7292 100644 --- a/lib/algorithms/metadata.ts +++ b/lib/algorithms/metadata.ts @@ -83,4 +83,34 @@ export const availableAlgorithms: Record = { "Depth-First Search (DFS) is a graph traversal algorithm that explores as far as possible along each branch before backtracking. It uses a stack data structure (often implemented using recursion) to keep track of vertices to visit next. DFS has applications in topological sorting, finding connected components, solving mazes, and detecting cycles in graphs.", difficulty: "medium", }, + bfs: { + name: "Breadth-First Search", + key: "bfs", + category: "graph", + subtitle: + "Graph traversal algorithm that explores all neighbors at the current depth before moving deeper", + description: + "Breadth-First Search (BFS) is a graph traversal algorithm that explores all vertices at the present depth level before moving on to vertices at the next depth level. It uses a queue to keep track of the next vertices to visit, ensuring that vertices are visited in order of their distance from the source vertex. BFS is commonly used for finding the shortest path in unweighted graphs, connected components, and solving puzzles with the fewest possible moves.", + difficulty: "medium", + }, + dijkstra: { + name: "Dijkstra's Algorithm", + key: "dijkstra", + category: "graph", + subtitle: + "Shortest path algorithm for weighted graphs with non-negative edge weights", + description: + "Dijkstra's Algorithm is a graph search algorithm that solves the single-source shortest path problem for a graph with non-negative edge weights. It works by maintaining a set of vertices whose shortest distance from the source is already known and repeatedly selecting the vertex with the minimum distance value, updating the distance values of its adjacent vertices. Dijkstra's algorithm is widely used in network routing protocols and as a subroutine in other graph algorithms.", + difficulty: "hard", + }, + topologicalSort: { + name: "Topological Sort", + key: "topologicalSort", + category: "graph", + subtitle: + "Linear ordering of vertices in a directed acyclic graph where directed edges are respected", + description: + "Topological Sort is an algorithm for ordering the vertices of a directed acyclic graph (DAG) such that for every directed edge (u,v), vertex u comes before vertex v in the ordering. This algorithm is essential for scheduling tasks with dependencies, course prerequisites planning, and compilation sequence determination. A graph must have no directed cycles to have a valid topological ordering, making this algorithm a useful tool for cycle detection as well.", + difficulty: "medium", + }, };