+ );
+};
+
+const Content = () => {
+ const { theme } = useTheme();
+
+ const paragraphs = [
+ `An adjacency list represents a graph as a collection of per-vertex neighbor lists: one entry per vertex, holding only the vertices it's actually connected to. Instead of a full V×V grid of mostly zeros, each vertex stores exactly as many entries as it has edges — nothing more.`,
+ `Adding an edge (u, v) appends v to u's list. For an undirected graph, u is also appended to v's list, since the edge goes both ways; for a directed graph, only u's list gets the new entry. Checking whether an edge exists means scanning through one vertex's list looking for the target — proportional to that vertex's degree (its number of neighbors), not the whole graph.`,
+ `This is the mirror image of an adjacency matrix's tradeoffs. A matrix spends O(V²) space no matter what, in exchange for O(1) edge-existence checks. A list spends space proportional to the actual number of edges — O(V + E) — but checking a specific edge now costs O(degree) instead of O(1). For the vast majority of real-world graphs, which are sparse (E is much smaller than V²), that tradeoff strongly favors the list.`,
+ `Adjacency lists are the default choice for most graph algorithms — BFS, DFS, Dijkstra's algorithm, and topological sort all need to repeatedly ask "what are this vertex's neighbors?", which a list answers by directly returning exactly the relevant entries, without wasting time scanning past vertices that aren't connected at all.`,
+ ];
+
+ const algorithm = [
+ { points: "Create an empty list (or map) for every vertex" },
+ { points: "For every edge (u, v) with weight w: append (v, w) to u's list" },
+ { points: "If the graph is undirected, also append (u, w) to v's list — the same edge is recorded from both directions" },
+ { points: "To check if an edge exists between two vertices, scan the source vertex's list for the target" },
+ ];
+
+ const complexity = [
+ { points: "Space Complexity: O(V + E) — proportional to the actual number of vertices and edges, not V²." },
+ { points: "Check if edge (u, v) exists: O(degree(u)) — scan u's list, which is only as long as u's actual neighbor count." },
+ { points: "Iterate over a vertex's neighbors: O(degree(u)) — the list already holds exactly the relevant entries." },
+ { points: "Add an edge: O(1) — appending to a list." },
+ ];
+
+ return (
+
+
+
+
+
+
+ {/* What is it */}
+
+
+
+ What is an Adjacency List?
+
+
+
+ {paragraphs[0]}
+
+
+
+
+ {/* How it works */}
+
+
+
+ How Does It Work?
+
+
+
+ {paragraphs[1]}
+
+
+
+
+ {paragraphs[2]}
+
+
+
+
+ The same triangle graph — every vertex's list holds only its actual neighbors
+
+
+
+ Edge exists
+
+
+
+ Most recently set edge
+
+
+
+ Selected cell (click any cell)
+
+
+
+
+
+ );
+};
+
+export default AdjacencyMatrixVisualizer;
diff --git a/app/visualizer/graph/representation/adjacency-matrix/code.js b/app/visualizer/graph/representation/adjacency-matrix/code.js
new file mode 100755
index 0000000..51453cf
--- /dev/null
+++ b/app/visualizer/graph/representation/adjacency-matrix/code.js
@@ -0,0 +1,140 @@
+const codeExamples = {
+ javascript: `class GraphMatrix {
+ constructor(vertices) {
+ this.vertices = vertices; // e.g. ["A", "B", "C"]
+ this.index = Object.fromEntries(vertices.map((v, i) => [v, i]));
+ this.matrix = Array.from({ length: vertices.length }, () => Array(vertices.length).fill(0));
+ }
+
+ addEdge(from, to, weight = 1, directed = false) {
+ const i = this.index[from];
+ const j = this.index[to];
+ this.matrix[i][j] = weight;
+ if (!directed) this.matrix[j][i] = weight; // mirror the edge both ways
+ }
+
+ hasEdge(from, to) {
+ return this.matrix[this.index[from]][this.index[to]] !== 0; // O(1) lookup
+ }
+
+ neighbors(vertex) {
+ const i = this.index[vertex];
+ return this.vertices.filter((_, j) => this.matrix[i][j] !== 0); // O(V) scan
+ }
+}
+
+// Usage example
+const g = new GraphMatrix(["A", "B", "C", "D"]);
+g.addEdge("A", "B");
+g.addEdge("A", "C");
+g.hasEdge("A", "B"); // true
+g.neighbors("A"); // ["B", "C"]`,
+
+ python: `class GraphMatrix:
+ def __init__(self, vertices):
+ self.vertices = vertices # e.g. ["A", "B", "C"]
+ self.index = {v: i for i, v in enumerate(vertices)}
+ n = len(vertices)
+ self.matrix = [[0] * n for _ in range(n)]
+
+ def add_edge(self, frm, to, weight=1, directed=False):
+ i, j = self.index[frm], self.index[to]
+ self.matrix[i][j] = weight
+ if not directed:
+ self.matrix[j][i] = weight # mirror the edge both ways
+
+ def has_edge(self, frm, to):
+ return self.matrix[self.index[frm]][self.index[to]] != 0 # O(1) lookup
+
+ def neighbors(self, vertex):
+ i = self.index[vertex]
+ return [v for j, v in enumerate(self.vertices) if self.matrix[i][j] != 0] # O(V) scan
+
+# Usage example
+g = GraphMatrix(["A", "B", "C", "D"])
+g.add_edge("A", "B")
+g.add_edge("A", "C")
+g.has_edge("A", "B") # True
+g.neighbors("A") # ["B", "C"]`,
+
+ c: `#include
+#include
+
+#define MAX_V 10
+
+int matrix[MAX_V][MAX_V];
+char vertices[MAX_V];
+int vertexCount = 0;
+
+int indexOf(char v) {
+ for (int i = 0; i < vertexCount; i++) if (vertices[i] == v) return i;
+ return -1;
+}
+
+void addVertex(char v) {
+ vertices[vertexCount++] = v;
+}
+
+void addEdge(char from, char to, int weight, int directed) {
+ int i = indexOf(from), j = indexOf(to);
+ matrix[i][j] = weight;
+ if (!directed) matrix[j][i] = weight; // mirror the edge both ways
+}
+
+int hasEdge(char from, char to) {
+ return matrix[indexOf(from)][indexOf(to)] != 0; // O(1) lookup
+}
+
+int main() {
+ addVertex('A'); addVertex('B'); addVertex('C');
+ addEdge('A', 'B', 1, 0);
+ addEdge('A', 'C', 1, 0);
+ printf("A-B edge: %d\\n", hasEdge('A', 'B'));
+ return 0;
+}`,
+
+ java: `import java.util.*;
+
+public class GraphMatrix {
+ List vertices = new ArrayList<>();
+ Map index = new HashMap<>();
+ int[][] matrix;
+
+ GraphMatrix(char[] vertexList) {
+ for (char v : vertexList) {
+ index.put(v, vertices.size());
+ vertices.add(v);
+ }
+ matrix = new int[vertices.size()][vertices.size()];
+ }
+
+ void addEdge(char from, char to, int weight, boolean directed) {
+ int i = index.get(from), j = index.get(to);
+ matrix[i][j] = weight;
+ if (!directed) matrix[j][i] = weight; // mirror the edge both ways
+ }
+
+ boolean hasEdge(char from, char to) {
+ return matrix[index.get(from)][index.get(to)] != 0; // O(1) lookup
+ }
+
+ List neighbors(char vertex) {
+ List result = new ArrayList<>();
+ int i = index.get(vertex);
+ for (int j = 0; j < vertices.size(); j++) { // O(V) scan
+ if (matrix[i][j] != 0) result.add(vertices.get(j));
+ }
+ return result;
+ }
+
+ public static void main(String[] args) {
+ GraphMatrix g = new GraphMatrix(new char[]{'A', 'B', 'C', 'D'});
+ g.addEdge('A', 'B', 1, false);
+ g.addEdge('A', 'C', 1, false);
+ System.out.println("A-B edge: " + g.hasEdge('A', 'B'));
+ System.out.println("A's neighbors: " + g.neighbors('A'));
+ }
+}`,
+};
+
+export default codeExamples;
diff --git a/app/visualizer/graph/representation/adjacency-matrix/content.jsx b/app/visualizer/graph/representation/adjacency-matrix/content.jsx
new file mode 100755
index 0000000..49bf41e
--- /dev/null
+++ b/app/visualizer/graph/representation/adjacency-matrix/content.jsx
@@ -0,0 +1,223 @@
+"use client";
+import ComplexityGraph from "@/app/components/ui/graph";
+import { useTheme } from "@/app/contexts/ThemeContext";
+import DailyDSAEmbed from "@/app/components/ui/DailyDSAEmbed";
+import NewsletterEmbed from "@/app/components/ui/NewsletterEmbed";
+import InContentAd from "@/app/components/ads/InContentAd";
+import { motion } from "framer-motion";
+
+const WalkthroughDiagram = () => {
+ const vertices = [
+ { id: "A", x: 110, y: 30 },
+ { id: "B", x: 40, y: 90 },
+ { id: "C", x: 180, y: 90 },
+ ];
+ const edges = [
+ ["A", "B"],
+ ["A", "C"],
+ ["B", "C"],
+ ];
+ const byId = Object.fromEntries(vertices.map((v) => [v.id, v]));
+ const matrix = {
+ A: { A: 0, B: 1, C: 1 },
+ B: { A: 1, B: 0, C: 1 },
+ C: { A: 1, B: 1, C: 0 },
+ };
+
+ return (
+
+
+
+
+
+
+
+ {["A", "B", "C"].map((c) => (
+
{c}
+ ))}
+
+
+
+ {["A", "B", "C"].map((r) => (
+
+
{r}
+ {["A", "B", "C"].map((c) => (
+
+ {matrix[r][c]}
+
+ ))}
+
+ ))}
+
+
+
+ );
+};
+
+const Content = () => {
+ const { theme } = useTheme();
+
+ const paragraphs = [
+ `An adjacency matrix represents a graph as a 2D grid: a V×V table where V is the number of vertices. The cell at row i, column j holds a nonzero value (often just 1, or an edge's weight) if there's an edge from vertex i to vertex j, and 0 otherwise. For an undirected graph, an edge sets both cell (i, j) and cell (j, i), so the matrix is symmetric across its diagonal; for a directed graph, only the one cell matching the edge's direction is set.`,
+ `The main advantage is speed for a very specific question: "is there an edge between these two vertices?" Checking cell (i, j) is a single array lookup — O(1) — regardless of how many edges the graph has. Iterating over all of a vertex's neighbors, though, means scanning an entire row, which costs O(V) even if that vertex only has one or two actual edges.`,
+ `That scanning cost is what makes adjacency matrices a poor fit for sparse graphs — graphs where the number of edges is much smaller than V². A social network with millions of users but only a few hundred friends each would waste almost the entire matrix on zeros, both in memory (O(V²) regardless of edge count) and in wasted iteration time. Adjacency lists exist specifically to fix this by only storing the edges that actually exist.`,
+ `Adjacency matrices earn their keep on dense graphs (where edges approach V²), in algorithms that need fast edge-existence checks (like Floyd-Warshall's all-pairs shortest paths, which is naturally matrix-based), and in small, fixed-size graphs where the O(V²) memory cost is negligible and the O(1) lookup is worth it.`,
+ ];
+
+ const algorithm = [
+ { points: "Create a V×V grid, initialized to all zeros" },
+ { points: "For every edge (u, v) with weight w: set matrix[u][v] = w" },
+ { points: "If the graph is undirected, also set matrix[v][u] = w — the same edge is recorded from both directions" },
+ { points: "To check if an edge exists between two vertices, read matrix[u][v] directly" },
+ ];
+
+ const complexity = [
+ { points: "Space Complexity: O(V²) — regardless of how many edges actually exist." },
+ { points: "Check if edge (u, v) exists: O(1) — a single cell lookup." },
+ { points: "Iterate over a vertex's neighbors: O(V) — the entire row must be scanned." },
+ { points: "Add or remove an edge: O(1) — updating a single (or mirrored pair of) cells." },
+ ];
+
+ return (
+
+
+
+
+
+
+ {/* What is it */}
+
+
+
+ What is an Adjacency Matrix?
+
+
+
+ {paragraphs[0]}
+
+
+
+
+ {/* How it works */}
+
+
+
+ How Does It Work?
+
+
+
+ {paragraphs[1]}
+
+
+
+
+ {paragraphs[2]}
+
+
+
+
+ An undirected triangle graph — its matrix is symmetric, and the diagonal stays 0 (no self-loops)
+
+
+ );
+};
+
+export default BfsVisualizer;
diff --git a/app/visualizer/graph/traversal/bfs/code.js b/app/visualizer/graph/traversal/bfs/code.js
new file mode 100755
index 0000000..9095d0f
--- /dev/null
+++ b/app/visualizer/graph/traversal/bfs/code.js
@@ -0,0 +1,133 @@
+const codeExamples = {
+ javascript: `// A queue (FIFO) is what makes this "breadth-first": every same-distance
+// vertex is dequeued before any farther vertex is even discovered.
+function bfs(adjList, start) {
+ const visited = new Set([start]);
+ const queue = [start];
+ const order = [];
+
+ while (queue.length > 0) {
+ const current = queue.shift();
+ order.push(current);
+
+ for (const neighbor of adjList[current] || []) {
+ if (!visited.has(neighbor)) {
+ visited.add(neighbor); // mark visited when enqueued, not when dequeued
+ queue.push(neighbor);
+ }
+ }
+ }
+
+ return order;
+}
+
+// Usage example
+const adjList = { A: ["B", "C"], B: ["A", "D"], C: ["A"], D: ["B"] };
+bfs(adjList, "A"); // ["A", "B", "C", "D"]`,
+
+ python: `from collections import deque
+
+# A queue (FIFO) is what makes this "breadth-first": every same-distance
+# vertex is dequeued before any farther vertex is even discovered.
+def bfs(adj_list, start):
+ visited = {start}
+ queue = deque([start])
+ order = []
+
+ while queue:
+ current = queue.popleft()
+ order.append(current)
+
+ for neighbor in adj_list.get(current, []):
+ if neighbor not in visited:
+ visited.add(neighbor) # mark visited when enqueued, not when dequeued
+ queue.append(neighbor)
+
+ return order
+
+# Usage example
+adj_list = {"A": ["B", "C"], "B": ["A", "D"], "C": ["A"], "D": ["B"]}
+bfs(adj_list, "A") # ["A", "B", "C", "D"]`,
+
+ c: `#include
+
+#define MAX_V 26
+
+char adjList[MAX_V][MAX_V];
+int adjCount[MAX_V] = {0};
+int visited[MAX_V] = {0};
+
+// A queue (FIFO) is what makes this "breadth-first": every same-distance
+// vertex is dequeued before any farther vertex is even discovered.
+void bfs(char start) {
+ char queue[MAX_V];
+ int front = 0, back = 0;
+
+ queue[back++] = start;
+ visited[start - 'A'] = 1;
+
+ while (front < back) {
+ char current = queue[front++];
+ printf("%c ", current);
+
+ for (int i = 0; i < adjCount[current - 'A']; i++) {
+ char neighbor = adjList[current - 'A'][i];
+ if (!visited[neighbor - 'A']) {
+ visited[neighbor - 'A'] = 1; // mark visited when enqueued
+ queue[back++] = neighbor;
+ }
+ }
+ }
+}
+
+int main() {
+ // Example: A-B, A-C, B-D (undirected)
+ adjList[0][0] = 'B'; adjList[0][1] = 'C'; adjCount[0] = 2; // A
+ adjList[1][0] = 'A'; adjList[1][1] = 'D'; adjCount[1] = 2; // B
+ adjList[2][0] = 'A'; adjCount[2] = 1; // C
+ adjList[3][0] = 'B'; adjCount[3] = 1; // D
+
+ bfs('A'); // A B C D
+ return 0;
+}`,
+
+ java: `import java.util.*;
+
+public class BFS {
+ // A queue (FIFO) is what makes this "breadth-first": every same-distance
+ // vertex is dequeued before any farther vertex is even discovered.
+ static List bfs(Map> adjList, char start) {
+ Set visited = new HashSet<>();
+ Queue queue = new LinkedList<>();
+ List order = new ArrayList<>();
+
+ visited.add(start);
+ queue.add(start);
+
+ while (!queue.isEmpty()) {
+ char current = queue.poll();
+ order.add(current);
+
+ for (char neighbor : adjList.getOrDefault(current, List.of())) {
+ if (!visited.contains(neighbor)) {
+ visited.add(neighbor); // mark visited when enqueued, not when dequeued
+ queue.add(neighbor);
+ }
+ }
+ }
+ return order;
+ }
+
+ public static void main(String[] args) {
+ Map> adjList = new HashMap<>();
+ adjList.put('A', List.of('B', 'C'));
+ adjList.put('B', List.of('A', 'D'));
+ adjList.put('C', List.of('A'));
+ adjList.put('D', List.of('B'));
+
+ System.out.println(bfs(adjList, 'A')); // [A, B, C, D]
+ }
+}`,
+};
+
+export default codeExamples;
diff --git a/app/visualizer/graph/traversal/bfs/content.jsx b/app/visualizer/graph/traversal/bfs/content.jsx
new file mode 100755
index 0000000..b6ea32f
--- /dev/null
+++ b/app/visualizer/graph/traversal/bfs/content.jsx
@@ -0,0 +1,207 @@
+"use client";
+import ComplexityGraph from "@/app/components/ui/graph";
+import { useTheme } from "@/app/contexts/ThemeContext";
+import DailyDSAEmbed from "@/app/components/ui/DailyDSAEmbed";
+import NewsletterEmbed from "@/app/components/ui/NewsletterEmbed";
+import InContentAd from "@/app/components/ads/InContentAd";
+import { motion } from "framer-motion";
+
+const WalkthroughDiagram = () => {
+ const vertices = [
+ { id: "A", x: 110, y: 20, ring: 0 },
+ { id: "B", x: 50, y: 70, ring: 1 },
+ { id: "C", x: 170, y: 70, ring: 1 },
+ { id: "D", x: 20, y: 125, ring: 2 },
+ { id: "E", x: 90, y: 125, ring: 2 },
+ ];
+ const edges = [
+ ["A", "B"],
+ ["A", "C"],
+ ["B", "D"],
+ ["B", "E"],
+ ];
+ const byId = Object.fromEntries(vertices.map((v) => [v.id, v]));
+ const ringColors = ["#f59e0b", "#3b82f6", "#10b981"];
+
+ return (
+
+ );
+};
+
+const Content = () => {
+ const { theme } = useTheme();
+
+ const paragraphs = [
+ `Breadth-First Search explores a graph outward from a starting vertex one "ring" of distance at a time: first the start vertex itself, then all of its direct neighbors, then all of their unvisited neighbors, and so on. The result is that every vertex gets visited in order of its distance (in number of edges) from the start — nothing two hops away is visited before everything one hop away.`,
+ `That ordering comes entirely from using a queue (first-in, first-out) instead of a stack. The start vertex is enqueued first. Then, repeatedly: dequeue a vertex, mark it visited, and enqueue any of its neighbors that haven't been visited yet. Because a queue preserves arrival order, every vertex discovered while processing distance-d vertices gets enqueued *after* all the other distance-d vertices already in the queue — which guarantees they'll all be dequeued (and their own neighbors discovered) before any distance-(d+1) vertex is dequeued.`,
+ `A visited set is essential alongside the queue: without one, a vertex reachable from multiple directions would be enqueued (and processed) more than once, and a graph with a cycle could loop forever. Marking a vertex visited *at the moment it's enqueued* — not when it's dequeued — is what prevents the same vertex from being added to the queue twice while it's still waiting its turn.`,
+ `BFS is the standard choice whenever "shortest path in terms of number of edges" is what's needed — it's how the "N degrees of separation" between two people in a social graph is found, how the fewest moves to solve a sliding puzzle is computed, and how the shortest route in an unweighted road network is found. For weighted graphs where edges have different costs, Dijkstra's algorithm generalizes this same expanding-frontier idea.`,
+ ];
+
+ const algorithm = [
+ { points: "Enqueue the start vertex and mark it visited" },
+ {
+ points: "While the queue isn't empty, repeat:",
+ subpoints: [
+ "Dequeue a vertex and process it (this is the visit order)",
+ "For each of its neighbors, if not already visited: mark it visited and enqueue it",
+ ],
+ },
+ { points: "Stop when the queue is empty — every vertex reachable from the start has been visited" },
+ ];
+
+ const complexity = [
+ { points: "Time Complexity: O(V + E) — every vertex is dequeued once and every edge is examined once across the whole run." },
+ { points: "Space Complexity: O(V) — for the queue and the visited set in the worst case." },
+ ];
+
+ return (
+
+
+
+
+
+
+ {/* What is it */}
+
+
+
+ What is Breadth-First Search?
+
+
+
+ {paragraphs[0]}
+
+
+
+
+ {/* How it works */}
+
+
+
+ How Does It Work?
+
+
+
+ {paragraphs[1]}
+
+
+
+
+ {paragraphs[2]}
+
+
+
+
+ BFS from A visits every vertex in order of distance — amber, then blue, then emerald
+
+ n}
+ averageCase={(n) => n + n}
+ worstCase={(n) => n + n}
+ maxN={25}
+ />
+
+
+
+
+
+ {/* Additional Info */}
+
+
+
+
+ {paragraphs[3]}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Content;
diff --git a/app/visualizer/graph/traversal/bfs/page.jsx b/app/visualizer/graph/traversal/bfs/page.jsx
new file mode 100755
index 0000000..7ef9876
--- /dev/null
+++ b/app/visualizer/graph/traversal/bfs/page.jsx
@@ -0,0 +1,114 @@
+import Animation from "@/app/visualizer/graph/traversal/bfs/animation";
+import Navbar from "@/app/components/navbarinner";
+import ModuleHeader from "@/app/components/modules/Header";
+import Footer from "@/app/components/footer";
+import BackToTop from "@/app/components/ui/backtotop";
+import ExploreOther from "@/app/components/ui/exploreOther";
+import CodeBlock from "@/app/components/modules/CodeBlock";
+import codeExamples from "./code";
+import Quiz from "@/app/visualizer/graph/traversal/bfs/quiz";
+import Content from "@/app/visualizer/graph/traversal/bfs/content";
+import ModuleCard from "@/app/components/ui/ModuleCard";
+import { MODULE_MAPS } from "@/lib/modulesMap";
+
+export const metadata = {
+ title: "Breadth-First Search (BFS) | Animation and Explanation",
+ description:
+ "Learn how Breadth-First Search explores a graph outward one distance-ring at a time using a queue, with an interactive visualizer, code examples in JavaScript, C, Python, and Java, and a quiz.",
+ keywords: [
+ "Breadth-First Search",
+ "BFS",
+ "Breadth-First Search Algorithm",
+ "BFS Algorithm",
+ "Breadth-First Search Visualization",
+ "BFS Visualization",
+ "Graph Traversal",
+ "Shortest Path Unweighted Graph",
+ "Breadth-First Search in JavaScript",
+ "BFS in JavaScript",
+ "Breadth-First Search in C",
+ "BFS in C",
+ "Breadth-First Search in Python",
+ "BFS in Python",
+ "Breadth-First Search in Java",
+ "BFS in Java",
+ "Graph Algorithms",
+ "DSA Graphs",
+ "Learn Graphs",
+ "Graph Quiz",
+ ],
+ robots: "index, follow",
+ openGraph: {
+ images: [
+ {
+ url: "/og.png",
+ width: 1200,
+ height: 630,
+ alt: "Breadth-First Search Visualization",
+ },
+ ],
+ },
+};
+
+export default function Page() {
+ const paths = [
+ { name: "Home", href: "/" },
+ { name: "Visualizer", href: "/visualizer" },
+ { name: "Graph : Breadth-First Search", href: "" },
+ ];
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Test Your Knowledge before moving forward!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/app/visualizer/graph/traversal/bfs/quiz.jsx b/app/visualizer/graph/traversal/bfs/quiz.jsx
new file mode 100755
index 0000000..4ea2f2b
--- /dev/null
+++ b/app/visualizer/graph/traversal/bfs/quiz.jsx
@@ -0,0 +1,379 @@
+"use client";
+import React, { useState } from 'react';
+import { FaCheck, FaTimes, FaArrowRight, FaArrowLeft, FaInfoCircle, FaRedo, FaTrophy, FaStar, FaAward } from 'react-icons/fa';
+import { motion, AnimatePresence } from 'framer-motion';
+
+const BfsQuiz = () => {
+ const questions = [
+ {
+ question: "In what order does BFS visit a graph's vertices from a start vertex?",
+ options: [
+ "In alphabetical order",
+ "In order of distance (number of edges) from the start vertex",
+ "In a random order",
+ "In order of the vertices' values, smallest first"
+ ],
+ correctAnswer: 1,
+ explanation: "BFS explores outward in expanding rings, visiting everything one hop away before anything two hops away, and so on."
+ },
+ {
+ question: "What data structure does BFS use to control visit order, and why?",
+ options: [
+ "A stack, because it processes the most recently discovered vertex first",
+ "A queue, because first-in-first-out order guarantees closer vertices are processed before farther ones",
+ "A priority queue sorted by vertex label",
+ "No auxiliary structure is needed"
+ ],
+ correctAnswer: 1,
+ explanation: "A queue's FIFO order is exactly what ensures every same-distance vertex is dequeued before any farther vertex — that's the mechanism behind BFS's ring-by-ring exploration."
+ },
+ {
+ question: "Why must a vertex be marked visited at the moment it's enqueued, not when it's dequeued?",
+ options: [
+ "It doesn't matter which point it's marked at",
+ "Marking it late could let the same vertex be enqueued multiple times before it's ever processed, wasting work or causing incorrect results",
+ "Marking early makes the algorithm run in O(1) time",
+ "Vertices are never actually marked visited in BFS"
+ ],
+ correctAnswer: 1,
+ explanation: "If a vertex isn't marked until dequeued, other vertices could discover and enqueue it again while it's still waiting in the queue, leading to duplicate processing."
+ },
+ {
+ question: "What is the time complexity of BFS on a graph with V vertices and E edges?",
+ options: ["O(V)", "O(E)", "O(V + E)", "O(V × E)"],
+ correctAnswer: 2,
+ explanation: "Every vertex is dequeued exactly once, and every edge is examined exactly once (twice for undirected graphs, still a constant factor), giving O(V + E) overall."
+ },
+ {
+ question: "What is BFS typically used for that DFS is not naturally suited to?",
+ options: [
+ "Detecting cycles in a graph",
+ "Finding the shortest path in terms of number of edges, in an unweighted graph",
+ "Topological sorting",
+ "Finding connected components"
+ ],
+ correctAnswer: 1,
+ explanation: "Because BFS visits vertices in strict order of distance from the start, the first time it reaches any vertex is guaranteed to be via a shortest unweighted path — DFS gives no such guarantee."
+ }
+ ];
+
+ const [currentQuestion, setCurrentQuestion] = useState(0);
+ const [selectedAnswer, setSelectedAnswer] = useState(null);
+ const [score, setScore] = useState(0);
+ const [showResult, setShowResult] = useState(false);
+ const [quizCompleted, setQuizCompleted] = useState(false);
+ const [answers, setAnswers] = useState(Array(questions.length).fill(null));
+ const [showIntro, setShowIntro] = useState(true);
+ const [showSuccessAnimation, setShowSuccessAnimation] = useState(false);
+
+ const handleAnswerSelect = (optionIndex) => {
+ setSelectedAnswer(optionIndex);
+ const newAnswers = [...answers];
+ newAnswers[currentQuestion] = optionIndex;
+ setAnswers(newAnswers);
+ };
+
+ const handleNextQuestion = () => {
+ if (selectedAnswer === null) return;
+
+ if (selectedAnswer === questions[currentQuestion].correctAnswer) {
+ setScore(score + 1);
+ }
+ if (currentQuestion < questions.length - 1) {
+ setCurrentQuestion(currentQuestion + 1);
+ setSelectedAnswer(null);
+ } else {
+ setShowSuccessAnimation(true);
+ setTimeout(() => {
+ setShowSuccessAnimation(false);
+ setQuizCompleted(true);
+ setShowResult(true);
+ }, 2000);
+ }
+ };
+
+ const handlePreviousQuestion = () => {
+ setCurrentQuestion(currentQuestion - 1);
+ setSelectedAnswer(answers[currentQuestion - 1]);
+ };
+
+ const resetQuiz = () => {
+ setCurrentQuestion(0);
+ setSelectedAnswer(null);
+ setScore(0);
+ setShowResult(false);
+ setQuizCompleted(false);
+ setAnswers(Array(questions.length).fill(null));
+ setShowIntro(true);
+ };
+
+ const calculateWeakAreas = () => {
+ const weakAreas = [];
+ if (answers[0] !== questions[0].correctAnswer) {
+ weakAreas.push("BFS's visit order");
+ }
+ if (answers[1] !== questions[1].correctAnswer) {
+ weakAreas.push("why BFS uses a queue");
+ }
+ if (answers[2] !== questions[2].correctAnswer) {
+ weakAreas.push("when to mark a vertex visited");
+ }
+ if (answers[3] !== questions[3].correctAnswer) {
+ weakAreas.push("BFS's time complexity");
+ }
+ if (answers[4] !== questions[4].correctAnswer) {
+ weakAreas.push("what BFS is best used for");
+ }
+
+ return weakAreas.length > 0
+ ? `Focus on improving: ${weakAreas.join(', ')}. Review the corresponding sections above.`
+ : "Perfect! You've mastered Breadth-First Search!";
+ };
+
+ const startQuiz = () => {
+ setShowIntro(false);
+ };
+
+ const getStarRating = () => {
+ const percentage = (score / questions.length) * 100;
+ if (percentage >= 90) return 5;
+ if (percentage >= 70) return 4;
+ if (percentage >= 50) return 3;
+ if (percentage >= 30) return 2;
+ return 1;
+ };
+
+ return (
+
+ {showIntro ? (
+
+
+
+
+
+
+
+ Breadth-First Search Quiz
+
+
+
+ How it works:
+
+
+
+
+ +1 point for each correct answer
+
+
+
+ 0 points for wrong answers
+
+
+
+ -0.5 point penalty for viewing explanations
+
+
+
+ Earn stars based on your final score (max 5 stars)
+
+
+ );
+};
+
+export default DfsVisualizer;
diff --git a/app/visualizer/graph/traversal/dfs/code.js b/app/visualizer/graph/traversal/dfs/code.js
new file mode 100755
index 0000000..cfc5571
--- /dev/null
+++ b/app/visualizer/graph/traversal/dfs/code.js
@@ -0,0 +1,153 @@
+const codeExamples = {
+ javascript: `// Recursive DFS: the call stack itself plays the role of the stack —
+// each recursive call goes one level deeper before returning to try
+// the next neighbor.
+function dfs(adjList, start, visited = new Set(), order = []) {
+ visited.add(start);
+ order.push(start);
+
+ for (const neighbor of adjList[start] || []) {
+ if (!visited.has(neighbor)) {
+ dfs(adjList, neighbor, visited, order);
+ }
+ }
+
+ return order;
+}
+
+// Iterative version with an explicit stack. A vertex may be pushed more
+// than once; the visited check happens when it's popped, not when it's
+// pushed, so a duplicate pop is simply skipped.
+function dfsIterative(adjList, start) {
+ const visited = new Set();
+ const stack = [start];
+ const order = [];
+
+ while (stack.length > 0) {
+ const current = stack.pop();
+ if (visited.has(current)) continue;
+
+ visited.add(current);
+ order.push(current);
+
+ for (const neighbor of [...(adjList[current] || [])].reverse()) {
+ if (!visited.has(neighbor)) stack.push(neighbor);
+ }
+ }
+
+ return order;
+}
+
+// Usage example
+const adjList = { A: ["B", "C"], B: ["A", "D"], C: ["A"], D: ["B"] };
+dfs(adjList, "A"); // ["A", "B", "D", "C"]`,
+
+ python: `# Recursive DFS: the call stack itself plays the role of the stack --
+# each recursive call goes one level deeper before returning to try
+# the next neighbor.
+def dfs(adj_list, start, visited=None, order=None):
+ if visited is None:
+ visited = set()
+ order = []
+
+ visited.add(start)
+ order.append(start)
+
+ for neighbor in adj_list.get(start, []):
+ if neighbor not in visited:
+ dfs(adj_list, neighbor, visited, order)
+
+ return order
+
+# Iterative version with an explicit stack. A vertex may be pushed more
+# than once; the visited check happens when it's popped, not when it's
+# pushed, so a duplicate pop is simply skipped.
+def dfs_iterative(adj_list, start):
+ visited = set()
+ stack = [start]
+ order = []
+
+ while stack:
+ current = stack.pop()
+ if current in visited:
+ continue
+
+ visited.add(current)
+ order.append(current)
+
+ for neighbor in reversed(adj_list.get(current, [])):
+ if neighbor not in visited:
+ stack.append(neighbor)
+
+ return order
+
+# Usage example
+adj_list = {"A": ["B", "C"], "B": ["A", "D"], "C": ["A"], "D": ["B"]}
+dfs(adj_list, "A") # ["A", "B", "D", "C"]`,
+
+ c: `#include
+
+#define MAX_V 26
+
+char adjList[MAX_V][MAX_V];
+int adjCount[MAX_V] = {0};
+int visited[MAX_V] = {0};
+
+// Recursive DFS: the call stack itself plays the role of the stack --
+// each recursive call goes one level deeper before returning to try
+// the next neighbor.
+void dfs(char current) {
+ visited[current - 'A'] = 1;
+ printf("%c ", current);
+
+ for (int i = 0; i < adjCount[current - 'A']; i++) {
+ char neighbor = adjList[current - 'A'][i];
+ if (!visited[neighbor - 'A']) {
+ dfs(neighbor);
+ }
+ }
+}
+
+int main() {
+ // Example: A-B, A-C, B-D (undirected)
+ adjList[0][0] = 'B'; adjList[0][1] = 'C'; adjCount[0] = 2; // A
+ adjList[1][0] = 'A'; adjList[1][1] = 'D'; adjCount[1] = 2; // B
+ adjList[2][0] = 'A'; adjCount[2] = 1; // C
+ adjList[3][0] = 'B'; adjCount[3] = 1; // D
+
+ dfs('A'); // A B D C
+ return 0;
+}`,
+
+ java: `import java.util.*;
+
+public class DFS {
+ // Recursive DFS: the call stack itself plays the role of the stack --
+ // each recursive call goes one level deeper before returning to try
+ // the next neighbor.
+ static void dfs(Map> adjList, char current, Set visited, List order) {
+ visited.add(current);
+ order.add(current);
+
+ for (char neighbor : adjList.getOrDefault(current, List.of())) {
+ if (!visited.contains(neighbor)) {
+ dfs(adjList, neighbor, visited, order);
+ }
+ }
+ }
+
+ public static void main(String[] args) {
+ Map> adjList = new HashMap<>();
+ adjList.put('A', List.of('B', 'C'));
+ adjList.put('B', List.of('A', 'D'));
+ adjList.put('C', List.of('A'));
+ adjList.put('D', List.of('B'));
+
+ List order = new ArrayList<>();
+ dfs(adjList, 'A', new HashSet<>(), order);
+ System.out.println(order); // [A, B, D, C]
+ }
+}`,
+};
+
+export default codeExamples;
diff --git a/app/visualizer/graph/traversal/dfs/content.jsx b/app/visualizer/graph/traversal/dfs/content.jsx
new file mode 100755
index 0000000..2a1db57
--- /dev/null
+++ b/app/visualizer/graph/traversal/dfs/content.jsx
@@ -0,0 +1,210 @@
+"use client";
+import ComplexityGraph from "@/app/components/ui/graph";
+import { useTheme } from "@/app/contexts/ThemeContext";
+import DailyDSAEmbed from "@/app/components/ui/DailyDSAEmbed";
+import NewsletterEmbed from "@/app/components/ui/NewsletterEmbed";
+import InContentAd from "@/app/components/ads/InContentAd";
+import { motion } from "framer-motion";
+
+const WalkthroughDiagram = () => {
+ const vertices = [
+ { id: "A", x: 100, y: 20, order: 1 },
+ { id: "B", x: 40, y: 70, order: 2 },
+ { id: "D", x: 20, y: 125, order: 3 },
+ { id: "E", x: 90, y: 125, order: 4 },
+ { id: "C", x: 160, y: 70, order: 5 },
+ ];
+ const edges = [
+ ["A", "B"],
+ ["A", "C"],
+ ["B", "D"],
+ ["B", "E"],
+ ];
+ const byId = Object.fromEntries(vertices.map((v) => [v.id, v]));
+
+ return (
+
+ );
+};
+
+const Content = () => {
+ const { theme } = useTheme();
+
+ const paragraphs = [
+ `Depth-First Search explores a graph by plunging as deep as possible down one path before ever backing up: from the current vertex, move to an unvisited neighbor, then from there to one of its unvisited neighbors, and so on — only backtracking to try a different branch once a path runs out of new vertices to reach. Unlike BFS's expanding rings, DFS traces out one long tendril at a time.`,
+ `That behavior comes from using a stack (last-in, first-out) instead of a queue. Whichever vertex was discovered most recently is the one explored next, which is exactly what "keep going deeper" means — the newest neighbor found always jumps ahead of anything discovered earlier and still waiting. A stack can be explicit (an array used as a stack) or implicit, via the call stack of a recursive function — both produce the same traversal order.`,
+ `A subtlety worth noting: with an explicit stack, a vertex can end up pushed more than once before it's actually processed, if two different vertices both discover it as a neighbor before it's popped. That's fine — a visited check happens when a vertex is popped, not when it's pushed, so any duplicate is simply skipped once it's already been handled. This is a real difference from BFS, which marks a vertex visited the moment it's enqueued specifically to prevent that kind of duplication.`,
+ `DFS is the natural tool whenever "is there a path at all" matters more than "what's the shortest path" — detecting cycles, finding connected components, topological sorting of a dependency graph, and solving maze- or puzzle-like search spaces where backtracking through one failed branch to try another is exactly the desired behavior.`,
+ ];
+
+ const algorithm = [
+ { points: "Push the start vertex onto the stack" },
+ {
+ points: "While the stack isn't empty, repeat:",
+ subpoints: [
+ "Pop a vertex; if it's already visited, skip it and continue",
+ "Otherwise, mark it visited and process it (this is the visit order)",
+ "Push each of its unvisited neighbors onto the stack",
+ ],
+ },
+ { points: "Stop when the stack is empty — every vertex reachable from the start has been visited" },
+ ];
+
+ const complexity = [
+ { points: "Time Complexity: O(V + E) — every vertex is popped and processed once, and every edge is examined once across the whole run." },
+ { points: "Space Complexity: O(V) — for the stack (explicit or via recursion) and the visited set in the worst case." },
+ ];
+
+ return (
+
+
+
+
+
+
+ {/* What is it */}
+
+
+
+ What is Depth-First Search?
+
+
+
+ {paragraphs[0]}
+
+
+
+
+ {/* How it works */}
+
+
+
+ How Does It Work?
+
+
+
+ {paragraphs[1]}
+
+
+
+
+ {paragraphs[2]}
+
+
+
+
+ DFS from A plunges down the B branch completely before ever trying C
+
+
+ );
+};
+
+export default SerializeDeserializeVisualizer;
diff --git a/app/visualizer/trees/algorithms/serialize-deserialize/code.js b/app/visualizer/trees/algorithms/serialize-deserialize/code.js
new file mode 100755
index 0000000..8500326
--- /dev/null
+++ b/app/visualizer/trees/algorithms/serialize-deserialize/code.js
@@ -0,0 +1,190 @@
+const codeExamples = {
+ javascript: `// Binary tree node
+class TreeNode {
+ constructor(value) {
+ this.value = value;
+ this.left = null;
+ this.right = null;
+ }
+}
+
+// Preorder traversal, writing a "null" marker for every empty child so the
+// shape of the tree — not just its values — is fully recoverable.
+function serialize(root) {
+ const tokens = [];
+
+ function visit(node) {
+ if (node === null) {
+ tokens.push("null");
+ return;
+ }
+ tokens.push(String(node.value));
+ visit(node.left);
+ visit(node.right);
+ }
+
+ visit(root);
+ return tokens.join(",");
+}
+
+// Reads tokens in the same preorder sequence they were written in.
+function deserialize(data) {
+ const tokens = data.split(",");
+ let i = 0;
+
+ function build() {
+ const token = tokens[i++];
+ if (token === "null") return null;
+
+ const node = new TreeNode(Number(token));
+ node.left = build();
+ node.right = build();
+ return node;
+ }
+
+ return build();
+}
+
+// Usage example
+const encoded = serialize(root); // "8,3,1,null,null,6,null,null,10,null,null"
+const rebuilt = deserialize(encoded); // structurally identical tree`,
+
+ python: `# Binary tree node
+class TreeNode:
+ def __init__(self, value):
+ self.value = value
+ self.left = None
+ self.right = None
+
+# Preorder traversal, writing a "null" marker for every empty child so the
+# shape of the tree -- not just its values -- is fully recoverable.
+def serialize(root):
+ tokens = []
+
+ def visit(node):
+ if node is None:
+ tokens.append("null")
+ return
+ tokens.append(str(node.value))
+ visit(node.left)
+ visit(node.right)
+
+ visit(root)
+ return ",".join(tokens)
+
+# Reads tokens in the same preorder sequence they were written in.
+def deserialize(data):
+ tokens = data.split(",")
+ i = 0
+
+ def build():
+ nonlocal i
+ token = tokens[i]
+ i += 1
+ if token == "null":
+ return None
+
+ node = TreeNode(int(token))
+ node.left = build()
+ node.right = build()
+ return node
+
+ return build()
+
+# Usage example
+encoded = serialize(root) # "8,3,1,null,null,6,null,null,10,null,null"
+rebuilt = deserialize(encoded) # structurally identical tree`,
+
+ c: `#include
+#include
+#include
+
+typedef struct TreeNode {
+ int value;
+ struct TreeNode *left, *right;
+} TreeNode;
+
+// Preorder traversal, writing a "null" marker for every empty child so the
+// shape of the tree -- not just its values -- is fully recoverable.
+void serialize(TreeNode* node, char* out) {
+ if (node == NULL) {
+ strcat(out, "null,");
+ return;
+ }
+ char buf[16];
+ sprintf(buf, "%d,", node->value);
+ strcat(out, buf);
+ serialize(node->left, out);
+ serialize(node->right, out);
+}
+
+// Reads tokens in the same preorder sequence they were written in.
+TreeNode* deserialize(char** tokens, int* index) {
+ char* token = tokens[*index];
+ (*index)++;
+ if (strcmp(token, "null") == 0) return NULL;
+
+ TreeNode* node = malloc(sizeof(TreeNode));
+ node->value = atoi(token);
+ node->left = deserialize(tokens, index);
+ node->right = deserialize(tokens, index);
+ return node;
+}
+
+int main() {
+ TreeNode* root = NULL; // build with insert() from BST Insertion
+ char encoded[256] = "";
+ serialize(root, encoded);
+ printf("Encoded: %s\\n", encoded);
+ return 0;
+}`,
+
+ java: `import java.util.Arrays;
+import java.util.Iterator;
+
+class TreeNode {
+ int value;
+ TreeNode left, right;
+
+ TreeNode(int value) {
+ this.value = value;
+ }
+}
+
+public class SerializeDeserialize {
+
+ // Preorder traversal, writing a "null" marker for every empty child so
+ // the shape of the tree -- not just its values -- is fully recoverable.
+ static void serialize(TreeNode node, StringBuilder out) {
+ if (node == null) {
+ out.append("null,");
+ return;
+ }
+ out.append(node.value).append(",");
+ serialize(node.left, out);
+ serialize(node.right, out);
+ }
+
+ // Reads tokens in the same preorder sequence they were written in.
+ static TreeNode deserialize(Iterator tokens) {
+ String token = tokens.next();
+ if (token.equals("null")) return null;
+
+ TreeNode node = new TreeNode(Integer.parseInt(token));
+ node.left = deserialize(tokens);
+ node.right = deserialize(tokens);
+ return node;
+ }
+
+ public static void main(String[] args) {
+ TreeNode root = null; // build with insert() from BST Insertion
+ StringBuilder encoded = new StringBuilder();
+ serialize(root, encoded);
+ System.out.println("Encoded: " + encoded);
+
+ TreeNode rebuilt = deserialize(Arrays.asList(encoded.toString().split(",")).iterator());
+ }
+}`,
+};
+
+export default codeExamples;
diff --git a/app/visualizer/trees/algorithms/serialize-deserialize/content.jsx b/app/visualizer/trees/algorithms/serialize-deserialize/content.jsx
new file mode 100755
index 0000000..14c9936
--- /dev/null
+++ b/app/visualizer/trees/algorithms/serialize-deserialize/content.jsx
@@ -0,0 +1,223 @@
+"use client";
+import ComplexityGraph from "@/app/components/ui/graph";
+import { useTheme } from "@/app/contexts/ThemeContext";
+import DailyDSAEmbed from "@/app/components/ui/DailyDSAEmbed";
+import NewsletterEmbed from "@/app/components/ui/NewsletterEmbed";
+import InContentAd from "@/app/components/ads/InContentAd";
+import { motion } from "framer-motion";
+
+const WalkthroughDiagram = () => {
+ const nodes = [
+ { id: "8", x: 110, y: 30 },
+ { id: "3", x: 70, y: 80 },
+ { id: "10", x: 150, y: 80 },
+ { id: "1", x: 50, y: 130 },
+ { id: "6", x: 90, y: 130 },
+ ];
+ const edges = [
+ ["8", "3"],
+ ["8", "10"],
+ ["3", "1"],
+ ["3", "6"],
+ ];
+ const byId = Object.fromEntries(nodes.map((n) => [n.id, n]));
+
+ return (
+
+ );
+};
+
+const Content = () => {
+ const { theme } = useTheme();
+
+ const paragraphs = [
+ `Serialization converts a tree into a flat string (or byte stream) that can be saved to disk, sent over a network, or stored in a database. Deserialization is the reverse: reading that string back and rebuilding a tree that's structurally identical to the original — same shape, same values, same left/right placement everywhere.`,
+ `The tricky part isn't encoding the values — it's encoding the *shape*. A plain list of values loses information about which nodes were children of which, and where branches were missing. The standard fix is to explicitly record empty children with a marker (commonly "null" or "#") at every point a node is absent, using a preorder traversal: visit the node, then recurse left, then recurse right, writing a marker whenever a child doesn't exist.`,
+ `Because every empty spot is explicitly recorded, deserialization can rebuild the exact structure by reading tokens in that same preorder sequence: read one token — if it's a null marker, that subtree is empty; otherwise create a node from it, then recursively build its left child from the next tokens, then its right child. The reader never needs to guess where one subtree ends and another begins.`,
+ `This pattern shows up anywhere tree-shaped data needs to survive outside memory: saving a game's scene graph, sending a parsed expression tree between services, caching a computed index structure, or simply persisting any hierarchical data model to JSON or a file.`,
+ ];
+
+ const algorithm = [
+ {
+ points: "Serialize (preorder, with null markers):",
+ subpoints: [
+ "If the current node is empty, write a null marker and return",
+ "Otherwise, write the node's value",
+ "Recurse into the left child, then the right child",
+ ],
+ },
+ {
+ points: "Deserialize (read tokens in the same order they were written):",
+ subpoints: [
+ "Read the next token",
+ "If it's a null marker, this subtree is empty — return immediately",
+ "Otherwise, create a node from it, then recursively read its left child, then its right child",
+ ],
+ },
+ ];
+
+ const complexity = [
+ { points: "Time Complexity: O(n) for both serialize and deserialize — every node and every null marker is visited exactly once." },
+ { points: "Space Complexity: O(n) for the output string, plus O(h) recursion stack for the traversal." },
+ ];
+
+ return (
+
+
+
+
+
+
+ {/* What is it */}
+
+
+
+ What is Serialization?
+
+
+
+ {paragraphs[0]}
+
+
+
+
+ {/* How it works */}
+
+
+
+ How Does It Work?
+
+
+
+ {paragraphs[1]}
+
+
+
+
+ {paragraphs[2]}
+
+
+
+
+ Preorder with null markers: visit 8, then 3, then 1 (both children null), back up, 6 (both children null), back up, 10 (both children null)
+
+
+
+
+
+
+ );
+};
+
+export default Content;
diff --git a/app/visualizer/trees/algorithms/serialize-deserialize/page.jsx b/app/visualizer/trees/algorithms/serialize-deserialize/page.jsx
new file mode 100755
index 0000000..0e6a143
--- /dev/null
+++ b/app/visualizer/trees/algorithms/serialize-deserialize/page.jsx
@@ -0,0 +1,115 @@
+import Animation from "@/app/visualizer/trees/algorithms/serialize-deserialize/animation";
+import Navbar from "@/app/components/navbarinner";
+import ModuleHeader from "@/app/components/modules/Header";
+import Footer from "@/app/components/footer";
+import BackToTop from "@/app/components/ui/backtotop";
+import ExploreOther from "@/app/components/ui/exploreOther";
+import CodeBlock from "@/app/components/modules/CodeBlock";
+import codeExamples from "./code";
+import Quiz from "@/app/visualizer/trees/algorithms/serialize-deserialize/quiz";
+import Content from "@/app/visualizer/trees/algorithms/serialize-deserialize/content";
+import ModuleCard from "@/app/components/ui/ModuleCard";
+import { MODULE_MAPS } from "@/lib/modulesMap";
+
+export const metadata = {
+ title: "Serialize and Deserialize a Binary Tree | Animation and Explanation",
+ description:
+ "Learn how to serialize a binary tree into a string using preorder traversal with null markers, and deserialize that string back into an identical tree, with an interactive visualizer, code examples in JavaScript, C, Python, and Java, and a quiz.",
+ keywords: [
+ "Serialize and Deserialize Binary Tree",
+ "Tree Serialization",
+ "Binary Tree Serialization",
+ "Tree Serialization Algorithm",
+ "Binary Tree Serialization Algorithm",
+ "Tree Serialization Visualization",
+ "Binary Tree Serialization Visualization",
+ "Encode and Decode Binary Tree",
+ "Preorder Serialization",
+ "Serialize Binary Tree in JavaScript",
+ "Tree Serialization in JavaScript",
+ "Serialize Binary Tree in C",
+ "Tree Serialization in C",
+ "Serialize Binary Tree in Python",
+ "Tree Serialization in Python",
+ "Serialize Binary Tree in Java",
+ "Tree Serialization in Java",
+ "Tree Algorithms",
+ "DSA Trees",
+ "Learn Trees",
+ "Tree Quiz",
+ ],
+ robots: "index, follow",
+ openGraph: {
+ images: [
+ {
+ url: "/og.png",
+ width: 1200,
+ height: 630,
+ alt: "Tree Serialization and Deserialization Visualization",
+ },
+ ],
+ },
+};
+
+export default function Page() {
+ const paths = [
+ { name: "Home", href: "/" },
+ { name: "Visualizer", href: "/visualizer" },
+ { name: "Trees : Serialize/Deserialize", href: "" },
+ ];
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Test Your Knowledge before moving forward!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/app/visualizer/trees/algorithms/serialize-deserialize/quiz.jsx b/app/visualizer/trees/algorithms/serialize-deserialize/quiz.jsx
new file mode 100755
index 0000000..5e6f214
--- /dev/null
+++ b/app/visualizer/trees/algorithms/serialize-deserialize/quiz.jsx
@@ -0,0 +1,379 @@
+"use client";
+import React, { useState } from 'react';
+import { FaCheck, FaTimes, FaArrowRight, FaArrowLeft, FaInfoCircle, FaRedo, FaTrophy, FaStar, FaAward } from 'react-icons/fa';
+import { motion, AnimatePresence } from 'framer-motion';
+
+const SerializeDeserializeQuiz = () => {
+ const questions = [
+ {
+ question: "What is the goal of serializing a tree?",
+ options: [
+ "To sort all the values in the tree",
+ "To convert the tree into a flat string that can later rebuild an identical tree",
+ "To delete duplicate nodes",
+ "To balance the tree"
+ ],
+ correctAnswer: 1,
+ explanation: "Serialization encodes a tree into a string (or byte stream) so it can be stored or transmitted, and later decoded back into a structurally identical tree."
+ },
+ {
+ question: "Why isn't a plain list of a tree's values enough to reconstruct it?",
+ options: [
+ "Values can't be written as text",
+ "A list of values alone loses information about which nodes were children of which and where children were missing",
+ "It's actually enough — no extra information is needed",
+ "Lists can only hold numbers, not tree nodes"
+ ],
+ correctAnswer: 1,
+ explanation: "Without recording the shape — including exactly where children are absent — many different trees could produce the same list of values."
+ },
+ {
+ question: "What does the null marker represent during serialization?",
+ options: [
+ "A node with value zero",
+ "An empty child — the traversal reached a spot where no node exists",
+ "The end of the entire string",
+ "A duplicate value that should be skipped"
+ ],
+ correctAnswer: 1,
+ explanation: "Writing an explicit marker for every missing child is what lets deserialization know precisely where each subtree ends, without any ambiguity."
+ },
+ {
+ question: "During deserialization, how does the algorithm know when a subtree is finished?",
+ options: [
+ "It always reads exactly 3 tokens per node",
+ "It counts commas in the string",
+ "As soon as it reads a null marker, that subtree is empty and it returns immediately",
+ "It reconstructs the tree in reverse order"
+ ],
+ correctAnswer: 2,
+ explanation: "A null marker is a direct signal to stop — there's no child here, so the recursive call returns without consuming any more tokens for that branch."
+ },
+ {
+ question: "What is the time complexity of both serialize and deserialize?",
+ options: ["O(1)", "O(log n)", "O(n)", "O(n²)"],
+ correctAnswer: 2,
+ explanation: "Every node and every null marker is visited exactly once during the traversal, in both directions, giving O(n) time for each."
+ }
+ ];
+
+ const [currentQuestion, setCurrentQuestion] = useState(0);
+ const [selectedAnswer, setSelectedAnswer] = useState(null);
+ const [score, setScore] = useState(0);
+ const [showResult, setShowResult] = useState(false);
+ const [quizCompleted, setQuizCompleted] = useState(false);
+ const [answers, setAnswers] = useState(Array(questions.length).fill(null));
+ const [showIntro, setShowIntro] = useState(true);
+ const [showSuccessAnimation, setShowSuccessAnimation] = useState(false);
+
+ const handleAnswerSelect = (optionIndex) => {
+ setSelectedAnswer(optionIndex);
+ const newAnswers = [...answers];
+ newAnswers[currentQuestion] = optionIndex;
+ setAnswers(newAnswers);
+ };
+
+ const handleNextQuestion = () => {
+ if (selectedAnswer === null) return;
+
+ if (selectedAnswer === questions[currentQuestion].correctAnswer) {
+ setScore(score + 1);
+ }
+ if (currentQuestion < questions.length - 1) {
+ setCurrentQuestion(currentQuestion + 1);
+ setSelectedAnswer(null);
+ } else {
+ setShowSuccessAnimation(true);
+ setTimeout(() => {
+ setShowSuccessAnimation(false);
+ setQuizCompleted(true);
+ setShowResult(true);
+ }, 2000);
+ }
+ };
+
+ const handlePreviousQuestion = () => {
+ setCurrentQuestion(currentQuestion - 1);
+ setSelectedAnswer(answers[currentQuestion - 1]);
+ };
+
+ const resetQuiz = () => {
+ setCurrentQuestion(0);
+ setSelectedAnswer(null);
+ setScore(0);
+ setShowResult(false);
+ setQuizCompleted(false);
+ setAnswers(Array(questions.length).fill(null));
+ setShowIntro(true);
+ };
+
+ const calculateWeakAreas = () => {
+ const weakAreas = [];
+ if (answers[0] !== questions[0].correctAnswer) {
+ weakAreas.push("the purpose of serialization");
+ }
+ if (answers[1] !== questions[1].correctAnswer) {
+ weakAreas.push("why a plain value list isn't enough");
+ }
+ if (answers[2] !== questions[2].correctAnswer) {
+ weakAreas.push("what the null marker represents");
+ }
+ if (answers[3] !== questions[3].correctAnswer) {
+ weakAreas.push("how deserialization detects the end of a subtree");
+ }
+ if (answers[4] !== questions[4].correctAnswer) {
+ weakAreas.push("time complexity of serialize/deserialize");
+ }
+
+ return weakAreas.length > 0
+ ? `Focus on improving: ${weakAreas.join(', ')}. Review the corresponding sections above.`
+ : "Perfect! You've mastered Tree Serialization & Deserialization!";
+ };
+
+ const startQuiz = () => {
+ setShowIntro(false);
+ };
+
+ const getStarRating = () => {
+ const percentage = (score / questions.length) * 100;
+ if (percentage >= 90) return 5;
+ if (percentage >= 70) return 4;
+ if (percentage >= 50) return 3;
+ if (percentage >= 30) return 2;
+ return 1;
+ };
+
+ return (
+
+ {showIntro ? (
+
+
+
+
+
+
+
+ Serialize & Deserialize Quiz
+
+
+
+ How it works:
+
+
+
+
+ +1 point for each correct answer
+
+
+
+ 0 points for wrong answers
+
+
+
+ -0.5 point penalty for viewing explanations
+
+
+
+ Earn stars based on your final score (max 5 stars)
+
+
+ );
+};
+
+export default DecisionTreeVisualizer;
diff --git a/app/visualizer/trees/applications/decision-trees/code.js b/app/visualizer/trees/applications/decision-trees/code.js
new file mode 100755
index 0000000..e5476f2
--- /dev/null
+++ b/app/visualizer/trees/applications/decision-trees/code.js
@@ -0,0 +1,253 @@
+const codeExamples = {
+ javascript: `// Gini impurity: 0 when a set is pure (all one class), higher when mixed.
+function gini(labels) {
+ const counts = {};
+ labels.forEach((l) => (counts[l] = (counts[l] || 0) + 1));
+ const n = labels.length;
+ let impurity = 1;
+ Object.values(counts).forEach((c) => {
+ const p = c / n;
+ impurity -= p * p;
+ });
+ return impurity;
+}
+
+function majorityLabel(labels) {
+ const counts = {};
+ labels.forEach((l) => (counts[l] = (counts[l] || 0) + 1));
+ return Object.entries(counts).sort((a, b) => b[1] - a[1])[0][0];
+}
+
+// Tries every midpoint between consecutive distinct values, keeping whichever
+// split minimizes the weighted Gini impurity of the two resulting groups.
+function bestSplit(data) {
+ const values = [...new Set(data.map((d) => d.value))].sort((a, b) => a - b);
+ let best = null;
+
+ for (let i = 0; i < values.length - 1; i++) {
+ const threshold = (values[i] + values[i + 1]) / 2;
+ const left = data.filter((d) => d.value <= threshold);
+ const right = data.filter((d) => d.value > threshold);
+ if (left.length === 0 || right.length === 0) continue;
+
+ const weighted =
+ (left.length / data.length) * gini(left.map((d) => d.label)) +
+ (right.length / data.length) * gini(right.map((d) => d.label));
+
+ if (!best || weighted < best.weighted) best = { threshold, weighted, left, right };
+ }
+ return best;
+}
+
+// Recursively splits the data on whichever threshold most reduces impurity.
+function buildTree(data, depth = 0, maxDepth = 4) {
+ const labels = data.map((d) => d.label);
+
+ if (new Set(labels).size === 1 || depth >= maxDepth || data.length < 2) {
+ return { isLeaf: true, prediction: majorityLabel(labels) };
+ }
+
+ const split = bestSplit(data);
+ if (!split) return { isLeaf: true, prediction: majorityLabel(labels) };
+
+ return {
+ isLeaf: false,
+ threshold: split.threshold,
+ left: buildTree(split.left, depth + 1, maxDepth),
+ right: buildTree(split.right, depth + 1, maxDepth),
+ };
+}
+
+function predict(tree, value) {
+ if (tree.isLeaf) return tree.prediction;
+ return value <= tree.threshold ? predict(tree.left, value) : predict(tree.right, value);
+}
+
+// Usage example
+const data = [
+ { value: 45, label: "No" }, { value: 70, label: "Yes" }, { value: 90, label: "No" },
+];
+const tree = buildTree(data);
+predict(tree, 72); // "Yes"`,
+
+ python: `# Gini impurity: 0 when a set is pure (all one class), higher when mixed.
+def gini(labels):
+ counts = {}
+ for l in labels:
+ counts[l] = counts.get(l, 0) + 1
+ n = len(labels)
+ impurity = 1
+ for c in counts.values():
+ p = c / n
+ impurity -= p * p
+ return impurity
+
+def majority_label(labels):
+ counts = {}
+ for l in labels:
+ counts[l] = counts.get(l, 0) + 1
+ return max(counts.items(), key=lambda kv: kv[1])[0]
+
+# Tries every midpoint between consecutive distinct values, keeping whichever
+# split minimizes the weighted Gini impurity of the two resulting groups.
+def best_split(data):
+ values = sorted(set(d["value"] for d in data))
+ best = None
+
+ for i in range(len(values) - 1):
+ threshold = (values[i] + values[i + 1]) / 2
+ left = [d for d in data if d["value"] <= threshold]
+ right = [d for d in data if d["value"] > threshold]
+ if not left or not right:
+ continue
+
+ weighted = (len(left) / len(data)) * gini([d["label"] for d in left]) + \\
+ (len(right) / len(data)) * gini([d["label"] for d in right])
+
+ if best is None or weighted < best["weighted"]:
+ best = {"threshold": threshold, "weighted": weighted, "left": left, "right": right}
+ return best
+
+# Recursively splits the data on whichever threshold most reduces impurity.
+def build_tree(data, depth=0, max_depth=4):
+ labels = [d["label"] for d in data]
+
+ if len(set(labels)) == 1 or depth >= max_depth or len(data) < 2:
+ return {"is_leaf": True, "prediction": majority_label(labels)}
+
+ split = best_split(data)
+ if split is None:
+ return {"is_leaf": True, "prediction": majority_label(labels)}
+
+ return {
+ "is_leaf": False,
+ "threshold": split["threshold"],
+ "left": build_tree(split["left"], depth + 1, max_depth),
+ "right": build_tree(split["right"], depth + 1, max_depth),
+ }
+
+def predict(tree, value):
+ if tree["is_leaf"]:
+ return tree["prediction"]
+ branch = tree["left"] if value <= tree["threshold"] else tree["right"]
+ return predict(branch, value)
+
+# Usage example
+data = [{"value": 45, "label": "No"}, {"value": 70, "label": "Yes"}, {"value": 90, "label": "No"}]
+tree = build_tree(data)
+predict(tree, 72) # "Yes"`,
+
+ c: `#include
+
+typedef struct { double value; char label[8]; } Sample;
+
+// Gini impurity for a binary-ish label set, simplified for two classes "Yes"/"No".
+double gini(Sample* data, int n) {
+ int yes = 0;
+ for (int i = 0; i < n; i++) if (data[i].label[0] == 'Y') yes++;
+ double pYes = (double)yes / n;
+ double pNo = 1.0 - pYes;
+ return 1.0 - pYes * pYes - pNo * pNo;
+}
+
+// A full C implementation needs dynamic node/tree structures; this sketch
+// shows the core impurity calculation used to score every candidate split
+// (as in the JavaScript/Python versions) when searching for the best threshold.
+int main() {
+ Sample data[] = { {45, "No"}, {70, "Yes"}, {90, "No"} };
+ printf("Gini: %f\\n", gini(data, 3));
+ return 0;
+}`,
+
+ java: `import java.util.*;
+
+public class DecisionTree {
+ static class Sample {
+ double value;
+ String label;
+ Sample(double value, String label) { this.value = value; this.label = label; }
+ }
+
+ static class Node {
+ boolean isLeaf;
+ String prediction;
+ double threshold;
+ Node left, right;
+ }
+
+ // Gini impurity: 0 when a set is pure (all one class), higher when mixed.
+ static double gini(List data) {
+ Map counts = new HashMap<>();
+ for (Sample s : data) counts.merge(s.label, 1, Integer::sum);
+ double impurity = 1.0;
+ for (int c : counts.values()) {
+ double p = (double) c / data.size();
+ impurity -= p * p;
+ }
+ return impurity;
+ }
+
+ static String majorityLabel(List data) {
+ Map counts = new HashMap<>();
+ for (Sample s : data) counts.merge(s.label, 1, Integer::sum);
+ return Collections.max(counts.entrySet(), Map.Entry.comparingByValue()).getKey();
+ }
+
+ // Recursively splits the data on whichever threshold most reduces impurity.
+ static Node buildTree(List data, int depth, int maxDepth) {
+ Node node = new Node();
+ Set labels = new HashSet<>();
+ for (Sample s : data) labels.add(s.label);
+
+ if (labels.size() == 1 || depth >= maxDepth || data.size() < 2) {
+ node.isLeaf = true;
+ node.prediction = majorityLabel(data);
+ return node;
+ }
+
+ double bestWeighted = Double.MAX_VALUE;
+ double bestThreshold = 0;
+ List bestLeft = null, bestRight = null;
+
+ List values = new ArrayList<>();
+ for (Sample s : data) if (!values.contains(s.value)) values.add(s.value);
+ Collections.sort(values);
+
+ for (int i = 0; i < values.size() - 1; i++) {
+ double threshold = (values.get(i) + values.get(i + 1)) / 2;
+ List left = new ArrayList<>(), right = new ArrayList<>();
+ for (Sample s : data) (s.value <= threshold ? left : right).add(s);
+ if (left.isEmpty() || right.isEmpty()) continue;
+
+ double weighted = ((double) left.size() / data.size()) * gini(left)
+ + ((double) right.size() / data.size()) * gini(right);
+ if (weighted < bestWeighted) {
+ bestWeighted = weighted;
+ bestThreshold = threshold;
+ bestLeft = left;
+ bestRight = right;
+ }
+ }
+
+ if (bestLeft == null) {
+ node.isLeaf = true;
+ node.prediction = majorityLabel(data);
+ return node;
+ }
+
+ node.isLeaf = false;
+ node.threshold = bestThreshold;
+ node.left = buildTree(bestLeft, depth + 1, maxDepth);
+ node.right = buildTree(bestRight, depth + 1, maxDepth);
+ return node;
+ }
+
+ public static void main(String[] args) {
+ List data = Arrays.asList(new Sample(45, "No"), new Sample(70, "Yes"), new Sample(90, "No"));
+ Node root = buildTree(data, 0, 4);
+ System.out.println("Root threshold: " + root.threshold);
+ }
+}`,
+};
+
+export default codeExamples;
diff --git a/app/visualizer/trees/applications/decision-trees/content.jsx b/app/visualizer/trees/applications/decision-trees/content.jsx
new file mode 100755
index 0000000..8009b44
--- /dev/null
+++ b/app/visualizer/trees/applications/decision-trees/content.jsx
@@ -0,0 +1,225 @@
+"use client";
+import ComplexityGraph from "@/app/components/ui/graph";
+import { useTheme } from "@/app/contexts/ThemeContext";
+import DailyDSAEmbed from "@/app/components/ui/DailyDSAEmbed";
+import NewsletterEmbed from "@/app/components/ui/NewsletterEmbed";
+import InContentAd from "@/app/components/ads/InContentAd";
+import { motion } from "framer-motion";
+
+const WalkthroughDiagram = () => {
+ const nodes = [
+ { id: "root", x: 110, y: 25, label: "?", color: "#3b82f6", sub: "temp ≤ 62.5" },
+ { id: "leafNo", x: 55, y: 80, label: "No", color: "#ef4444", sub: "n=4" },
+ { id: "mid", x: 165, y: 80, label: "?", color: "#3b82f6", sub: "temp ≤ 82.5" },
+ { id: "leafYes", x: 130, y: 135, label: "Yes", color: "#10b981", sub: "n=6" },
+ { id: "leafNo2", x: 200, y: 135, label: "No", color: "#ef4444", sub: "n=2" },
+ ];
+ const edges = [
+ ["root", "leafNo", "≤"],
+ ["root", "mid", ">"],
+ ["mid", "leafYes", "≤"],
+ ["mid", "leafNo2", ">"],
+ ];
+ const byId = Object.fromEntries(nodes.map((n) => [n.id, n]));
+
+ return (
+
+ );
+};
+
+const Content = () => {
+ const { theme } = useTheme();
+
+ const paragraphs = [
+ `A decision tree makes predictions by asking a sequence of yes/no questions about the data, one per internal node, until it reaches a leaf that holds the answer. To classify a new example, start at the root, follow the branch that matches its features, and repeat at each node down to a leaf — the leaf's label is the prediction. The appeal is that the tree itself is readable: the path from root to leaf is a plain-language explanation of the decision.`,
+ `Building the tree from data is a greedy, recursive process. At each node, every possible way of splitting the remaining data on a feature is scored by how well it separates the classes — the standard measure is Gini impurity, which is 0 for a perfectly pure group (every example the same class) and higher the more mixed a group is. The split chosen is whichever one minimizes the weighted impurity of the two resulting groups.`,
+ `That same scoring process then repeats independently inside each of the two new groups, splitting further and further until a stopping condition is met — usually that a group is already pure, or a maximum depth is reached, or too few examples remain to split meaningfully. The result is a tree where each split is locally optimal, even though the overall tree isn't guaranteed to be the single best possible tree for the data.`,
+ `Decision trees are valued for being interpretable — a doctor, loan officer, or engineer can read the exact chain of thresholds that led to a prediction, unlike many other models. They're rarely used alone at the state of the art, but they're the building block of ensemble methods like Random Forests and Gradient Boosted Trees, which combine many decision trees to trade away some interpretability for substantially better accuracy.`,
+ ];
+
+ const algorithm = [
+ { points: "Compute the impurity of the current node's data (how mixed the classes are)" },
+ { points: "If the data is already pure, or a stopping condition (max depth, minimum samples) is met, make this node a leaf labeled with the majority class" },
+ {
+ points: "Otherwise, find the best split:",
+ subpoints: [
+ "Try splitting on candidate thresholds for the available feature(s)",
+ "For each candidate, compute the weighted impurity of the two resulting groups",
+ "Keep whichever split minimizes that weighted impurity",
+ ],
+ },
+ { points: "Recurse into the left and right groups independently, building each subtree the same way" },
+ ];
+
+ const complexity = [
+ { points: "Time Complexity: O(n · f · log n) to build, where n is the number of samples and f the number of features — each level considers every feature and threshold across roughly n samples." },
+ { points: "Space Complexity: O(n) for the tree in the worst case (one leaf per sample), though depth limits keep real trees far smaller." },
+ ];
+
+ return (
+
+
+
+
+
+
+ {/* What is it */}
+
+
+
+ What is a Decision Tree?
+
+
+
+ {paragraphs[0]}
+
+
+
+
+ {/* How it works */}
+
+
+
+ How Does It Work?
+
+
+
+ {paragraphs[1]}
+
+
+
+
+ {paragraphs[2]}
+
+
+
+
+ Predicting "play outside?" from temperature — two splits fully separate the classes
+
+ n * Math.log2(n)}
+ averageCase={(n) => n * Math.log2(n)}
+ worstCase={(n) => n * n}
+ maxN={20}
+ />
+
+
+
+
+
+ {/* Additional Info */}
+
+
+
+
+ {paragraphs[3]}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Content;
diff --git a/app/visualizer/trees/applications/decision-trees/page.jsx b/app/visualizer/trees/applications/decision-trees/page.jsx
new file mode 100755
index 0000000..8ff1b17
--- /dev/null
+++ b/app/visualizer/trees/applications/decision-trees/page.jsx
@@ -0,0 +1,112 @@
+import Animation from "@/app/visualizer/trees/applications/decision-trees/animation";
+import Navbar from "@/app/components/navbarinner";
+import ModuleHeader from "@/app/components/modules/Header";
+import Footer from "@/app/components/footer";
+import BackToTop from "@/app/components/ui/backtotop";
+import ExploreOther from "@/app/components/ui/exploreOther";
+import CodeBlock from "@/app/components/modules/CodeBlock";
+import codeExamples from "./code";
+import Quiz from "@/app/visualizer/trees/applications/decision-trees/quiz";
+import Content from "@/app/visualizer/trees/applications/decision-trees/content";
+import ModuleCard from "@/app/components/ui/ModuleCard";
+import { MODULE_MAPS } from "@/lib/modulesMap";
+
+export const metadata = {
+ title: "Decision Trees | Animation and Explanation",
+ description:
+ "Learn how a decision tree greedily splits data using Gini impurity to build an interpretable classifier, with an interactive visualizer showing both the dataset and the resulting tree, code examples in JavaScript, C, Python, and Java, and a quiz.",
+ keywords: [
+ "Decision Trees",
+ "Decision Tree Algorithm",
+ "Decision Tree Visualization",
+ "Gini Impurity",
+ "Gini Index",
+ "Decision Tree Classifier",
+ "Decision Tree Machine Learning",
+ "CART Algorithm",
+ "Decision Tree in JavaScript",
+ "Decision Tree in C",
+ "Decision Tree in Python",
+ "Decision Tree in Java",
+ "Machine Learning Trees",
+ "Tree Applications",
+ "DSA Trees",
+ "Learn Trees",
+ "Tree Quiz",
+ ],
+ robots: "index, follow",
+ openGraph: {
+ images: [
+ {
+ url: "/og.png",
+ width: 1200,
+ height: 630,
+ alt: "Decision Tree Visualization",
+ },
+ ],
+ },
+};
+
+export default function Page() {
+ const paths = [
+ { name: "Home", href: "/" },
+ { name: "Visualizer", href: "/visualizer" },
+ { name: "Trees : Decision Trees", href: "" },
+ ];
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Test Your Knowledge before moving forward!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/app/visualizer/trees/applications/decision-trees/quiz.jsx b/app/visualizer/trees/applications/decision-trees/quiz.jsx
new file mode 100755
index 0000000..ee7a647
--- /dev/null
+++ b/app/visualizer/trees/applications/decision-trees/quiz.jsx
@@ -0,0 +1,384 @@
+"use client";
+import React, { useState } from 'react';
+import { FaCheck, FaTimes, FaArrowRight, FaArrowLeft, FaInfoCircle, FaRedo, FaTrophy, FaStar, FaAward } from 'react-icons/fa';
+import { motion, AnimatePresence } from 'framer-motion';
+
+const DecisionTreeQuiz = () => {
+ const questions = [
+ {
+ question: "How does a decision tree make a prediction for a new example?",
+ options: [
+ "It averages all the training data",
+ "It follows the branch matching the example's features at each node, from the root down to a leaf, and uses that leaf's label",
+ "It randomly picks one of the leaf labels",
+ "It looks up the example in a hash table"
+ ],
+ correctAnswer: 1,
+ explanation: "Each internal node asks a yes/no question about a feature; following the matching branch at every node traces a path down to a leaf, whose label is the prediction."
+ },
+ {
+ question: "What does a Gini impurity of 0 for a group of data mean?",
+ options: [
+ "The group is empty",
+ "The group is perfectly pure — every example belongs to the same class",
+ "The group has the maximum possible mixture of classes",
+ "The group's feature values are all zero"
+ ],
+ correctAnswer: 1,
+ explanation: "Gini impurity measures how mixed a group's classes are; it bottoms out at 0 exactly when there's no mixture at all."
+ },
+ {
+ question: "How is the best split chosen at a node?",
+ options: [
+ "Whichever split is fastest to compute",
+ "The split that minimizes the weighted impurity of the two resulting groups",
+ "A random threshold is chosen every time",
+ "Splits always happen at the median value"
+ ],
+ correctAnswer: 1,
+ explanation: "Every candidate threshold is scored by the weighted impurity it would produce, and the one with the lowest weighted impurity is picked."
+ },
+ {
+ question: "When does the recursive splitting process stop and create a leaf?",
+ options: [
+ "It never stops — trees are always infinite",
+ "When the node's data is pure, a depth limit is reached, or too few samples remain to split usefully",
+ "As soon as the first split is made",
+ "Only when every feature has been used exactly once"
+ ],
+ correctAnswer: 1,
+ explanation: "Stopping conditions like purity, maximum depth, and minimum sample count are what keep the tree from growing indefinitely and overfitting."
+ },
+ {
+ question: "Why are decision trees often considered more interpretable than many other models?",
+ options: [
+ "They're always more accurate",
+ "The path from root to leaf is a readable sequence of threshold questions that explains exactly why a prediction was made",
+ "They don't use any mathematics",
+ "They only work on small datasets"
+ ],
+ correctAnswer: 1,
+ explanation: "Because each split is a plain threshold comparison on a named feature, tracing the root-to-leaf path gives a human-readable explanation for any given prediction."
+ }
+ ];
+
+ const [currentQuestion, setCurrentQuestion] = useState(0);
+ const [selectedAnswer, setSelectedAnswer] = useState(null);
+ const [score, setScore] = useState(0);
+ const [showResult, setShowResult] = useState(false);
+ const [quizCompleted, setQuizCompleted] = useState(false);
+ const [answers, setAnswers] = useState(Array(questions.length).fill(null));
+ const [showIntro, setShowIntro] = useState(true);
+ const [showSuccessAnimation, setShowSuccessAnimation] = useState(false);
+
+ const handleAnswerSelect = (optionIndex) => {
+ setSelectedAnswer(optionIndex);
+ const newAnswers = [...answers];
+ newAnswers[currentQuestion] = optionIndex;
+ setAnswers(newAnswers);
+ };
+
+ const handleNextQuestion = () => {
+ if (selectedAnswer === null) return;
+
+ if (selectedAnswer === questions[currentQuestion].correctAnswer) {
+ setScore(score + 1);
+ }
+ if (currentQuestion < questions.length - 1) {
+ setCurrentQuestion(currentQuestion + 1);
+ setSelectedAnswer(null);
+ } else {
+ setShowSuccessAnimation(true);
+ setTimeout(() => {
+ setShowSuccessAnimation(false);
+ setQuizCompleted(true);
+ setShowResult(true);
+ }, 2000);
+ }
+ };
+
+ const handlePreviousQuestion = () => {
+ setCurrentQuestion(currentQuestion - 1);
+ setSelectedAnswer(answers[currentQuestion - 1]);
+ };
+
+ const resetQuiz = () => {
+ setCurrentQuestion(0);
+ setSelectedAnswer(null);
+ setScore(0);
+ setShowResult(false);
+ setQuizCompleted(false);
+ setAnswers(Array(questions.length).fill(null));
+ setShowIntro(true);
+ };
+
+ const calculateWeakAreas = () => {
+ const weakAreas = [];
+ if (answers[0] !== questions[0].correctAnswer) {
+ weakAreas.push("how a prediction is made by following the tree");
+ }
+ if (answers[1] !== questions[1].correctAnswer) {
+ weakAreas.push("what Gini impurity measures");
+ }
+ if (answers[2] !== questions[2].correctAnswer) {
+ weakAreas.push("how the best split is chosen");
+ }
+ if (answers[3] !== questions[3].correctAnswer) {
+ weakAreas.push("the stopping conditions for splitting");
+ }
+ if (answers[4] !== questions[4].correctAnswer) {
+ weakAreas.push("why decision trees are considered interpretable");
+ }
+
+ return weakAreas.length > 0
+ ? `Focus on improving: ${weakAreas.join(', ')}. Review the corresponding sections above.`
+ : "Perfect! You've mastered Decision Trees!";
+ };
+
+ const startQuiz = () => {
+ setShowIntro(false);
+ };
+
+ const getStarRating = () => {
+ const percentage = (score / questions.length) * 100;
+ if (percentage >= 90) return 5;
+ if (percentage >= 70) return 4;
+ if (percentage >= 50) return 3;
+ if (percentage >= 30) return 2;
+ return 1;
+ };
+
+ return (
+
+ {showIntro ? (
+
+
+
+
+
+
+
+ Decision Trees Quiz
+
+
+
+ How it works:
+
+
+
+
+ +1 point for each correct answer
+
+
+
+ 0 points for wrong answers
+
+
+
+ -0.5 point penalty for viewing explanations
+
+
+
+ Earn stars based on your final score (max 5 stars)
+
+
+ );
+};
+
+export default HeapSortVisualizer;
diff --git a/app/visualizer/trees/applications/heap-sort/code.js b/app/visualizer/trees/applications/heap-sort/code.js
new file mode 100755
index 0000000..f631e28
--- /dev/null
+++ b/app/visualizer/trees/applications/heap-sort/code.js
@@ -0,0 +1,161 @@
+const codeExamples = {
+ javascript: `// Sifts the node at index i down so it and its descendants satisfy the
+// max-heap property, considering only the first heapSize elements.
+function siftDown(arr, heapSize, i) {
+ let largest = i;
+ const left = 2 * i + 1;
+ const right = 2 * i + 2;
+
+ if (left < heapSize && arr[left] > arr[largest]) largest = left;
+ if (right < heapSize && arr[right] > arr[largest]) largest = right;
+
+ if (largest !== i) {
+ [arr[i], arr[largest]] = [arr[largest], arr[i]];
+ siftDown(arr, heapSize, largest);
+ }
+}
+
+function heapSort(arr) {
+ const n = arr.length;
+
+ // Phase 1: build a max-heap out of the whole array
+ for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
+ siftDown(arr, n, i);
+ }
+
+ // Phase 2: repeatedly move the root (current max) to the end
+ for (let end = n - 1; end > 0; end--) {
+ [arr[0], arr[end]] = [arr[end], arr[0]];
+ siftDown(arr, end, 0); // heap shrinks by one each time
+ }
+
+ return arr;
+}
+
+// Usage example
+heapSort([9, 5, 8, 2, 4, 7]); // [2, 4, 5, 7, 8, 9]`,
+
+ python: `# Sifts the node at index i down so it and its descendants satisfy the
+# max-heap property, considering only the first heap_size elements.
+def sift_down(arr, heap_size, i):
+ largest = i
+ left = 2 * i + 1
+ right = 2 * i + 2
+
+ if left < heap_size and arr[left] > arr[largest]:
+ largest = left
+ if right < heap_size and arr[right] > arr[largest]:
+ largest = right
+
+ if largest != i:
+ arr[i], arr[largest] = arr[largest], arr[i]
+ sift_down(arr, heap_size, largest)
+
+def heap_sort(arr):
+ n = len(arr)
+
+ # Phase 1: build a max-heap out of the whole array
+ for i in range(n // 2 - 1, -1, -1):
+ sift_down(arr, n, i)
+
+ # Phase 2: repeatedly move the root (current max) to the end
+ for end in range(n - 1, 0, -1):
+ arr[0], arr[end] = arr[end], arr[0]
+ sift_down(arr, end, 0) # heap shrinks by one each time
+
+ return arr
+
+# Usage example
+heap_sort([9, 5, 8, 2, 4, 7]) # [2, 4, 5, 7, 8, 9]`,
+
+ c: `#include
+
+void swap(int* a, int* b) {
+ int temp = *a;
+ *a = *b;
+ *b = temp;
+}
+
+// Sifts the node at index i down so it and its descendants satisfy the
+// max-heap property, considering only the first heapSize elements.
+void siftDown(int arr[], int heapSize, int i) {
+ int largest = i;
+ int left = 2 * i + 1;
+ int right = 2 * i + 2;
+
+ if (left < heapSize && arr[left] > arr[largest]) largest = left;
+ if (right < heapSize && arr[right] > arr[largest]) largest = right;
+
+ if (largest != i) {
+ swap(&arr[i], &arr[largest]);
+ siftDown(arr, heapSize, largest);
+ }
+}
+
+void heapSort(int arr[], int n) {
+ // Phase 1: build a max-heap out of the whole array
+ for (int i = n / 2 - 1; i >= 0; i--) {
+ siftDown(arr, n, i);
+ }
+
+ // Phase 2: repeatedly move the root (current max) to the end
+ for (int end = n - 1; end > 0; end--) {
+ swap(&arr[0], &arr[end]);
+ siftDown(arr, end, 0); // heap shrinks by one each time
+ }
+}
+
+int main() {
+ int arr[] = {9, 5, 8, 2, 4, 7};
+ int n = sizeof(arr) / sizeof(arr[0]);
+ heapSort(arr, n);
+ for (int i = 0; i < n; i++) printf("%d ", arr[i]);
+ return 0;
+}`,
+
+ java: `public class HeapSort {
+
+ // Sifts the node at index i down so it and its descendants satisfy the
+ // max-heap property, considering only the first heapSize elements.
+ static void siftDown(int[] arr, int heapSize, int i) {
+ int largest = i;
+ int left = 2 * i + 1;
+ int right = 2 * i + 2;
+
+ if (left < heapSize && arr[left] > arr[largest]) largest = left;
+ if (right < heapSize && arr[right] > arr[largest]) largest = right;
+
+ if (largest != i) {
+ int temp = arr[i];
+ arr[i] = arr[largest];
+ arr[largest] = temp;
+ siftDown(arr, heapSize, largest);
+ }
+ }
+
+ static void heapSort(int[] arr) {
+ int n = arr.length;
+
+ // Phase 1: build a max-heap out of the whole array
+ for (int i = n / 2 - 1; i >= 0; i--) {
+ siftDown(arr, n, i);
+ }
+
+ // Phase 2: repeatedly move the root (current max) to the end
+ for (int end = n - 1; end > 0; end--) {
+ int temp = arr[0];
+ arr[0] = arr[end];
+ arr[end] = temp;
+ siftDown(arr, end, 0); // heap shrinks by one each time
+ }
+ }
+
+ public static void main(String[] args) {
+ int[] arr = {9, 5, 8, 2, 4, 7};
+ heapSort(arr);
+ for (int v : arr) System.out.print(v + " ");
+ }
+}`,
+};
+
+export default codeExamples;
diff --git a/app/visualizer/trees/applications/heap-sort/content.jsx b/app/visualizer/trees/applications/heap-sort/content.jsx
new file mode 100755
index 0000000..72882ed
--- /dev/null
+++ b/app/visualizer/trees/applications/heap-sort/content.jsx
@@ -0,0 +1,234 @@
+"use client";
+import ComplexityGraph from "@/app/components/ui/graph";
+import { useTheme } from "@/app/contexts/ThemeContext";
+import DailyDSAEmbed from "@/app/components/ui/DailyDSAEmbed";
+import NewsletterEmbed from "@/app/components/ui/NewsletterEmbed";
+import InContentAd from "@/app/components/ads/InContentAd";
+import { motion } from "framer-motion";
+
+const WalkthroughDiagram = () => {
+ const nodes = [
+ { id: "9", x: 110, y: 25 },
+ { id: "5", x: 70, y: 72 },
+ { id: "8", x: 150, y: 72 },
+ { id: "2", x: 50, y: 119 },
+ { id: "4", x: 90, y: 119 },
+ ];
+ const edges = [
+ ["9", "5"],
+ ["9", "8"],
+ ["5", "2"],
+ ["5", "4"],
+ ];
+ const byId = Object.fromEntries(nodes.map((n) => [n.id, n]));
+
+ return (
+
+ );
+};
+
+const Content = () => {
+ const { theme } = useTheme();
+
+ const paragraphs = [
+ `Heap Sort sorts an array in place by first turning it into a max-heap — a binary tree where every parent is greater than or equal to its children — and then repeatedly removing the largest element (the root) and placing it at the end of the unsorted region. Because the largest remaining value is always at the root of a max-heap, this naturally produces the array in ascending order, one extraction at a time.`,
+ `A binary heap doesn't need pointers or a separate tree structure at all — it can be stored directly in an array. For a node at index i, its children live at indices 2i+1 and 2i+2, and its parent lives at index floor((i-1)/2). This "implicit tree" is exactly what makes Heap Sort an in-place algorithm: the same array that holds the values also encodes the tree shape.`,
+ `The algorithm runs in two phases. First, build a max-heap out of the entire array by sifting down every non-leaf node, starting from the last one and working back to the root — this bottom-up approach builds the heap in linear time. Second, repeatedly swap the root with the last element of the current heap, shrink the heap by one, and sift the new root down to restore the heap property. After n-1 extractions, the array is fully sorted.`,
+ `Because it needs no extra memory beyond a few variables and never degrades on any input, Heap Sort is a reliable choice when worst-case O(n log n) time and O(1) space both matter — it's what backs priority queues, and shows up in hybrid sorts like introsort, which falls back to Heap Sort when Quick Sort's recursion gets too deep.`,
+ ];
+
+ const algorithm = [
+ {
+ points: "Build a max-heap from the array:",
+ subpoints: [
+ "Starting from the last non-leaf node down to the root, sift each node down so it and its descendants satisfy the heap property",
+ ],
+ },
+ {
+ points: "Repeatedly extract the maximum:",
+ subpoints: [
+ "Swap the root (largest value) with the last element of the current heap",
+ "Shrink the heap size by one — that swapped element is now in its final sorted position",
+ "Sift the new root down to restore the max-heap property",
+ ],
+ },
+ { points: "Stop once the heap size reaches 1 — the array is fully sorted" },
+ ];
+
+ const complexity = [
+ { points: "Best Case: O(n log n) — building the heap is O(n), and each of the n extractions costs O(log n)." },
+ { points: "Average Case: O(n log n) — same shape of work regardless of the input's initial order." },
+ { points: "Worst Case: O(n log n) — unlike Quick Sort, there's no pathological input that degrades this." },
+ { points: "Space Complexity: O(1) — sorting happens in place within the array." },
+ ];
+
+ return (
+
+
+
+
+
+
+ {/* What is Heap Sort */}
+
+
+
+ What is Heap Sort?
+
+
+
+ {paragraphs[0]}
+
+
+
+
+ {/* How it works */}
+
+
+
+ How Does It Work?
+
+
+
+ {paragraphs[1]}
+
+
+
+
+ {paragraphs[2]}
+
+
+
+
+ A max-heap: every parent is ≥ its children, and the largest value sits at the root
+
+ n * Math.log2(n)}
+ averageCase={(n) => n * Math.log2(n)}
+ worstCase={(n) => n * Math.log2(n)}
+ maxN={25}
+ />
+
+
+
+
+
+ {/* Additional Info */}
+
+
+
+
+ {paragraphs[3]}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Content;
diff --git a/app/visualizer/trees/applications/heap-sort/page.jsx b/app/visualizer/trees/applications/heap-sort/page.jsx
new file mode 100755
index 0000000..6af53d7
--- /dev/null
+++ b/app/visualizer/trees/applications/heap-sort/page.jsx
@@ -0,0 +1,113 @@
+import Animation from "@/app/visualizer/trees/applications/heap-sort/animation";
+import Navbar from "@/app/components/navbarinner";
+import ModuleHeader from "@/app/components/modules/Header";
+import Footer from "@/app/components/footer";
+import BackToTop from "@/app/components/ui/backtotop";
+import ExploreOther from "@/app/components/ui/exploreOther";
+import CodeBlock from "@/app/components/modules/CodeBlock";
+import codeExamples from "./code";
+import Quiz from "@/app/visualizer/trees/applications/heap-sort/quiz";
+import Content from "@/app/visualizer/trees/applications/heap-sort/content";
+import ModuleCard from "@/app/components/ui/ModuleCard";
+import { MODULE_MAPS } from "@/lib/modulesMap";
+
+export const metadata = {
+ title: "Heap Sort | Animation and Explanation",
+ description:
+ "Learn how Heap Sort builds a max-heap from an array and repeatedly extracts the largest element to sort in place, with an interactive visualizer showing both the array and its heap tree, code examples in JavaScript, C, Python, and Java, and a quiz.",
+ keywords: [
+ "Heap Sort",
+ "Heap Sort Algorithm",
+ "Heap Sort Visualization",
+ "Max Heap",
+ "Max Heap Sort",
+ "Binary Heap",
+ "Binary Heap Sort",
+ "Heapify",
+ "Heap Sort in JavaScript",
+ "Heap Sort in C",
+ "Heap Sort in Python",
+ "Heap Sort in Java",
+ "Sorting Algorithms",
+ "Tree Applications",
+ "DSA Trees",
+ "Learn Trees",
+ "Tree Quiz",
+ ],
+ robots: "index, follow",
+ openGraph: {
+ images: [
+ {
+ url: "/og.png",
+ width: 1200,
+ height: 630,
+ alt: "Heap Sort Visualization",
+ },
+ ],
+ },
+};
+
+export default function Page() {
+ const paths = [
+ { name: "Home", href: "/" },
+ { name: "Visualizer", href: "/visualizer" },
+ { name: "Trees : Heap Sort", href: "" },
+ ];
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Test Your Knowledge before moving forward!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/app/visualizer/trees/applications/heap-sort/quiz.jsx b/app/visualizer/trees/applications/heap-sort/quiz.jsx
new file mode 100755
index 0000000..0442263
--- /dev/null
+++ b/app/visualizer/trees/applications/heap-sort/quiz.jsx
@@ -0,0 +1,384 @@
+"use client";
+import React, { useState } from 'react';
+import { FaCheck, FaTimes, FaArrowRight, FaArrowLeft, FaInfoCircle, FaRedo, FaTrophy, FaStar, FaAward } from 'react-icons/fa';
+import { motion, AnimatePresence } from 'framer-motion';
+
+const HeapSortQuiz = () => {
+ const questions = [
+ {
+ question: "What property must a max-heap satisfy?",
+ options: [
+ "Every left child is smaller than every right child",
+ "Every parent node's value is greater than or equal to its children's values",
+ "All leaf nodes must have the same value",
+ "The tree must be a valid Binary Search Tree"
+ ],
+ correctAnswer: 1,
+ explanation: "In a max-heap, every parent is greater than or equal to its children, which guarantees the maximum value in the whole structure is always at the root."
+ },
+ {
+ question: "For a node stored at index i in the array representation of a heap, where are its children?",
+ options: [
+ "At indices i-1 and i+1",
+ "At indices 2i+1 and 2i+2",
+ "At indices i/2 and i*2",
+ "Children aren't derivable from the index — pointers are required"
+ ],
+ correctAnswer: 1,
+ explanation: "This index arithmetic is what lets a heap live entirely inside a flat array with no pointers — the tree shape is implicit in the indices."
+ },
+ {
+ question: "What are the two main phases of Heap Sort?",
+ options: [
+ "Partition, then merge",
+ "Build a max-heap from the array, then repeatedly extract the root and shrink the heap",
+ "Sort the left half, then the right half",
+ "Insert one element at a time into a new sorted array"
+ ],
+ correctAnswer: 1,
+ explanation: "First the whole array is turned into a max-heap, then the root (the current maximum) is repeatedly swapped to the end and the heap shrinks by one each time."
+ },
+ {
+ question: "After the root is swapped with the last element of the current heap, what happens next?",
+ options: [
+ "The heap is rebuilt completely from scratch",
+ "The new root is sifted down to restore the max-heap property, using the now-smaller heap size",
+ "Nothing — the array is already sorted at that point",
+ "The swapped element is sifted down"
+ ],
+ correctAnswer: 1,
+ explanation: "Only the new root can violate the heap property after the swap, so a single sift-down (not a full rebuild) is enough to restore it."
+ },
+ {
+ question: "Why is Heap Sort's worst-case time complexity more predictable than Quick Sort's?",
+ options: [
+ "Heap Sort doesn't actually sort correctly in the worst case",
+ "Sifting down always costs O(log n) regardless of the input's initial arrangement, so there's no pathological input that degrades it to O(n²)",
+ "Heap Sort uses extra memory to avoid worst cases",
+ "It isn't more predictable — both have the same worst case"
+ ],
+ correctAnswer: 1,
+ explanation: "Quick Sort can degrade to O(n²) on certain inputs depending on pivot choice, but Heap Sort's heap operations stay O(log n) no matter how the input is arranged, keeping it at O(n log n) always."
+ }
+ ];
+
+ const [currentQuestion, setCurrentQuestion] = useState(0);
+ const [selectedAnswer, setSelectedAnswer] = useState(null);
+ const [score, setScore] = useState(0);
+ const [showResult, setShowResult] = useState(false);
+ const [quizCompleted, setQuizCompleted] = useState(false);
+ const [answers, setAnswers] = useState(Array(questions.length).fill(null));
+ const [showIntro, setShowIntro] = useState(true);
+ const [showSuccessAnimation, setShowSuccessAnimation] = useState(false);
+
+ const handleAnswerSelect = (optionIndex) => {
+ setSelectedAnswer(optionIndex);
+ const newAnswers = [...answers];
+ newAnswers[currentQuestion] = optionIndex;
+ setAnswers(newAnswers);
+ };
+
+ const handleNextQuestion = () => {
+ if (selectedAnswer === null) return;
+
+ if (selectedAnswer === questions[currentQuestion].correctAnswer) {
+ setScore(score + 1);
+ }
+ if (currentQuestion < questions.length - 1) {
+ setCurrentQuestion(currentQuestion + 1);
+ setSelectedAnswer(null);
+ } else {
+ setShowSuccessAnimation(true);
+ setTimeout(() => {
+ setShowSuccessAnimation(false);
+ setQuizCompleted(true);
+ setShowResult(true);
+ }, 2000);
+ }
+ };
+
+ const handlePreviousQuestion = () => {
+ setCurrentQuestion(currentQuestion - 1);
+ setSelectedAnswer(answers[currentQuestion - 1]);
+ };
+
+ const resetQuiz = () => {
+ setCurrentQuestion(0);
+ setSelectedAnswer(null);
+ setScore(0);
+ setShowResult(false);
+ setQuizCompleted(false);
+ setAnswers(Array(questions.length).fill(null));
+ setShowIntro(true);
+ };
+
+ const calculateWeakAreas = () => {
+ const weakAreas = [];
+ if (answers[0] !== questions[0].correctAnswer) {
+ weakAreas.push("the max-heap property");
+ }
+ if (answers[1] !== questions[1].correctAnswer) {
+ weakAreas.push("array-index-to-child mapping in a heap");
+ }
+ if (answers[2] !== questions[2].correctAnswer) {
+ weakAreas.push("the two phases of Heap Sort");
+ }
+ if (answers[3] !== questions[3].correctAnswer) {
+ weakAreas.push("why sift-down (not a full rebuild) follows each extraction");
+ }
+ if (answers[4] !== questions[4].correctAnswer) {
+ weakAreas.push("why the worst case stays O(n log n)");
+ }
+
+ return weakAreas.length > 0
+ ? `Focus on improving: ${weakAreas.join(', ')}. Review the corresponding sections above.`
+ : "Perfect! You've mastered Heap Sort!";
+ };
+
+ const startQuiz = () => {
+ setShowIntro(false);
+ };
+
+ const getStarRating = () => {
+ const percentage = (score / questions.length) * 100;
+ if (percentage >= 90) return 5;
+ if (percentage >= 70) return 4;
+ if (percentage >= 50) return 3;
+ if (percentage >= 30) return 2;
+ return 1;
+ };
+
+ return (
+
+ {showIntro ? (
+
+
+
+
+
+
+
+ Heap Sort Quiz
+
+
+
+ How it works:
+
+
+
+
+ +1 point for each correct answer
+
+
+
+ 0 points for wrong answers
+
+
+
+ -0.5 point penalty for viewing explanations
+
+
+
+ Earn stars based on your final score (max 5 stars)
+
+ n * Math.log2(n)}
+ averageCase={(n) => n * Math.log2(n)}
+ worstCase={(n) => n * Math.log2(n)}
+ maxN={25}
+ />
+
+
+
+
+
+ {/* Additional Info */}
+
+
+
+
+ {paragraphs[3]}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Content;
diff --git a/app/visualizer/trees/applications/huffman-coding/page.jsx b/app/visualizer/trees/applications/huffman-coding/page.jsx
new file mode 100755
index 0000000..7eb1718
--- /dev/null
+++ b/app/visualizer/trees/applications/huffman-coding/page.jsx
@@ -0,0 +1,113 @@
+import Animation from "@/app/visualizer/trees/applications/huffman-coding/animation";
+import Navbar from "@/app/components/navbarinner";
+import ModuleHeader from "@/app/components/modules/Header";
+import Footer from "@/app/components/footer";
+import BackToTop from "@/app/components/ui/backtotop";
+import ExploreOther from "@/app/components/ui/exploreOther";
+import CodeBlock from "@/app/components/modules/CodeBlock";
+import codeExamples from "./code";
+import Quiz from "@/app/visualizer/trees/applications/huffman-coding/quiz";
+import Content from "@/app/visualizer/trees/applications/huffman-coding/content";
+import ModuleCard from "@/app/components/ui/ModuleCard";
+import { MODULE_MAPS } from "@/lib/modulesMap";
+
+export const metadata = {
+ title: "Huffman Coding | Animation and Explanation",
+ description:
+ "Learn how Huffman Coding builds an optimal prefix-free binary code by repeatedly merging the two lowest-frequency symbols into a tree, with an interactive visualizer, code examples in JavaScript, C, Python, and Java, and a quiz.",
+ keywords: [
+ "Huffman Coding",
+ "Huffman Coding Algorithm",
+ "Huffman Tree",
+ "Huffman Coding Visualization",
+ "Huffman Tree Visualization",
+ "Huffman Compression",
+ "Prefix-Free Codes",
+ "Data Compression Algorithm",
+ "Huffman Coding in JavaScript",
+ "Huffman Coding in C",
+ "Huffman Coding in Python",
+ "Huffman Coding in Java",
+ "Greedy Algorithms",
+ "Tree Applications",
+ "DSA Trees",
+ "Learn Trees",
+ "Tree Quiz",
+ ],
+ robots: "index, follow",
+ openGraph: {
+ images: [
+ {
+ url: "/og.png",
+ width: 1200,
+ height: 630,
+ alt: "Huffman Coding Visualization",
+ },
+ ],
+ },
+};
+
+export default function Page() {
+ const paths = [
+ { name: "Home", href: "/" },
+ { name: "Visualizer", href: "/visualizer" },
+ { name: "Trees : Huffman Coding", href: "" },
+ ];
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Test Your Knowledge before moving forward!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/app/visualizer/trees/applications/huffman-coding/quiz.jsx b/app/visualizer/trees/applications/huffman-coding/quiz.jsx
new file mode 100755
index 0000000..02461d4
--- /dev/null
+++ b/app/visualizer/trees/applications/huffman-coding/quiz.jsx
@@ -0,0 +1,379 @@
+"use client";
+import React, { useState } from 'react';
+import { FaCheck, FaTimes, FaArrowRight, FaArrowLeft, FaInfoCircle, FaRedo, FaTrophy, FaStar, FaAward } from 'react-icons/fa';
+import { motion, AnimatePresence } from 'framer-motion';
+
+const HuffmanQuiz = () => {
+ const questions = [
+ {
+ question: "What is the main idea behind Huffman coding?",
+ options: [
+ "Every character gets the same fixed-length code",
+ "Frequent characters get shorter codes and rare characters get longer codes",
+ "Characters are sorted alphabetically before encoding",
+ "The input is compressed by removing duplicate characters"
+ ],
+ correctAnswer: 1,
+ explanation: "By spending fewer bits on common characters and more on rare ones, the total encoded length ends up shorter than a fixed-width encoding."
+ },
+ {
+ question: "What does it mean for Huffman codes to be \"prefix-free\"?",
+ options: [
+ "No character's code is a prefix of another character's code",
+ "All codes must start with 0",
+ "Codes are assigned in alphabetical order",
+ "Every code must be exactly the same length"
+ ],
+ correctAnswer: 0,
+ explanation: "Prefix-free codes can be decoded unambiguously from a single bitstream, since no code could be mistaken for the start of a longer one."
+ },
+ {
+ question: "How is a character's Huffman code determined from the tree?",
+ options: [
+ "By its frequency value directly",
+ "By the sequence of left (0) and right (1) turns on the path from the root to that character's leaf",
+ "By its position in the input text",
+ "By a random assignment after the tree is built"
+ ],
+ correctAnswer: 1,
+ explanation: "Each left/right step down the tree appends a 0 or 1, so a character's full code is exactly its root-to-leaf path."
+ },
+ {
+ question: "At each step of building the tree, which two nodes get merged?",
+ options: [
+ "The two nodes with the highest frequency",
+ "Two randomly chosen nodes",
+ "The two nodes with the lowest frequency",
+ "The leftmost two nodes in the queue"
+ ],
+ correctAnswer: 2,
+ explanation: "Always merging the two least-frequent nodes is what pushes rare symbols deepest into the tree, giving them the longest codes."
+ },
+ {
+ question: "What is the time complexity of building a Huffman tree for n distinct symbols?",
+ options: ["O(n)", "O(n log n)", "O(n²)", "O(2^n)"],
+ correctAnswer: 1,
+ explanation: "Each of the n-1 merges removes the two smallest elements and reinserts one, which costs O(log n) with a priority queue, giving O(n log n) overall."
+ }
+ ];
+
+ const [currentQuestion, setCurrentQuestion] = useState(0);
+ const [selectedAnswer, setSelectedAnswer] = useState(null);
+ const [score, setScore] = useState(0);
+ const [showResult, setShowResult] = useState(false);
+ const [quizCompleted, setQuizCompleted] = useState(false);
+ const [answers, setAnswers] = useState(Array(questions.length).fill(null));
+ const [showIntro, setShowIntro] = useState(true);
+ const [showSuccessAnimation, setShowSuccessAnimation] = useState(false);
+
+ const handleAnswerSelect = (optionIndex) => {
+ setSelectedAnswer(optionIndex);
+ const newAnswers = [...answers];
+ newAnswers[currentQuestion] = optionIndex;
+ setAnswers(newAnswers);
+ };
+
+ const handleNextQuestion = () => {
+ if (selectedAnswer === null) return;
+
+ if (selectedAnswer === questions[currentQuestion].correctAnswer) {
+ setScore(score + 1);
+ }
+ if (currentQuestion < questions.length - 1) {
+ setCurrentQuestion(currentQuestion + 1);
+ setSelectedAnswer(null);
+ } else {
+ setShowSuccessAnimation(true);
+ setTimeout(() => {
+ setShowSuccessAnimation(false);
+ setQuizCompleted(true);
+ setShowResult(true);
+ }, 2000);
+ }
+ };
+
+ const handlePreviousQuestion = () => {
+ setCurrentQuestion(currentQuestion - 1);
+ setSelectedAnswer(answers[currentQuestion - 1]);
+ };
+
+ const resetQuiz = () => {
+ setCurrentQuestion(0);
+ setSelectedAnswer(null);
+ setScore(0);
+ setShowResult(false);
+ setQuizCompleted(false);
+ setAnswers(Array(questions.length).fill(null));
+ setShowIntro(true);
+ };
+
+ const calculateWeakAreas = () => {
+ const weakAreas = [];
+ if (answers[0] !== questions[0].correctAnswer) {
+ weakAreas.push("the core idea behind Huffman coding");
+ }
+ if (answers[1] !== questions[1].correctAnswer) {
+ weakAreas.push("what prefix-free codes are");
+ }
+ if (answers[2] !== questions[2].correctAnswer) {
+ weakAreas.push("how a code is read off the tree");
+ }
+ if (answers[3] !== questions[3].correctAnswer) {
+ weakAreas.push("which nodes get merged at each step");
+ }
+ if (answers[4] !== questions[4].correctAnswer) {
+ weakAreas.push("time complexity of building the tree");
+ }
+
+ return weakAreas.length > 0
+ ? `Focus on improving: ${weakAreas.join(', ')}. Review the corresponding sections above.`
+ : "Perfect! You've mastered Huffman Coding!";
+ };
+
+ const startQuiz = () => {
+ setShowIntro(false);
+ };
+
+ const getStarRating = () => {
+ const percentage = (score / questions.length) * 100;
+ if (percentage >= 90) return 5;
+ if (percentage >= 70) return 4;
+ if (percentage >= 50) return 3;
+ if (percentage >= 30) return 2;
+ return 1;
+ };
+
+ return (
+
+ {showIntro ? (
+
+
+
+
+
+
+
+ Huffman Coding Quiz
+
+
+
+ How it works:
+
+
+
+
+ +1 point for each correct answer
+
+
+
+ 0 points for wrong answers
+
+
+
+ -0.5 point penalty for viewing explanations
+
+
+
+ Earn stars based on your final score (max 5 stars)
+