diff --git a/package-lock.json b/package-lock.json index 8fd0861..7343cb3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,7 @@ "@types/node": "^24.7.1", "@types/react": "^19.1.16", "@types/react-dom": "^19.1.9", - "@vitejs/plugin-react": "^5.0.4", + "@vitejs/plugin-react": "^5.1.0", "autoprefixer": "^10.4.21", "eslint": "^9.36.0", "eslint-plugin-react-hooks": "^5.2.0", @@ -2424,6 +2424,7 @@ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.0.tgz", "integrity": "sha512-4LuWrg7EKWgQaMJfnN+wcmbAW+VSsCmqGohftWjuct47bv8uE4n/nPpq4XjJPsxgq00GGG5J8dvBczp8uxScew==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.28.4", "@babel/plugin-transform-react-jsx-self": "^7.27.1", diff --git a/package.json b/package.json index 46755e2..8ae4e6b 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@types/node": "^24.7.1", "@types/react": "^19.1.16", "@types/react-dom": "^19.1.9", - "@vitejs/plugin-react": "^5.0.4", + "@vitejs/plugin-react": "^5.1.0", "autoprefixer": "^10.4.21", "eslint": "^9.36.0", "eslint-plugin-react-hooks": "^5.2.0", diff --git a/src/algorithms/graph/topoSortDFS.js b/src/algorithms/graph/topoSortDFS.js new file mode 100644 index 0000000..3d03ce3 --- /dev/null +++ b/src/algorithms/graph/topoSortDFS.js @@ -0,0 +1,87 @@ +export default function runTopologicalSortDFS(nodes, edges) { + const adjList = {}; + const visited = {}; + const topoOrder = []; + const steps = []; + let hasCycle = false; + + // Initialize adjacency list + nodes.forEach(node => { + adjList[node] = []; + visited[node] = 0; // 0 = unvisited, 1 = visiting, 2 = visited + }); + + // Build graph + edges.forEach(({ from, to }) => { + adjList[from].push(to); + }); + + function dfs(node) { + if (hasCycle) return; // stop early if cycle found + visited[node] = 1; + steps.push({ + type: "visit", + node, + status: "visiting", + message: `Visiting ${node}` + }); + + for (const neighbor of adjList[node]) { + if (visited[neighbor] === 0) { + steps.push({ + type: "exploreEdge", + from: node, + to: neighbor, + message: `Exploring edge ${node} → ${neighbor}` + }); + dfs(neighbor); + } else if (visited[neighbor] === 1) { + // Back edge → cycle detected + hasCycle = true; + steps.push({ + type: "cycleDetected", + from: node, + to: neighbor, + message: `Cycle detected via ${node} → ${neighbor}` + }); + } + } + + visited[node] = 2; + topoOrder.push(node); + steps.push({ + type: "addToTopoOrder", + node, + topoOrder: [...topoOrder], + message: `Added ${node} to topo order` + }); + } + + // Run DFS for all unvisited nodes + for (const node of nodes) { + if (visited[node] === 0) { + steps.push({ + type: "startDFS", + node, + message: `Starting DFS from ${node}` + }); + dfs(node); + } + } + + if (!hasCycle) { + topoOrder.reverse(); // reverse for correct topological order + steps.push({ + type: "finalOrder", + topoOrder: [...topoOrder], + message: `Topological sort completed successfully` + }); + } else { + steps.push({ + type: "finalCycle", + message: `Topological sort failed — cycle exists in graph` + }); + } + + return steps; +} diff --git a/src/algorithms/graph/topoSortKahn.js b/src/algorithms/graph/topoSortKahn.js new file mode 100644 index 0000000..91de658 --- /dev/null +++ b/src/algorithms/graph/topoSortKahn.js @@ -0,0 +1,73 @@ +export default function runTopologicalSort(nodes, edges) { + const inDegree = {}; + const adjList = {}; + + // Initialize in-degree and adjacency list + nodes.forEach(node => { + inDegree[node] = 0; + adjList[node] = []; + }); + + // Build graph + edges.forEach(({ from, to }) => { + adjList[from].push(to); + inDegree[to]++; + }); + + const steps = []; + const queue = []; + const topoOrder = []; + + // Enqueue nodes with 0 in-degree + for (const node of nodes) { + if (inDegree[node] === 0) { + queue.push(node); + steps.push({ + type: "enqueue", + node, + reason: "in-degree 0" + }); + } + } + + // Process nodes + while (queue.length > 0) { + const current = queue.shift(); + topoOrder.push(current); + + steps.push({ + type: "visit", + node: current, + topoOrder: [...topoOrder] + }); + + for (const neighbor of adjList[current]) { + inDegree[neighbor]--; + steps.push({ + type: "decrementInDegree", + from: current, + to: neighbor, + newInDegree: inDegree[neighbor] + }); + + if (inDegree[neighbor] === 0) { + queue.push(neighbor); + steps.push({ + type: "enqueue", + node: neighbor, + reason: "in-degree became 0" + }); + } + } + } + + // Check for cycles (if topoOrder doesn't include all nodes) + if (topoOrder.length !== nodes.length) { + steps.push({ + type: "cycleDetected", + remainingNodes: nodes.filter(n => !topoOrder.includes(n)) + }); + } + + return steps; +} diff --git a/src/components/graph/TopoSortDFSVisualizer.jsx b/src/components/graph/TopoSortDFSVisualizer.jsx new file mode 100644 index 0000000..7089612 --- /dev/null +++ b/src/components/graph/TopoSortDFSVisualizer.jsx @@ -0,0 +1,304 @@ +import React, { useState, useEffect, useMemo } from "react"; + +export default function TopoSortGraphDFS({ + nodes = [], + edges = [], + visited = [], + topoOrder = [], + highlight = {}, +}) { + const [positions, setPositions] = useState({}); + + // Arrange nodes in a circle + useEffect(() => { + if (nodes.length === 0) return; + const radius = 180; + const centerX = 300; + const centerY = 250; + const newPositions = {}; + nodes.forEach((node, index) => { + const angle = (2 * Math.PI * index) / nodes.length; + newPositions[node] = { + x: centerX + radius * Math.cos(angle), + y: centerY + radius * Math.sin(angle), + }; + }); + setPositions(newPositions); + }, [nodes]); + + // Dummy layout for idle mode + const dummyGraph = useMemo(() => { + const dummyNodes = Array.from({ length: 6 }).map((_, i) => { + const radius = 180; + const centerX = 300; + const centerY = 250; + const angle = (2 * Math.PI * i) / 6; + return { + id: i, + x: centerX + radius * Math.cos(angle), + y: centerY + radius * Math.sin(angle), + }; + }); + + const dummyEdges = Array.from({ length: 8 }).map(() => { + const u = Math.floor(Math.random() * 6); + const v = (u + 1 + Math.floor(Math.random() * 5)) % 6; + return { u, v }; + }); + + return { dummyNodes, dummyEdges }; + }, []); + + return ( +
+ {/* Graph Area */} +
+ + + + + + + + + + + + + + + {/* Dummy mode */} + {nodes.length === 0 && ( + <> + {dummyGraph.dummyEdges.map((e, i) => { + const u = dummyGraph.dummyNodes[e.u]; + const v = dummyGraph.dummyNodes[e.v]; + return ( + + ); + })} + {dummyGraph.dummyNodes.map((n) => ( + + ))} + + ⚙️ Demo Mode: Waiting for input... + + + + )} + + {/* Render real edges */} + {edges.map((e, idx) => { + const from = positions[e.u]; + const to = positions[e.v]; + if (!from || !to) return null; + + const isHighlight = + highlight?.edge && + highlight.edge.u === e.u && + highlight.edge.v === e.v; + + return ( + + ); + })} + + {/* Render nodes */} + {nodes.map((n) => { + const pos = positions[n]; + if (!pos) return null; + + const isVisited = visited.includes(n); + const isHighlight = highlight?.node === n; + const isOrdered = topoOrder.includes(n); + + let fillColor = "#1f2937"; + if (isVisited) fillColor = "#374151"; + if (isOrdered) fillColor = "#10b981"; + if (isHighlight) fillColor = "#2563eb"; + + return ( + + + + {n} + + + ); + })} + +
+ + {/* Right Info Panel */} +
+

+ 🧭 DFS-Based Topological Sort +

+ + {nodes.length === 0 ? ( +

+ Add nodes and edges to visualize DFS-based topological sorting. +

+ ) : ( + <> + {highlight && highlight.step && ( +
+ {highlight.step === "startDFS" && ( +

+ 🔍 Starting DFS from {highlight.node} +

+ )} + {highlight.step === "visit" && ( +

+ 🌀 Visiting {highlight.node} +

+ )} + {highlight.step === "exploreEdge" && ( +

+ ➡️ Exploring edge {highlight.from} → {highlight.to} +

+ )} + {highlight.step === "cycleDetected" && ( +

+ ⚠️ Cycle detected between {highlight.from} → {highlight.to} +

+ )} + {highlight.step === "addToTopoOrder" && ( +

+ ✅ Added {highlight.node} to topo order +

+ )} + {highlight.step === "finalOrder" && ( +

+ 🏁 Topological Sort Completed +

+ )} +
+ )} + +
+
+

+ Visited: +

+
+ {visited.length === 0 ? ( + None yet + ) : ( + visited.map((n) => ( + + {n} + + )) + )} +
+
+ +
+

+ Topological Order: +

+
+ {topoOrder.length === 0 ? ( + Empty + ) : ( + topoOrder.map((n) => ( + + {n} + + )) + )} +
+
+
+ + )} +
+
+ ); +} diff --git a/src/components/graph/TopoSortKahnVisualizer.jsx b/src/components/graph/TopoSortKahnVisualizer.jsx new file mode 100644 index 0000000..5a827e2 --- /dev/null +++ b/src/components/graph/TopoSortKahnVisualizer.jsx @@ -0,0 +1,286 @@ +import React, { useState, useEffect, useMemo } from "react"; + +export default function TopoSortGraph({ + nodes = [], + edges = [], + processed = [], + queue = [], + highlight = {}, +}) { + const [positions, setPositions] = useState({}); + + // Arrange nodes in a circle + useEffect(() => { + if (nodes.length === 0) return; + const radius = 180; + const centerX = 300; + const centerY = 250; + const newPositions = {}; + nodes.forEach((node, index) => { + const angle = (2 * Math.PI * index) / nodes.length; + newPositions[node] = { + x: centerX + radius * Math.cos(angle), + y: centerY + radius * Math.sin(angle), + }; + }); + setPositions(newPositions); + }, [nodes]); + + // Dummy layout for idle mode + const dummyGraph = useMemo(() => { + const dummyNodes = Array.from({ length: 6 }).map((_, i) => { + const radius = 180; + const centerX = 300; + const centerY = 250; + const angle = (2 * Math.PI * i) / 6; + return { + id: i, + x: centerX + radius * Math.cos(angle), + y: centerY + radius * Math.sin(angle), + }; + }); + + const dummyEdges = Array.from({ length: 8 }).map(() => { + const u = Math.floor(Math.random() * 6); + const v = (u + 1 + Math.floor(Math.random() * 5)) % 6; + return { u, v }; + }); + + return { dummyNodes, dummyEdges }; + }, []); + + return ( +
+ {/* Graph Area */} +
+ + + + + + + + + + + + + + + {/* Dummy mode */} + {nodes.length === 0 && ( + <> + {dummyGraph.dummyEdges.map((e, i) => { + const u = dummyGraph.dummyNodes[e.u]; + const v = dummyGraph.dummyNodes[e.v]; + return ( + + ); + })} + {dummyGraph.dummyNodes.map((n) => ( + + ))} + + ⚙️ Demo Mode: Waiting for input... + + + + )} + + {/* Render real edges */} + {edges.map((e, idx) => { + const from = positions[e.u]; + const to = positions[e.v]; + if (!from || !to) return null; + + const isRemoved = processed.includes(e.u) && processed.includes(e.v); + const isHighlight = + highlight?.edge && highlight.edge.u === e.u && highlight.edge.v === e.v; + + return ( + + ); + })} + + {/* Render nodes */} + {nodes.map((n) => { + const pos = positions[n]; + if (!pos) return null; + + const isInQueue = queue.includes(n); + const isProcessed = processed.includes(n); + const isHighlight = highlight?.node === n; + + let fillColor = "#1f2937"; + let strokeColor = "#9ca3af"; + if (isProcessed) fillColor = "#374151"; + if (isInQueue) strokeColor = "#34d399"; + if (isHighlight) fillColor = "#065f46"; + + return ( + + + + {n} + + + ); + })} + +
+ + {/* Right Info Panel */} +
+

+ 🧮 Kahn’s Algorithm — Topological Sort +

+ + {nodes.length === 0 ? ( +

+ Add nodes and edges to visualize topological sorting. +

+ ) : ( + <> + {highlight && highlight.step && ( +
+ {highlight.step === "enqueue" && ( +

+ ➕ Added {highlight.node} to queue (in-degree 0) +

+ )} + {highlight.step === "process" && ( +

+ ⚙️ Processing {highlight.node}, removing outgoing edges +

+ )} + {highlight.step === "done" && ( +

+ ✅ All nodes processed. Topological order found! +

+ )} +
+ )} + +
+
+

Queue:

+
+ {queue.length === 0 ? ( + Empty + ) : ( + queue.map((n) => ( + + {n} + + )) + )} +
+
+ +
+

Processed:

+
+ {processed.length === 0 ? ( + None yet + ) : ( + processed.map((n) => ( + + {n} + + )) + )} +
+
+
+ + )} +
+
+ ); +} diff --git a/src/pages/graph/GraphPage.jsx b/src/pages/graph/GraphPage.jsx index fbb98b1..f8a09a9 100644 --- a/src/pages/graph/GraphPage.jsx +++ b/src/pages/graph/GraphPage.jsx @@ -7,6 +7,8 @@ import FloydWarshall from "./FloydWarshall"; import CycleDetection from "./CycleDetection"; import DFSTraversal from "./DFSTraversal"; import BFS from "./BFS"; +import KahnTopologicalSort from "./TopoSortKahn"; +import DFSTopologicalSort from "./TopoSortDFS"; export default function GraphPage() { const [selectedAlgo, setSelectedAlgo] = useState(""); @@ -14,47 +16,23 @@ export default function GraphPage() { const renderAlgorithm = () => { switch (selectedAlgo) { case "bellman-ford": - return ( -
- -
- ); + return ; case "union-find": - return ( -
- -
- ); + return ; case "kruskal": - return ( -
- -
- ); + return ; case "floyd-warshall": - return ( -
- -
- ); - case "cycle-detection": // ✅ Added - return ( -
- -
- ); + return ; + case "cycle-detection": + return ; case "dfs-traversal": - return ( -
- -
- ); + return ; case "bfs": - return ( -
- -
- ); + return ; + case "topo-kahn": + return ; + case "topo-dfs": + return ; default: return (
@@ -99,12 +77,15 @@ export default function GraphPage() { + - - + + + + + +
+ + {nodes.length > 0 && ( +
+
+ {nodes.map((n, idx) => ( +
+ {n} +
+ ))} +
+ +
+ + + +
+ +
+ + +
+
+ )} + + + + ); +} diff --git a/src/pages/graph/TopoSortKahn.jsx b/src/pages/graph/TopoSortKahn.jsx new file mode 100644 index 0000000..2d3a31a --- /dev/null +++ b/src/pages/graph/TopoSortKahn.jsx @@ -0,0 +1,286 @@ +import React, { useState } from "react"; +import { Toaster, toast } from "react-hot-toast"; +import TopoGraph from "../../components/graph/TopoSortKahnVisualizer"; + +// Topological Sort generator (Kahn's Algorithm) +function* topologicalSort(nodes, edges) { + const inDegree = {}; + const adjList = {}; + + // Initialize graph + nodes.forEach((n) => { + inDegree[n] = 0; + adjList[n] = []; + }); + + // Build graph + for (let { u, v } of edges) { + adjList[u].push(v); + inDegree[v]++; + } + + const queue = nodes.filter((n) => inDegree[n] === 0); + const topoOrder = []; + + // Initial enqueue state + yield { + type: "init", + queue: [...queue], + inDegree: { ...inDegree }, + topoOrder: [], + }; + + while (queue.length > 0) { + const current = queue.shift(); + topoOrder.push(current); + + yield { + type: "visit", + node: current, + queue: [...queue], + inDegree: { ...inDegree }, + topoOrder: [...topoOrder], + }; + + for (const neighbor of adjList[current]) { + inDegree[neighbor]--; + yield { + type: "decrement", + from: current, + to: neighbor, + inDegree: { ...inDegree }, + }; + if (inDegree[neighbor] === 0) { + queue.push(neighbor); + yield { + type: "enqueue", + node: neighbor, + queue: [...queue], + inDegree: { ...inDegree }, + }; + } + } + } + + if (topoOrder.length !== nodes.length) { + yield { + type: "cycle", + message: "Graph has a cycle. Topological sort not possible!", + }; + return; + } + + yield { type: "done", topoOrder }; +} + +// Main Component +export default function KahnTopologicalSort() { + const [numNodes, setNumNodes] = useState(0); + const [nodes, setNodes] = useState([]); + const [edges, setEdges] = useState([]); + const [highlight, setHighlight] = useState(null); + const [isRunning, setIsRunning] = useState(false); + const [topoOrder, setTopoOrder] = useState([]); + const [inDegree, setInDegree] = useState({}); + const [queue, setQueue] = useState([]); + + // Edge input state + const [fromNode, setFromNode] = useState(""); + const [toNode, setToNode] = useState(""); + + // Generate nodes + const handleGenerateNodes = () => { + if (numNodes < 2) { + toast.error("Please select at least 2 nodes!"); + return; + } + const generated = Array.from({ length: numNodes }, (_, i) => + String.fromCharCode(65 + i) + ); + setNodes(generated); + setEdges([]); + setTopoOrder([]); + setHighlight(null); + }; + + // Add edge + const handleAddEdge = () => { + if (!fromNode || !toNode) { + toast.error("Please fill both node fields!"); + return; + } + if (fromNode === toNode) { + toast.error("No self-loops allowed!"); + return; + } + const exists = edges.find((e) => e.u === fromNode && e.v === toNode); + if (exists) { + toast.error("Edge already exists!"); + return; + } + setEdges([...edges, { u: fromNode, v: toNode }]); + setFromNode(""); + setToNode(""); + }; + + // Run Topological Sort visualization + const handleStart = async () => { + if (isRunning) return; + if (nodes.length === 0 || edges.length === 0) { + toast.error("Please generate nodes and edges first!"); + return; + } + + setIsRunning(true); + const gen = topologicalSort(nodes, edges); + for (let step of gen) { + setHighlight(step); + if (step.inDegree) setInDegree(step.inDegree); + if (step.queue) setQueue(step.queue); + if (step.topoOrder) setTopoOrder(step.topoOrder); + + await new Promise((r) => setTimeout(r, 800)); + + if (step.type === "cycle") { + toast.error(step.message); + break; + } + } + setIsRunning(false); + }; + + const handleReset = () => { + setEdges([]); + setHighlight(null); + setTopoOrder([]); + setInDegree({}); + setQueue([]); + }; + + // Load example DAG + const handleLoadExample = () => { + const exampleNodes = ["A", "B", "C", "D", "E", "F"]; + const exampleEdges = [ + { u: "A", v: "C" }, + { u: "B", v: "C" }, + { u: "B", v: "D" }, + { u: "C", v: "E" }, + { u: "D", v: "F" }, + { u: "E", v: "F" }, + ]; + setNodes(exampleNodes); + setEdges(exampleEdges); + setTopoOrder([]); + setHighlight(null); + setNumNodes(6); + }; + + return ( +
+ +

+ Topological Sort Visualizer +

+ +
+ setNumNodes(parseInt(e.target.value))} + className="p-2 rounded-lg text-gray-900 bg-gray-100" + /> + + +
+ + {nodes.length > 0 && ( +
+
+ {nodes.map((n, idx) => ( +
+ {n} +
+ ))} +
+ +
+ + + +
+ +
+ + +
+
+ )} + + +
+ ); +}