diff --git a/src/algorithms/graph/bfs.js b/src/algorithms/graph/bfs.js new file mode 100644 index 0000000..cda76b4 --- /dev/null +++ b/src/algorithms/graph/bfs.js @@ -0,0 +1,108 @@ +/** + * Builds an adjacency list from the edge array. + * @param {Array} nodes - Array of node objects. + * @param {Array} edges - Array of edge objects. + * @returns {Map} - An adjacency list. + */ +function buildAdjList(nodes, edges) { + const adj = new Map(); + nodes.forEach((n) => adj.set(n.id, [])); + edges.forEach((edge) => { + // This assumes a directed graph + if (adj.has(edge.from)) { + adj.get(edge.from).push(edge.to); + } + }); + return adj; +} + +/** + * Generates all steps for a Breadth-First Search traversal. + * @param {Array} nodes - Array of node objects. + * @param {Array} edges - Array of edge objects. + * @param {number} startNodeId - The ID of the node to start from. + * @returns {Array} - An array of step objects for the visualization. + */ +export function bfsSteps(nodes, edges, startNodeId) { + const adj = buildAdjList(nodes, edges); + const steps = []; + const queue = []; + + // 'visited' tracks all nodes that have been *at least enqueued*. + // This prevents adding the same node to the queue multiple times. + const visited = new Set(); + + // 'discoveryOrder' tracks nodes *after* they are processed (dequeued). + // This will control the "green" (visited) state in the UI. + const discoveryOrder = []; + + // --- Initial Step --- + queue.push(startNodeId); + visited.add(startNodeId); // Mark as visited *when enqueuing* + + steps.push({ + type: "enqueue", + node: startNodeId, + log: `Starting BFS. Enqueueing Start Node ${startNodeId}.`, + queueState: [...queue], + visitedSet: new Set(), // Nothing is fully processed yet + order: [], + }); + + // --- Traversal Loop --- + while (queue.length > 0) { + const currentNodeId = queue.shift(); // FIFO: Dequeue from the front + discoveryOrder.push(currentNodeId); + + // --- Dequeue Step --- + steps.push({ + type: "dequeue", + node: currentNodeId, + log: `Dequeuing Node ${currentNodeId} to process.`, + queueState: [...queue], + visitedSet: new Set(discoveryOrder), // Add to processed set + order: [...discoveryOrder], + }); + + const neighbors = adj.get(currentNodeId) || []; + + for (const neighborId of neighbors) { + if (!visited.has(neighborId)) { + // --- Enqueue Neighbor Step --- + visited.add(neighborId); + queue.push(neighborId); + + steps.push({ + type: "enqueue", + node: neighborId, + log: `Found unvisited neighbor ${neighborId}. Enqueueing.`, + queueState: [...queue], + visitedSet: new Set(discoveryOrder), // Processed set is unchanged + order: [...discoveryOrder], + }); + } else { + // --- Skip Neighbor Step --- + steps.push({ + type: "skip", + node: neighborId, + log: `Neighbor ${neighborId} already in queue or processed. Skipping.`, + queueState: [...queue], + visitedSet: new Set(discoveryOrder), + order: [...discoveryOrder], + }); + } + } + } + + // --- Final Step --- + steps.push({ + type: "done", + node: null, + log: "BFS complete. Queue is empty.", + queueState: [], + visitedSet: new Set(discoveryOrder), + order: [...discoveryOrder], + }); + + return steps; +} \ No newline at end of file diff --git a/src/components/graph/BFSVisualizer.jsx b/src/components/graph/BFSVisualizer.jsx new file mode 100644 index 0000000..0e2ca20 --- /dev/null +++ b/src/components/graph/BFSVisualizer.jsx @@ -0,0 +1,306 @@ +import React, { useState, useRef } from "react"; +import { bfsSteps } from "../../algorithms/graph/bfs"; + +export default function BFSVisualizer() { + const svgRef = useRef(null); + const [nodes, setNodes] = useState([]); + const [edges, setEdges] = useState([]); + const [selected, setSelected] = useState({ from: null, to: null }); + const [running, setRunning] = useState(false); + const [status, setStatus] = useState("Double-click to add nodes"); + + // --- BFS State --- + const [stepIndex, setStepIndex] = useState(0); + const [queueState, setQueueState] = useState([]); + const [visitedNodes, setVisitedNodes] = useState(new Set()); + const [discoveryOrder, setDiscoveryOrder] = useState([]); + const [currentNode, setCurrentNode] = useState(null); + + // 🟢 Add node + const addNode = (e) => { + if (running) return; + const rect = svgRef.current.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + const newNode = { + id: nodes.length + 1, + x, + y, + label: `N${nodes.length + 1}`, + }; + setNodes([...nodes, newNode]); + setStatus("Click two nodes to create an edge"); + }; + + // 🟣 Add edges + const handleNodeClick = (id) => { + if (running) return; + if (!selected.from) { + setSelected({ from: id, to: null }); + } else if (selected.from && !selected.to && selected.from !== id) { + // NOTE: This creates a DIRECTED edge (from -> to) + setEdges([ + ...edges, + { + from: selected.from, + to: id, + colorClass: "stroke-gray-500", + }, + ]); + setSelected({ from: null, to: null }); + } + }; + + // 🧩 BFS visualization + const startVisualization = async () => { + if (running || nodes.length === 0) return; + + const startNodeId = parseInt(prompt("Enter Start Node ID (e.g., 1):"), 10); + if (isNaN(startNodeId) || !nodes.find((n) => n.id === startNodeId)) { + setStatus("Invalid start node ID."); + return; + } + + setRunning(true); + resetVisualizationState(); // Clear previous run + + // Call the bfsSteps function + const steps = bfsSteps(nodes, edges, startNodeId); + const interval = 1200; // 1.2 seconds per step + + for (let i = 0; i < steps.length; i++) { + const step = steps[i]; + setStepIndex(i + 1); + setStatus(step.log); + setQueueState(step.queueState); + setVisitedNodes(step.visitedSet); + setDiscoveryOrder(step.order); + setCurrentNode(step.node); // Highlight the node being acted upon + + await new Promise((r) => setTimeout(r, interval)); + } + + setRunning(false); + setStatus("Completed!"); + setCurrentNode(null); + }; + + // 🟠 Resets the visualization, but keeps the graph + const resetVisualizationState = () => { + setRunning(false); + setStatus("Idle. Click 'Start' to run again."); + setQueueState([]); + setVisitedNodes(new Set()); + setDiscoveryOrder([]); + setCurrentNode(null); + setStepIndex(0); + setEdges(edges.map((e) => ({ ...e, colorClass: "stroke-gray-500" }))); + }; + + // 🔴 Resets the entire canvas + const resetGraph = () => { + setNodes([]); + setEdges([]); + setSelected({ from: null, to: null }); + setRunning(false); + setStatus("Double-click to add nodes"); + setQueueState([]); + setVisitedNodes(new Set()); + setDiscoveryOrder([]); + setCurrentNode(null); + setStepIndex(0); + }; + + // 🧩 Render edges + const renderEdges = () => + edges.map((edge, i) => { + const fromNode = nodes.find((n) => n.id === edge.from); + const toNode = nodes.find((n) => n.id === edge.to); + if (!fromNode || !toNode) return null; + + return ( + + + + ); + }); + + // 🧩 Render nodes + const renderNodes = () => + nodes.map((n) => ( + handleNodeClick(n.id)} + className="cursor-pointer" + > + + + {n.label} + + + )); + + return ( +
+ {/* 🧭 Manual / Instructions */} +
+

+ How to Use the BFS Visualizer +

+
    +
  • + Double-click on the canvas to create a node. +
  • +
  • + Click one node and then another to create a directed{" "} + edge. +
  • +
  • + Once your graph is ready, click Start Visualization and enter + a Start Node ID. +
  • +
  • + The algorithm will highlight the current node (yellow), + nodes in queue (blue), and processed nodes (green). +
  • +
  • + The Queue & Discovery Order panels show the algorithm's + state. +
  • +
+
+ + {/* Controls */} +
+ + + +
+ + {/* Status + Step */} +
+

+ Status: {status} +

+

+ Step: {stepIndex} +

+
+ + {/* Layout: Data Structures + Graph */} +
+ {/* Left Data Panel */} +
+ {/* --- QUEUE --- */} +

+ Queue (FIFO) +

+
+ {queueState.map((id, index) => ( +
+ {index === 0 && ( + Front + )} + Node {id} +
+ ))} + {queueState.length === 0 && ( + Queue is empty + )} +
+ + {/* --- DISCOVERY ORDER --- */} +

+ Discovery Order (Processed) +

+
+ {discoveryOrder.map((id) => ( +
+ N{id} +
+ ))} +
+
+ + {/* Right Graph */} +
+ + {/* Arrowhead Definition */} + + + + + + {renderEdges()} + {renderNodes()} + +
+
+
+ ); +} diff --git a/src/pages/graph/BFS.jsx b/src/pages/graph/BFS.jsx new file mode 100644 index 0000000..9138f69 --- /dev/null +++ b/src/pages/graph/BFS.jsx @@ -0,0 +1,15 @@ +import React from "react"; +import BFSVisualizer from "../../components/graph/BFSVisualizer"; + +export default function BFS() { + return ( +
+
+

+ Breadth-First Search (BFS) Visualizer +

+ +
+
+ ); +} diff --git a/src/pages/graph/GraphPage.jsx b/src/pages/graph/GraphPage.jsx index a55958f..fbb98b1 100644 --- a/src/pages/graph/GraphPage.jsx +++ b/src/pages/graph/GraphPage.jsx @@ -4,8 +4,9 @@ import BellmanFord from "./BellmanFord"; import UnionFindPage from "./UnionFind"; import Kruskal from "./Kruskal"; import FloydWarshall from "./FloydWarshall"; -import CycleDetection from "./CycleDetection"; +import CycleDetection from "./CycleDetection"; import DFSTraversal from "./DFSTraversal"; +import BFS from "./BFS"; export default function GraphPage() { const [selectedAlgo, setSelectedAlgo] = useState(""); @@ -48,6 +49,12 @@ export default function GraphPage() { ); + case "bfs": + return ( +
+ +
+ ); default: return (
@@ -91,11 +98,13 @@ export default function GraphPage() { > + - {/* ✅ Added */} + +