diff --git a/app/blogs/Content/dsaDifferent/content.jsx b/app/blogs/Content/dsaDifferent/content.jsx
index a0abbb0..029d183 100755
--- a/app/blogs/Content/dsaDifferent/content.jsx
+++ b/app/blogs/Content/dsaDifferent/content.jsx
@@ -104,7 +104,7 @@ const BlogContent = () => {
{ points: "Use language-specific benchmarks to verify performance assumptions" },
{ points: "Study standard library implementations of common structures" },
{ points: "Understand how your language's memory model affects data structures" },
- { points: "Don’t just translate code between languages—adapt it to leverage language strengths" }
+ { points: "Don’t just translate code between languages, adapt it to leverage language strengths" }
];
return (
diff --git a/app/blogs/Content/dsaDifferent/page.jsx b/app/blogs/Content/dsaDifferent/page.jsx
index fcf7d4d..092a06c 100755
--- a/app/blogs/Content/dsaDifferent/page.jsx
+++ b/app/blogs/Content/dsaDifferent/page.jsx
@@ -41,7 +41,7 @@ export const metadata = {
card: "summary_large_image",
title: "Are Data Structures and Algorithms Different for Different Languages?",
description:
- "Do arrays, stacks, or recursion work the same in Python and C++? Learn how the core DSA concepts stay the same—but their implementation varies.",
+ "Do arrays, stacks, or recursion work the same in Python and C++? Learn how the core DSA concepts stay the same, but their implementation varies.",
images: ["./blog/dsaDifferent.png"],
},
category: "Technology",
diff --git a/app/blogs/Content/dsaWebDev/page.jsx b/app/blogs/Content/dsaWebDev/page.jsx
index d7590c3..42bec40 100755
--- a/app/blogs/Content/dsaWebDev/page.jsx
+++ b/app/blogs/Content/dsaWebDev/page.jsx
@@ -22,7 +22,7 @@ export const metadata = {
openGraph: {
title: "Is Data Structures and Algorithms Important for Web Developers?",
description:
- "Explore how learning DSA can boost your efficiency, optimize performance, and prepare you for tech interviews—even as a web developer.",
+ "Explore how learning DSA can boost your efficiency, optimize performance, and prepare you for tech interviews, even as a web developer.",
url: "./blog/dsaWebDev.png",
siteName: "DSA Visualizer",
locale: "en_IN",
diff --git a/app/blogs/Content/timeRequired/content.jsx b/app/blogs/Content/timeRequired/content.jsx
index 402478b..13e4fe6 100755
--- a/app/blogs/Content/timeRequired/content.jsx
+++ b/app/blogs/Content/timeRequired/content.jsx
@@ -12,7 +12,7 @@ const BlogContent = () => {
};
const Paragraphs = [
- `If you're a student, developer, or career switcher, you've likely asked: "How long will it take to learn Data Structures and Algorithms (DSA)?" The answer isn't one-size-fits-all — it depends on your goals, consistency, and background.`,
+ `If you're a student, developer, or career switcher, you've likely asked: "How long will it take to learn Data Structures and Algorithms (DSA)?" The answer isn't one-size-fits-all: it depends on your goals, consistency, and background.`,
`Before estimating time, let's define what “mastering” DSA really means. Mastery isn't just knowing syntax or solving rote problems. It means pattern recognition, approaching unseen questions with confidence, and making trade-offs in real-world system design.`,
`For most learners, 3 months gets you foundational knowledge, 6–9 months develops confidence for interviews, and 12+ months results in true mastery. DSA is a long-term game.`,
`Here's a simple 12-week roadmap:\n\n- Weeks 1–2: Arrays, Strings, HashMaps\n- Weeks 3–4: Stacks, Queues, Recursion\n- Weeks 5–6: Linked Lists, Trees\n- Weeks 7–8: Heaps, Binary Trees, BSTs\n- Weeks 9–10: Graphs, DFS/BFS\n- Weeks 11–12: DP, Tries, Bit Manipulation`,
diff --git a/app/blogs/Content/whatIsDS/content.jsx b/app/blogs/Content/whatIsDS/content.jsx
index 12551cb..200a439 100755
--- a/app/blogs/Content/whatIsDS/content.jsx
+++ b/app/blogs/Content/whatIsDS/content.jsx
@@ -12,19 +12,19 @@ const BlogContent = () => {
};
const Paragraphs = [
- `If you're new to coding, you've probably come across terms like array, stack, or linked list and thought, "What does that even mean?" Don't worry — you're not alone. These are all types of data structures, and they form the foundation of how programs organize and process information.`,
- `In this guide, we'll break down what data structures are, why they matter, and introduce you to the most common types — all in simple language with relatable examples.`,
+ `If you're new to coding, you've probably come across terms like array, stack, or linked list and thought, "What does that even mean?" Don't worry, you're not alone. These are all types of data structures, and they form the foundation of how programs organize and process information.`,
+ `In this guide, we'll break down what data structures are, why they matter, and introduce you to the most common types, all in simple language with relatable examples.`,
`A data structure is a way to organize and store data in a computer so it can be used efficiently.`,
`Think of it like organizing your wardrobe: shirts go in one drawer, pants in another, socks in a box. Each drawer (or structure) is designed to hold and access a specific type of item. Similarly, in programming, different data structures are used depending on the type of data and what you want to do with it.`,
`Here's why every programmer needs to understand data structures:`,
- `Data structures are the toolbox every programmer must carry. Mastering them helps you build efficient, scalable, and real-world applications. Start small — practice with basic structures, and slowly move up to complex ones. If you're consistent, what once felt like intimidating jargon will become second nature.`,
+ `Data structures are the toolbox every programmer must carry. Mastering them helps you build efficient, scalable, and real-world applications. Start small: practice with basic structures, and slowly move up to complex ones. If you're consistent, what once felt like intimidating jargon will become second nature.`,
];
const importancePoints = [
{ points: "Efficiency: The right structure makes programs faster and less memory-hungry" },
{ points: "Scalability: Handles larger data more smoothly" },
{ points: "Problem Solving: Many coding problems are based on data structures" },
- { points: "Real-World Use: From social media feeds to navigation systems — they're everywhere" },
+ { points: "Real-World Use: From social media feeds to navigation systems, they're everywhere" },
];
const dataStructures = [
diff --git a/app/components/ads/bottom.jsx b/app/components/ads/bottom.jsx
index abe848f..2b74915 100755
--- a/app/components/ads/bottom.jsx
+++ b/app/components/ads/bottom.jsx
@@ -80,7 +80,7 @@ const BottomAd = () => {
{minimized ? (
- Ad minimized — click ▲ to reopen
+ Ad minimized. Click ▲ to reopen
) : (
{
{
name: 'Arnav Gupta',
email: '@Co-founder, Geekroom',
- review: `DSA Visualizer, this could be the upcoming game-changer. When you're stuck on LeetCode or trying to debug your logic, it helps you actually see what your code is doing at each step. Whether you're just starting out or deep into competitive programming, this is something which can make learning DSA so much more intuitive and less frustrating. Well! my opinion says in the future, I'd love to see a built-in compiler or a feature where users can test their own code with custom inputs — that would take it to the next level. But even now, it's incredibly helpful and super polished!”`,
+ review: `DSA Visualizer, this could be the upcoming game-changer. When you're stuck on LeetCode or trying to debug your logic, it helps you actually see what your code is doing at each step. Whether you're just starting out or deep into competitive programming, this is something which can make learning DSA so much more intuitive and less frustrating. Well! my opinion says in the future, I'd love to see a built-in compiler or a feature where users can test their own code with custom inputs, that would take it to the next level. But even now, it's incredibly helpful and super polished!”`,
stars: 4,
},
{
@@ -34,7 +34,7 @@ const TestimonialSection = () => {
{
name: 'kartik',
email: '@kartik2005221',
- review: `This platform made DSA so much easier to grasp. Everything's in one place — topics, visualizations, and practice. It really helped me connect the dots between theory and how things actually work.`,
+ review: `This platform made DSA so much easier to grasp. Everything's in one place: topics, visualizations, and practice. It really helped me connect the dots between theory and how things actually work.`,
stars: 5,
},
{
diff --git a/app/cookies/page.jsx b/app/cookies/page.jsx
index b3fe4da..1433b68 100755
--- a/app/cookies/page.jsx
+++ b/app/cookies/page.jsx
@@ -43,10 +43,10 @@ const cookieSections = [
title: "Third-Party Cookies",
data: "We use cookies from the following third-party services, each governed by its own privacy policy:",
points: [
- "Google Analytics — measures site traffic and usage patterns",
- "Google AdSense — serves ads and may use cookies to personalize ads based on your visits to this and other sites",
- "Supabase — sets authentication cookies to keep you signed in",
- "Cloudflare Turnstile — sets cookies to verify you're not a bot on login and signup",
+ "Google Analytics: measures site traffic and usage patterns",
+ "Google AdSense: serves ads and may use cookies to personalize ads based on your visits to this and other sites",
+ "Supabase: sets authentication cookies to keep you signed in",
+ "Cloudflare Turnstile: sets cookies to verify you're not a bot on login and signup",
],
},
{
diff --git a/app/privacy/page.jsx b/app/privacy/page.jsx
index d1a4edb..e5af999 100755
--- a/app/privacy/page.jsx
+++ b/app/privacy/page.jsx
@@ -38,11 +38,11 @@ const policySections = [
title: "Third-Party Services",
data: "We use the following third-party services, each of which may process data according to its own privacy policy:",
points: [
- "Supabase — authentication and database storage for accounts and progress data",
- "Google Analytics — website usage analytics",
- "Google AdSense — displays ads and may use cookies to personalize them based on your visits to this and other sites; you can manage ad personalization at adssettings.google.com",
- "Cloudflare Turnstile — bot/spam protection on login and signup",
- "Vercel — hosting and performance analytics",
+ "Supabase: authentication and database storage for accounts and progress data",
+ "Google Analytics: website usage analytics",
+ "Google AdSense: displays ads and may use cookies to personalize them based on your visits to this and other sites; you can manage ad personalization at adssettings.google.com",
+ "Cloudflare Turnstile: bot/spam protection on login and signup",
+ "Vercel: hosting and performance analytics",
],
},
{
diff --git a/app/visualizer/graph/algorithms/dijkstra/animation.jsx b/app/visualizer/graph/algorithms/dijkstra/animation.jsx
index f67afb6..10484c9 100755
--- a/app/visualizer/graph/algorithms/dijkstra/animation.jsx
+++ b/app/visualizer/graph/algorithms/dijkstra/animation.jsx
@@ -28,7 +28,7 @@ const buildWeightedAdjList = (vertices, edges) => {
};
// Greedy relaxation: always finalize whichever unvisited vertex currently
-// has the smallest tentative distance — since all weights are non-negative,
+// has the smallest tentative distance, since all weights are non-negative,
// no edge discovered later could ever produce a shorter path to it.
const dijkstraWithSteps = (vertices, adjList, start) => {
const dist = {};
@@ -167,7 +167,7 @@ const DijkstraVisualizer = () => {
setStartVertex("A");
setTargetVertex("E");
clearResult();
- setMessage("Loaded an example graph — click Run Dijkstra");
+ setMessage("Loaded an example graph, click Run Dijkstra");
};
const reset = () => {
@@ -202,7 +202,7 @@ const DijkstraVisualizer = () => {
setCurrent(step.vertex);
setVisited((p) => new Set(p).add(step.vertex));
setActiveEdge(null);
- setMessage(`Finalize ${step.vertex} — no shorter path to it can exist now`);
+ setMessage(`Finalize ${step.vertex}: no shorter path to it can exist now`);
} else if (step.type === "relax") {
setActiveEdge({ from: step.from, to: step.to });
setDistances((prevD) => ({ ...prevD, [step.to]: step.newDist }));
@@ -220,7 +220,7 @@ const DijkstraVisualizer = () => {
setCurrent(null);
setActiveEdge(null);
setFinalPrev(prev);
- setMessage("Dijkstra complete — every vertex now has its shortest distance from the start");
+ setMessage("Dijkstra complete: every vertex now has its shortest distance from the start");
setBusy(false);
}, STEP_DELAY);
}
diff --git a/app/visualizer/graph/algorithms/dijkstra/content.jsx b/app/visualizer/graph/algorithms/dijkstra/content.jsx
index d40ed67..1d43b15 100755
--- a/app/visualizer/graph/algorithms/dijkstra/content.jsx
+++ b/app/visualizer/graph/algorithms/dijkstra/content.jsx
@@ -74,9 +74,9 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `Dijkstra's algorithm finds the shortest-distance path from a single start vertex to every other vertex in a weighted graph — "shortest" meaning the smallest total edge weight along the path, not the fewest edges (which is what plain BFS finds on an unweighted graph). It requires every edge weight to be non-negative; a single negative weight can break its core assumption and produce wrong answers.`,
- `The algorithm keeps a tentative distance for every vertex, starting at 0 for the source and infinity for everything else. At each step, it finalizes whichever unvisited vertex currently has the smallest tentative distance — once a vertex is finalized, its distance is guaranteed correct and will never be revised again. Then it "relaxes" every edge out of that vertex: for each neighbor, if going through the just-finalized vertex would produce a shorter distance than what's currently recorded, the neighbor's distance is updated.`,
- `The key insight behind why picking the smallest unvisited distance is always safe: since every edge weight is non-negative, any path to that vertex through a still-unvisited (and therefore farther-or-equal) vertex could only be equal or longer. There's no way a shortcut could still be waiting to be discovered. That guarantee is exactly what breaks down if a negative edge weight is allowed — a path through a vertex that currently looks farther away could later turn out shorter, and algorithms like Bellman-Ford exist specifically to handle that case.`,
+ `Dijkstra's algorithm finds the shortest-distance path from a single start vertex to every other vertex in a weighted graph: "shortest" meaning the smallest total edge weight along the path, not the fewest edges (which is what plain BFS finds on an unweighted graph). It requires every edge weight to be non-negative; a single negative weight can break its core assumption and produce wrong answers.`,
+ `The algorithm keeps a tentative distance for every vertex, starting at 0 for the source and infinity for everything else. At each step, it finalizes whichever unvisited vertex currently has the smallest tentative distance. Once a vertex is finalized, its distance is guaranteed correct and will never be revised again. Then it "relaxes" every edge out of that vertex: for each neighbor, if going through the just-finalized vertex would produce a shorter distance than what's currently recorded, the neighbor's distance is updated.`,
+ `The key insight behind why picking the smallest unvisited distance is always safe: since every edge weight is non-negative, any path to that vertex through a still-unvisited (and therefore farther-or-equal) vertex could only be equal or longer. There's no way a shortcut could still be waiting to be discovered. That guarantee is exactly what breaks down if a negative edge weight is allowed: a path through a vertex that currently looks farther away could later turn out shorter, and algorithms like Bellman-Ford exist specifically to handle that case.`,
`Dijkstra's algorithm (typically implemented with a min-priority-queue for efficiency) is the standard tool behind GPS and mapping route-finding, network routing protocols that pick the cheapest path between routers, and any scenario where "cheapest route through a weighted network" needs an exact answer rather than an approximation.`,
];
@@ -94,8 +94,8 @@ const Content = () => {
];
const complexity = [
- { points: "Time Complexity: O((V + E) log V) with a binary heap priority queue — each vertex is extracted once and each edge triggers at most one relaxation, both at logarithmic cost." },
- { points: "Space Complexity: O(V) — for the distance array, the previous-vertex pointers, and the priority queue." },
+ { points: "Time Complexity: O((V + E) log V) with a binary heap priority queue, since each vertex is extracted once and each edge triggers at most one relaxation, both at logarithmic cost." },
+ { points: "Space Complexity: O(V), for the distance array, the previous-vertex pointers, and the priority queue." },
];
return (
@@ -136,7 +136,7 @@ const Content = () => {
- Shortest distances from A — the path A→C→B→D (1+1+3=5) beats A→B→D directly (3+3=6)
+ Shortest distances from A: the path A→C→B→D (1+1+3=5) beats A→B→D directly (3+3=6)
diff --git a/app/visualizer/graph/algorithms/dijkstra/quiz.jsx b/app/visualizer/graph/algorithms/dijkstra/quiz.jsx
index a2c8e45..619bf17 100755
--- a/app/visualizer/graph/algorithms/dijkstra/quiz.jsx
+++ b/app/visualizer/graph/algorithms/dijkstra/quiz.jsx
@@ -14,7 +14,7 @@ const DijkstraQuiz = () => {
"A random spanning tree of the graph"
],
correctAnswer: 1,
- explanation: "Dijkstra minimizes the sum of edge weights along the path, not the number of edges — that distinction is what separates it from plain BFS."
+ explanation: "Dijkstra minimizes the sum of edge weights along the path, not the number of edges; that distinction is what separates it from plain BFS."
},
{
question: "What critical requirement must a graph's edge weights satisfy for Dijkstra's algorithm to work correctly?",
@@ -25,7 +25,7 @@ const DijkstraQuiz = () => {
"There must be no more than 10 edges"
],
correctAnswer: 1,
- explanation: "A negative edge weight can violate the algorithm's core assumption and produce incorrect shortest-distance results — Bellman-Ford is used instead when negative weights are possible."
+ explanation: "A negative edge weight can violate the algorithm's core assumption and produce incorrect shortest-distance results; Bellman-Ford is used instead when negative weights are possible."
},
{
question: "At each step, which vertex does Dijkstra's algorithm finalize next?",
diff --git a/app/visualizer/graph/algorithms/kruskal/animation.jsx b/app/visualizer/graph/algorithms/kruskal/animation.jsx
index 1975e77..8cad8f5 100755
--- a/app/visualizer/graph/algorithms/kruskal/animation.jsx
+++ b/app/visualizer/graph/algorithms/kruskal/animation.jsx
@@ -25,7 +25,7 @@ const find = (parent, v) => {
// Process edges cheapest-first; a Union-Find (disjoint set) tracks which
// vertices are already connected. An edge is only added if its two
-// endpoints are in different sets — accepting it would otherwise create a
+// endpoints are in different sets: accepting it would otherwise create a
// cycle, since both endpoints are already reachable from each other.
const kruskalWithSteps = (vertices, edges) => {
const parent = {};
@@ -139,7 +139,7 @@ const KruskalVisualizer = () => {
setVertices(EXAMPLE_VERTICES);
setEdges(EXAMPLE_EDGES);
clearResult();
- setMessage("Loaded an example graph — click Run Kruskal's");
+ setMessage("Loaded an example graph, click Run Kruskal's");
};
const reset = () => {
@@ -174,14 +174,14 @@ const KruskalVisualizer = () => {
if (step.type === "accept") {
setVertexColors(componentColors(vertices, step.parentSnapshot));
setMstEdges((prev) => [...prev, step.edge]);
- setMessage(`Accept ${step.edge.from}-${step.edge.to} (weight ${step.edge.weight}) — connects two separate components`);
+ setMessage(`Accept ${step.edge.from}-${step.edge.to} (weight ${step.edge.weight}): connects two separate components`);
const bars = document.querySelectorAll(".kruskal-node-active");
if (bars.length > 0) {
gsap.fromTo(bars, { scale: 1 }, { scale: 1.12, duration: 0.2, yoyo: true, repeat: 1 });
}
} else {
- setMessage(`Reject ${step.edge.from}-${step.edge.to} (weight ${step.edge.weight}) — both endpoints are already connected, would create a cycle`);
+ setMessage(`Reject ${step.edge.from}-${step.edge.to} (weight ${step.edge.weight}): both endpoints are already connected, would create a cycle`);
}
i++;
@@ -190,7 +190,7 @@ const KruskalVisualizer = () => {
} else {
setTimeout(() => {
setTotalWeight(tw);
- setMessage(`Kruskal's complete — minimum spanning tree total weight is ${tw}`);
+ setMessage(`Kruskal's complete: minimum spanning tree total weight is ${tw}`);
setBusy(false);
}, STEP_DELAY);
}
@@ -291,7 +291,7 @@ const KruskalVisualizer = () => {
{totalWeight !== null && (
)}
diff --git a/app/visualizer/graph/algorithms/kruskal/content.jsx b/app/visualizer/graph/algorithms/kruskal/content.jsx
index 686b433..af85c3c 100755
--- a/app/visualizer/graph/algorithms/kruskal/content.jsx
+++ b/app/visualizer/graph/algorithms/kruskal/content.jsx
@@ -74,9 +74,9 @@ const Content = () => {
const paragraphs = [
`A minimum spanning tree (MST) of a connected, undirected, weighted graph is a subset of its edges that connects every vertex together, contains no cycles, and has the smallest possible total edge weight among all such subsets. "Spanning" means every vertex is included; "tree" means there's exactly one path between any two vertices in it (n vertices, n-1 edges, no cycles); "minimum" means no other spanning tree costs less.`,
- `Kruskal's algorithm builds one with a simple greedy rule: sort every edge in the graph by weight, from cheapest to most expensive, then walk through them in that order, adding each edge to the tree unless doing so would create a cycle. An edge creates a cycle exactly when its two endpoints are already connected to each other through edges already accepted — so the only real question at each step is "are these two vertices already in the same connected piece?"`,
+ `Kruskal's algorithm builds one with a simple greedy rule: sort every edge in the graph by weight, from cheapest to most expensive, then walk through them in that order, adding each edge to the tree unless doing so would create a cycle. An edge creates a cycle exactly when its two endpoints are already connected to each other through edges already accepted, so the only real question at each step is "are these two vertices already in the same connected piece?"`,
`That question is answered efficiently with a Union-Find (disjoint-set) structure, which tracks which connected component each vertex currently belongs to. Checking whether two vertices are in the same component is a "find" operation; accepting an edge and merging two components is a "union" operation. Both run in close to constant time with the right implementation, which is what keeps the whole algorithm fast even though it needs one check per edge.`,
- `Kruskal's algorithm is the standard choice when a graph is sparse (relatively few edges compared to vertices) since sorting the edge list dominates its cost. It's used to design minimum-cost networks — laying cable or pipe to connect a set of locations as cheaply as possible, building efficient road or utility networks, and as a subroutine in clustering algorithms that group data points by cutting the most expensive edges out of a spanning tree.`,
+ `Kruskal's algorithm is the standard choice when a graph is sparse (relatively few edges compared to vertices) since sorting the edge list dominates its cost. It's used to design minimum-cost networks: laying cable or pipe to connect a set of locations as cheaply as possible, building efficient road or utility networks, and as a subroutine in clustering algorithms that group data points by cutting the most expensive edges out of a spanning tree.`,
];
const algorithm = [
@@ -84,16 +84,16 @@ const Content = () => {
{
points: "Process edges in that order, and for each one:",
subpoints: [
- "If its two endpoints are in different components, accept the edge — add it to the tree and merge the two components",
- "If its two endpoints are already in the same component, reject the edge — accepting it would create a cycle",
+ "If its two endpoints are in different components, accept the edge: add it to the tree and merge the two components",
+ "If its two endpoints are already in the same component, reject the edge, since accepting it would create a cycle",
],
},
- { points: "Stop once the tree has (number of vertices − 1) edges — every vertex is now connected" },
+ { points: "Stop once the tree has (number of vertices − 1) edges, since every vertex is now connected" },
];
const complexity = [
- { points: "Time Complexity: O(E log E) — dominated by sorting the edge list; the Union-Find operations that follow are nearly O(1) each." },
- { points: "Space Complexity: O(V + E) — for the edge list and the Union-Find structure." },
+ { points: "Time Complexity: O(E log E), dominated by sorting the edge list; the Union-Find operations that follow are nearly O(1) each." },
+ { points: "Space Complexity: O(V + E), for the edge list and the Union-Find structure." },
];
return (
diff --git a/app/visualizer/graph/algorithms/kruskal/quiz.jsx b/app/visualizer/graph/algorithms/kruskal/quiz.jsx
index 966a9ce..b045924 100755
--- a/app/visualizer/graph/algorithms/kruskal/quiz.jsx
+++ b/app/visualizer/graph/algorithms/kruskal/quiz.jsx
@@ -31,7 +31,7 @@ const KruskalQuiz = () => {
question: "When does Kruskal's algorithm reject an edge?",
options: [
"When its weight is above some fixed threshold",
- "When its two endpoints are already in the same connected component — accepting it would create a cycle",
+ "When its two endpoints are already in the same connected component, since accepting it would create a cycle",
"When one of its endpoints has too many other edges",
"Kruskal's algorithm never rejects edges"
],
@@ -52,10 +52,10 @@ const KruskalQuiz = () => {
{
question: "What is the overall time complexity of Kruskal's algorithm, and what dominates it?",
options: [
- "O(V) — dominated by visiting each vertex once",
- "O(E log E) — dominated by sorting the edge list",
- "O(V²) — dominated by checking every pair of vertices",
- "O(1) — Union-Find makes it constant time"
+ "O(V), dominated by visiting each vertex once",
+ "O(E log E), dominated by sorting the edge list",
+ "O(V²), dominated by checking every pair of vertices",
+ "O(1), since Union-Find makes it constant time"
],
correctAnswer: 1,
explanation: "The Union-Find operations are nearly O(1) each, so sorting all the edges up front is what actually determines the algorithm's overall cost."
diff --git a/app/visualizer/graph/algorithms/prim/animation.jsx b/app/visualizer/graph/algorithms/prim/animation.jsx
index 5fc17ac..c3b8a98 100755
--- a/app/visualizer/graph/algorithms/prim/animation.jsx
+++ b/app/visualizer/graph/algorithms/prim/animation.jsx
@@ -155,7 +155,7 @@ const PrimVisualizer = () => {
setEdges(EXAMPLE_EDGES);
setStartVertex("A");
clearResult();
- setMessage("Loaded an example graph — click Run Prim's");
+ setMessage("Loaded an example graph, click Run Prim's");
};
const reset = () => {
@@ -211,7 +211,7 @@ const PrimVisualizer = () => {
setActiveEdge(null);
setMstEdges(finalMst);
setTotalWeight(tw);
- setMessage(`Prim's complete — minimum spanning tree total weight is ${tw}`);
+ setMessage(`Prim's complete: minimum spanning tree total weight is ${tw}`);
setBusy(false);
}, STEP_DELAY);
}
@@ -320,7 +320,7 @@ const PrimVisualizer = () => {
{totalWeight !== null && (
)}
diff --git a/app/visualizer/graph/algorithms/prim/content.jsx b/app/visualizer/graph/algorithms/prim/content.jsx
index b944ad1..28a896a 100755
--- a/app/visualizer/graph/algorithms/prim/content.jsx
+++ b/app/visualizer/graph/algorithms/prim/content.jsx
@@ -76,10 +76,10 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `Prim's algorithm builds a minimum spanning tree the same way Kruskal's does — greedily, ending up with the cheapest possible set of edges connecting every vertex with no cycles — but it grows outward from a single starting vertex instead of considering edges from the whole graph in sorted order. At every step, the tree adds whichever edge is cheapest among all the edges connecting the current tree to a vertex not yet in it.`,
+ `Prim's algorithm builds a minimum spanning tree the same way Kruskal's does, greedily, ending up with the cheapest possible set of edges connecting every vertex with no cycles, but it grows outward from a single starting vertex instead of considering edges from the whole graph in sorted order. At every step, the tree adds whichever edge is cheapest among all the edges connecting the current tree to a vertex not yet in it.`,
`Rather than sorting the whole edge list up front, Prim's algorithm tracks a "key" value for every vertex outside the tree: the weight of the cheapest edge discovered so far connecting it directly to the tree (infinity if none is known yet). At each step, the algorithm pulls in whichever outside vertex has the smallest key, adds the edge that earned it that key, and then checks whether any of its edges give some other outside vertex an even cheaper way into the (now larger) tree.`,
- `This "key" is deliberately different from Dijkstra's "distance": Dijkstra's distance is the cumulative weight of the entire path from the start, while Prim's key is just the weight of one direct edge into the tree, regardless of how far the tree has traveled to get there. That's exactly why Prim's algorithm finds a minimum spanning tree — cheapest total connections — while Dijkstra finds shortest paths — cheapest cumulative routes. They look almost identical in code, but they're solving genuinely different problems.`,
- `Prim's algorithm tends to be the better choice on dense graphs (many edges relative to vertices), since it never needs to sort the full edge list the way Kruskal's does — with a good priority queue it can outperform Kruskal's as edge count grows. Like Kruskal's, it's used for minimum-cost network design: wiring, piping, or cabling a set of locations together as cheaply as possible.`,
+ `This "key" is deliberately different from Dijkstra's "distance": Dijkstra's distance is the cumulative weight of the entire path from the start, while Prim's key is just the weight of one direct edge into the tree, regardless of how far the tree has traveled to get there. That's exactly why Prim's algorithm finds a minimum spanning tree (cheapest total connections) while Dijkstra finds shortest paths (cheapest cumulative routes). They look almost identical in code, but they're solving genuinely different problems.`,
+ `Prim's algorithm tends to be the better choice on dense graphs (many edges relative to vertices), since it never needs to sort the full edge list the way Kruskal's does: with a good priority queue it can outperform Kruskal's as edge count grows. Like Kruskal's, it's used for minimum-cost network design: wiring, piping, or cabling a set of locations together as cheaply as possible.`,
];
const algorithm = [
@@ -95,8 +95,8 @@ const Content = () => {
];
const complexity = [
- { points: "Time Complexity: O((V + E) log V) with a binary heap priority queue — comparable to Dijkstra's, and often faster than Kruskal's on dense graphs." },
- { points: "Space Complexity: O(V) — for the key array, the parent pointers, and the priority queue." },
+ { points: "Time Complexity: O((V + E) log V) with a binary heap priority queue, comparable to Dijkstra's, and often faster than Kruskal's on dense graphs." },
+ { points: "Space Complexity: O(V), for the key array, the parent pointers, and the priority queue." },
];
return (
diff --git a/app/visualizer/graph/algorithms/prim/quiz.jsx b/app/visualizer/graph/algorithms/prim/quiz.jsx
index 33c2a2b..c30eea7 100755
--- a/app/visualizer/graph/algorithms/prim/quiz.jsx
+++ b/app/visualizer/graph/algorithms/prim/quiz.jsx
@@ -25,12 +25,12 @@ const PrimQuiz = () => {
"The total number of edges it has"
],
correctAnswer: 1,
- explanation: "The key is just one edge's weight — the best direct connection into the tree found so far — not a running total like Dijkstra's distance."
+ explanation: "The key is just one edge's weight (the best direct connection into the tree found so far), not a running total like Dijkstra's distance."
},
{
question: "What is the key difference between Prim's \"key\" and Dijkstra's \"distance\", even though the algorithms look very similar?",
options: [
- "There is no real difference — they compute exactly the same thing",
+ "There is no real difference, they compute exactly the same thing",
"Prim's key is the weight of one direct edge into the tree; Dijkstra's distance is the cumulative weight of the whole path from the start",
"Dijkstra's algorithm doesn't use a key or distance value at all",
"Prim's algorithm only works on unweighted graphs"
@@ -41,13 +41,13 @@ const PrimQuiz = () => {
{
question: "When is a vertex's key value updated during the algorithm?",
options: [
- "Never — keys are fixed once set",
+ "Never, keys are fixed once set",
"Whenever an edge from a vertex just added to the tree offers a cheaper direct connection than the vertex's current key",
"Only at the very start of the algorithm",
"Every time any edge in the graph is examined, regardless of relevance"
],
correctAnswer: 1,
- explanation: "After pulling a vertex into the tree, its edges are checked — if one offers some other outside vertex a cheaper way in than it currently has, that vertex's key improves."
+ explanation: "After pulling a vertex into the tree, its edges are checked; if one offers some other outside vertex a cheaper way in than it currently has, that vertex's key improves."
},
{
question: "On which kind of graph does Prim's algorithm tend to outperform Kruskal's?",
diff --git a/app/visualizer/graph/algorithms/topological-sort/animation.jsx b/app/visualizer/graph/algorithms/topological-sort/animation.jsx
index 52da0ea..2f41238 100755
--- a/app/visualizer/graph/algorithms/topological-sort/animation.jsx
+++ b/app/visualizer/graph/algorithms/topological-sort/animation.jsx
@@ -124,7 +124,7 @@ const TopologicalSortVisualizer = () => {
setVertices(EXAMPLE_VERTICES);
setEdges(EXAMPLE_EDGES);
clearResult();
- setMessage("Loaded an example DAG — click Run Topological Sort");
+ setMessage("Loaded an example DAG, click Run Topological Sort");
};
const reset = () => {
@@ -158,13 +158,13 @@ const TopologicalSortVisualizer = () => {
setProcessed((prev) => new Set(prev).add(step.vertex));
setOrder(step.order);
setActiveEdge(null);
- setMessage(`${step.vertex} has no remaining prerequisites — place it next in the order`);
+ setMessage(`${step.vertex} has no remaining prerequisites, place it next in the order`);
} else if (step.type === "decrement") {
setActiveEdge({ from: step.from, to: step.to });
setInDegrees((prev) => ({ ...prev, [step.to]: step.newDegree }));
- setMessage(`${step.from} is done — ${step.to} now has ${step.newDegree} remaining prerequisite${step.newDegree === 1 ? "" : "s"}`);
+ setMessage(`${step.from} is done, ${step.to} now has ${step.newDegree} remaining prerequisite${step.newDegree === 1 ? "" : "s"}`);
} else if (step.type === "ready") {
- setMessage(`${step.vertex} has no prerequisites left — it's ready to be processed`);
+ setMessage(`${step.vertex} has no prerequisites left, it's ready to be processed`);
}
i++;
@@ -177,7 +177,7 @@ const TopologicalSortVisualizer = () => {
setResult({ order: finalOrder, hasCycle, stuck });
setMessage(
hasCycle
- ? `Cycle detected — ${stuck.join(", ")} could never reach zero remaining prerequisites, so no valid order exists`
+ ? `Cycle detected: ${stuck.join(", ")} could never reach zero remaining prerequisites, so no valid order exists`
: `Topological Sort complete: ${finalOrder.join(" → ")}`
);
setBusy(false);
@@ -271,7 +271,7 @@ const TopologicalSortVisualizer = () => {
{result && (
)}
diff --git a/app/visualizer/graph/algorithms/topological-sort/content.jsx b/app/visualizer/graph/algorithms/topological-sort/content.jsx
index 13c12f4..05905e2 100755
--- a/app/visualizer/graph/algorithms/topological-sort/content.jsx
+++ b/app/visualizer/graph/algorithms/topological-sort/content.jsx
@@ -67,9 +67,9 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `A topological sort takes a directed acyclic graph (a DAG — directed edges, no cycles) and arranges every vertex into a linear order such that, for every edge u → v, u appears before v in that order. Think of each edge as a "must come before" constraint: topological sort finds an order that satisfies every constraint at once. Multiple valid orders can exist for the same graph — the algorithm just needs to find one of them.`,
- `Kahn's algorithm builds the order using each vertex's in-degree — the number of edges pointing into it, which represents "how many prerequisites are left." Any vertex with in-degree 0 has no unmet prerequisites, so it's safe to place next in the order right away. Whenever a vertex is placed, its outgoing edges are "removed" by decrementing the in-degree of everything it points to — which may free up new vertices to become in-degree 0 and get queued themselves.`,
- `That queue-driven process is exactly why the algorithm doubles as a cycle detector: if the graph really is acyclic, every vertex eventually reaches in-degree 0 and gets processed. But if a cycle exists, every vertex in that cycle keeps at least one unmet prerequisite forever — none of them can ever reach in-degree 0, so they're never queued. If the final order doesn't include every vertex, the graph must contain a cycle, and no valid topological order exists at all.`,
+ `A topological sort takes a directed acyclic graph (a DAG, meaning directed edges and no cycles) and arranges every vertex into a linear order such that, for every edge u → v, u appears before v in that order. Think of each edge as a "must come before" constraint: topological sort finds an order that satisfies every constraint at once. Multiple valid orders can exist for the same graph, and the algorithm just needs to find one of them.`,
+ `Kahn's algorithm builds the order using each vertex's in-degree, the number of edges pointing into it, which represents "how many prerequisites are left." Any vertex with in-degree 0 has no unmet prerequisites, so it's safe to place next in the order right away. Whenever a vertex is placed, its outgoing edges are "removed" by decrementing the in-degree of everything it points to, which may free up new vertices to become in-degree 0 and get queued themselves.`,
+ `That queue-driven process is exactly why the algorithm doubles as a cycle detector: if the graph really is acyclic, every vertex eventually reaches in-degree 0 and gets processed. But if a cycle exists, every vertex in that cycle keeps at least one unmet prerequisite forever; none of them can ever reach in-degree 0, so they're never queued. If the final order doesn't include every vertex, the graph must contain a cycle, and no valid topological order exists at all.`,
`Topological sort is the standard tool for scheduling problems with dependencies: build systems compiling files in the right order, package managers installing dependencies before the packages that need them, course prerequisite planning, and task schedulers in project management tools that need to respect "this must finish before that starts" constraints.`,
];
@@ -88,8 +88,8 @@ const Content = () => {
];
const complexity = [
- { points: "Time Complexity: O(V + E) — every vertex is enqueued once and every edge is examined once during in-degree updates." },
- { points: "Space Complexity: O(V) — for the in-degree array and the queue." },
+ { points: "Time Complexity: O(V + E), since every vertex is enqueued once and every edge is examined once during in-degree updates." },
+ { points: "Space Complexity: O(V), for the in-degree array and the queue." },
];
return (
diff --git a/app/visualizer/graph/algorithms/topological-sort/quiz.jsx b/app/visualizer/graph/algorithms/topological-sort/quiz.jsx
index 682c2e4..f9204bf 100755
--- a/app/visualizer/graph/algorithms/topological-sort/quiz.jsx
+++ b/app/visualizer/graph/algorithms/topological-sort/quiz.jsx
@@ -20,12 +20,12 @@ const TopologicalSortQuiz = () => {
question: "What kind of graph can be topologically sorted?",
options: [
"Any undirected graph",
- "A directed acyclic graph (a DAG) — directed edges with no cycles",
+ "A directed acyclic graph (a DAG), meaning directed edges with no cycles",
"Only graphs with exactly one vertex",
"Any weighted graph"
],
correctAnswer: 1,
- explanation: "A cycle would require some vertex to come both before and after another, which is impossible to satisfy in a linear order — so cycles rule out a valid topological sort entirely."
+ explanation: "A cycle would require some vertex to come both before and after another, which is impossible to satisfy in a linear order, so cycles rule out a valid topological sort entirely."
},
{
question: "In Kahn's algorithm, what does a vertex's in-degree represent, and why does in-degree 0 matter?",
@@ -47,18 +47,18 @@ const TopologicalSortQuiz = () => {
"All neighbors are immediately removed from the graph"
],
correctAnswer: 1,
- explanation: "Processing a vertex effectively \"removes\" its outgoing edges, which decreases the in-degree of everything it pointed to — possibly freeing those vertices up to be queued."
+ explanation: "Processing a vertex effectively \"removes\" its outgoing edges, which decreases the in-degree of everything it pointed to, possibly freeing those vertices up to be queued."
},
{
question: "How does Kahn's algorithm detect that a graph contains a cycle?",
options: [
- "It doesn't — cycles cause an infinite loop",
+ "It doesn't, cycles cause an infinite loop",
"If the final output order has fewer vertices than the graph, the remaining vertices never reached in-degree 0, which only happens because of a cycle",
"It checks whether any edge has a negative weight",
"It counts the total number of edges before starting"
],
correctAnswer: 1,
- explanation: "Every vertex in a cycle keeps at least one unmet prerequisite forever, so it never gets queued — an incomplete final order is the tell-tale sign of a cycle."
+ explanation: "Every vertex in a cycle keeps at least one unmet prerequisite forever, so it never gets queued; an incomplete final order is the tell-tale sign of a cycle."
}
];
diff --git a/app/visualizer/graph/representation/adjacency-list/animation.jsx b/app/visualizer/graph/representation/adjacency-list/animation.jsx
index 1660574..d7dcaa6 100755
--- a/app/visualizer/graph/representation/adjacency-list/animation.jsx
+++ b/app/visualizer/graph/representation/adjacency-list/animation.jsx
@@ -17,7 +17,7 @@ const layoutCircle = (vertices) => {
});
};
-// Unlike a matrix, a list only stores entries for edges that actually exist —
+// Unlike a matrix, a list only stores entries for edges that actually exist:
// each vertex's row is exactly as long as its number of neighbors.
const buildAdjList = (vertices, edges) => {
const list = {};
@@ -85,7 +85,7 @@ const AdjacencyListVisualizer = () => {
setRecentEdge({ from: fromVertex, to: toVertex, directed: directedMode });
setSelectedEdge(null);
- setMessage(`Appended ${toVertex} to ${fromVertex}'s list${directedMode ? "" : `, and ${fromVertex} to ${toVertex}'s list — undirected edges are mirrored`}`);
+ setMessage(`Appended ${toVertex} to ${fromVertex}'s list${directedMode ? "" : `, and ${fromVertex} to ${toVertex}'s list, since undirected edges are mirrored`}`);
const chips = document.querySelectorAll(".list-chip-active");
if (chips.length > 0) {
diff --git a/app/visualizer/graph/representation/adjacency-list/content.jsx b/app/visualizer/graph/representation/adjacency-list/content.jsx
index 7ad2f52..6b929b2 100755
--- a/app/visualizer/graph/representation/adjacency-list/content.jsx
+++ b/app/visualizer/graph/representation/adjacency-list/content.jsx
@@ -76,24 +76,24 @@ 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.`,
+ `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: "If the graph is undirected, also append (u, w) to v's list, since 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." },
+ { 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 (
@@ -134,7 +134,7 @@ const Content = () => {
- The same triangle graph — every vertex's list holds only its actual neighbors
+ The same triangle graph: every vertex's list holds only its actual neighbors
diff --git a/app/visualizer/graph/representation/adjacency-list/quiz.jsx b/app/visualizer/graph/representation/adjacency-list/quiz.jsx
index 178906c..692ed82 100755
--- a/app/visualizer/graph/representation/adjacency-list/quiz.jsx
+++ b/app/visualizer/graph/representation/adjacency-list/quiz.jsx
@@ -14,7 +14,7 @@ const AdjacencyListQuiz = () => {
"A single boolean indicating whether it has any edges"
],
correctAnswer: 1,
- explanation: "Unlike a matrix, a list only stores entries for edges that actually exist — no wasted space on non-edges."
+ explanation: "Unlike a matrix, a list only stores entries for edges that actually exist, with no wasted space on non-edges."
},
{
question: "When an undirected edge (u, v) is added, what happens to the lists?",
@@ -29,15 +29,15 @@ const AdjacencyListQuiz = () => {
},
{
question: "What is the space complexity of an adjacency list?",
- options: ["O(V²) always", "O(V + E) — proportional to vertices and edges actually present", "O(1)", "O(E²)"],
+ options: ["O(V²) always", "O(V + E), proportional to vertices and edges actually present", "O(1)", "O(E²)"],
correctAnswer: 1,
explanation: "Space scales with the actual number of vertices and edges, unlike a matrix's fixed O(V²) regardless of edge count."
},
{
question: "What is the time complexity of checking whether an edge (u, v) exists using an adjacency list?",
- options: ["O(1) always", "O(degree(u)) — proportional to how many neighbors u actually has", "O(V²)", "O(log V)"],
+ options: ["O(1) always", "O(degree(u)), proportional to how many neighbors u actually has", "O(V²)", "O(log V)"],
correctAnswer: 1,
- explanation: "Finding v means scanning u's list, so the cost depends on how many neighbors u has — not the size of the whole graph."
+ explanation: "Finding v means scanning u's list, so the cost depends on how many neighbors u has, not the size of the whole graph."
},
{
question: "Why are adjacency lists the default choice for algorithms like BFS, DFS, and Dijkstra's algorithm?",
@@ -48,7 +48,7 @@ const AdjacencyListQuiz = () => {
"Matrices don't support weighted edges"
],
correctAnswer: 1,
- explanation: "Since these algorithms are fundamentally about visiting neighbors, a structure that returns exactly the relevant neighbors — with no wasted scanning — fits naturally, especially on the sparse graphs most real-world problems involve."
+ explanation: "Since these algorithms are fundamentally about visiting neighbors, a structure that returns exactly the relevant neighbors, with no wasted scanning, fits naturally, especially on the sparse graphs most real-world problems involve."
}
];
diff --git a/app/visualizer/graph/representation/adjacency-matrix/animation.jsx b/app/visualizer/graph/representation/adjacency-matrix/animation.jsx
index 8cef924..3e22680 100755
--- a/app/visualizer/graph/representation/adjacency-matrix/animation.jsx
+++ b/app/visualizer/graph/representation/adjacency-matrix/animation.jsx
@@ -18,7 +18,7 @@ const layoutCircle = (vertices) => {
};
// Every edge sets one (or, for undirected edges, two mirrored) cells in the
-// matrix — adding an edge to the edge list and setting a matrix cell are the
+// matrix, adding an edge to the edge list and setting a matrix cell are the
// same operation viewed from two different representations.
const buildMatrix = (vertices, edges) => {
const n = vertices.length;
@@ -89,7 +89,7 @@ const AdjacencyMatrixVisualizer = () => {
setRecentEdge({ from: fromVertex, to: toVertex, directed: directedMode });
setSelectedCell(null);
- setMessage(`Set edge ${fromVertex} → ${toVertex} (weight ${weight})${directedMode ? "" : " — mirrored both ways since the graph is undirected"}`);
+ setMessage(`Set edge ${fromVertex} → ${toVertex} (weight ${weight})${directedMode ? "" : ", mirrored both ways since the graph is undirected"}`);
const bars = document.querySelectorAll(".matrix-cell-active");
if (bars.length > 0) {
@@ -127,7 +127,7 @@ const AdjacencyMatrixVisualizer = () => {
setSelectedCell({ i, j });
setRecentEdge(null);
const matrix = buildMatrix(vertices, edges);
- setMessage(matrix[i][j] !== 0 ? `Cell (${a}, ${b}) = ${matrix[i][j]} — there is an edge from ${a} to ${b}` : `Cell (${a}, ${b}) = 0 — no edge from ${a} to ${b}`);
+ setMessage(matrix[i][j] !== 0 ? `Cell (${a}, ${b}) = ${matrix[i][j]}, there is an edge from ${a} to ${b}` : `Cell (${a}, ${b}) = 0, no edge from ${a} to ${b}`);
};
const positions = layoutCircle(vertices);
diff --git a/app/visualizer/graph/representation/adjacency-matrix/content.jsx b/app/visualizer/graph/representation/adjacency-matrix/content.jsx
index 49bf41e..f6f5084 100755
--- a/app/visualizer/graph/representation/adjacency-matrix/content.jsx
+++ b/app/visualizer/graph/representation/adjacency-matrix/content.jsx
@@ -93,23 +93,23 @@ const Content = () => {
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.`,
+ `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: "If the graph is undirected, also set matrix[v][u] = w, since 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." },
+ { 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 (
@@ -150,7 +150,7 @@ const Content = () => {
- An undirected triangle graph — its matrix is symmetric, and the diagonal stays 0 (no self-loops)
+ An undirected triangle graph: its matrix is symmetric, and the diagonal stays 0 (no self-loops)
diff --git a/app/visualizer/graph/representation/adjacency-matrix/quiz.jsx b/app/visualizer/graph/representation/adjacency-matrix/quiz.jsx
index e6672e3..6a42224 100755
--- a/app/visualizer/graph/representation/adjacency-matrix/quiz.jsx
+++ b/app/visualizer/graph/representation/adjacency-matrix/quiz.jsx
@@ -14,7 +14,7 @@ const AdjacencyMatrixQuiz = () => {
"The degree of vertex i"
],
correctAnswer: 1,
- explanation: "Each cell directly encodes one potential edge — nonzero (often the weight) if that edge exists, zero if it doesn't."
+ explanation: "Each cell directly encodes one potential edge: nonzero (often the weight) if that edge exists, zero if it doesn't."
},
{
question: "Why is an adjacency matrix for an undirected graph always symmetric?",
@@ -35,14 +35,14 @@ const AdjacencyMatrixQuiz = () => {
},
{
question: "What is the time complexity of listing all of a single vertex's neighbors?",
- options: ["O(1)", "O(V) — the entire row must be scanned", "O(log V)", "O(E)"],
+ options: ["O(1)", "O(V), the entire row must be scanned", "O(log V)", "O(E)"],
correctAnswer: 1,
explanation: "Even if a vertex has very few actual neighbors, finding them means scanning across its full row of V cells looking for nonzero entries."
},
{
question: "Why do adjacency matrices waste memory on sparse graphs?",
options: [
- "They don't — memory usage matches the number of edges exactly",
+ "They don't, memory usage matches the number of edges exactly",
"The matrix always uses O(V²) space regardless of edge count, so a graph with far fewer than V² edges stores mostly zeros",
"Sparse graphs can't be represented as matrices at all",
"Matrices only work for graphs with fewer than 10 vertices"
diff --git a/app/visualizer/graph/traversal/bfs/animation.jsx b/app/visualizer/graph/traversal/bfs/animation.jsx
index bd5e29f..389717a 100755
--- a/app/visualizer/graph/traversal/bfs/animation.jsx
+++ b/app/visualizer/graph/traversal/bfs/animation.jsx
@@ -128,7 +128,7 @@ const BfsVisualizer = () => {
setEdges(EXAMPLE_EDGES);
setStartVertex("A");
clearResult();
- setMessage("Loaded an example graph — click Start BFS");
+ setMessage("Loaded an example graph, click Start BFS");
};
const reset = () => {
@@ -158,7 +158,7 @@ const BfsVisualizer = () => {
if (step.type === "enqueue") {
setVisited((prev) => new Set(prev).add(step.vertex));
if (step.from) setActiveEdge({ from: step.from, to: step.vertex });
- setMessage(step.from ? `Discovered ${step.vertex} from ${step.from} — added to the queue` : `Start at ${step.vertex} — added to the queue`);
+ setMessage(step.from ? `Discovered ${step.vertex} from ${step.from}, added to the queue` : `Start at ${step.vertex}, added to the queue`);
} else if (step.type === "visit") {
setCurrent(step.vertex);
setActiveEdge(null);
@@ -166,7 +166,7 @@ const BfsVisualizer = () => {
setMessage(`Dequeue and visit ${step.vertex}`);
} else if (step.type === "skip") {
setActiveEdge({ from: step.from, to: step.vertex });
- setMessage(`${step.vertex} is already visited — skip`);
+ setMessage(`${step.vertex} is already visited, skip`);
}
i++;
diff --git a/app/visualizer/graph/traversal/bfs/content.jsx b/app/visualizer/graph/traversal/bfs/content.jsx
index b6ea32f..75baa31 100755
--- a/app/visualizer/graph/traversal/bfs/content.jsx
+++ b/app/visualizer/graph/traversal/bfs/content.jsx
@@ -54,7 +54,7 @@ const WalkthroughDiagram = () => {
))}
- visit order: A, B, C, D, E — by distance from A
+ visit order: A, B, C, D, E, by distance from A
);
@@ -64,10 +64,10 @@ 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.`,
+ `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, so 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 = [
@@ -79,12 +79,12 @@ const Content = () => {
"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" },
+ { points: "Stop when the queue is empty, since 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." },
+ { points: "Time Complexity: O(V + E), since 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 (
@@ -125,7 +125,7 @@ const Content = () => {
- BFS from A visits every vertex in order of distance — amber, then blue, then emerald
+ BFS from A visits every vertex in order of distance: amber, then blue, then emerald
diff --git a/app/visualizer/graph/traversal/bfs/quiz.jsx b/app/visualizer/graph/traversal/bfs/quiz.jsx
index 4ea2f2b..7317265 100755
--- a/app/visualizer/graph/traversal/bfs/quiz.jsx
+++ b/app/visualizer/graph/traversal/bfs/quiz.jsx
@@ -25,7 +25,7 @@ const BfsQuiz = () => {
"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."
+ 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?",
@@ -53,7 +53,7 @@ const BfsQuiz = () => {
"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."
+ 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."
}
];
diff --git a/app/visualizer/graph/traversal/dfs/animation.jsx b/app/visualizer/graph/traversal/dfs/animation.jsx
index ebf26d8..651a4aa 100755
--- a/app/visualizer/graph/traversal/dfs/animation.jsx
+++ b/app/visualizer/graph/traversal/dfs/animation.jsx
@@ -29,7 +29,7 @@ const buildAdjList = (vertices, edges) => {
// Iterative DFS with an explicit stack: a vertex is only marked visited when
// it's popped, not when it's pushed, so the same vertex can appear in the
-// stack more than once — whichever push gets popped first wins, and any
+// stack more than once, whichever push gets popped first wins, and any
// later pop of an already-visited vertex is simply skipped. Neighbors are
// pushed in reverse so the first neighbor in the list is popped (and
// explored) first, matching the order a recursive DFS would visit them in.
@@ -136,7 +136,7 @@ const DfsVisualizer = () => {
setEdges(EXAMPLE_EDGES);
setStartVertex("A");
clearResult();
- setMessage("Loaded an example graph — click Start DFS");
+ setMessage("Loaded an example graph, click Start DFS");
};
const reset = () => {
@@ -174,9 +174,9 @@ const DfsVisualizer = () => {
setMessage(`Pop and visit ${step.vertex}`);
} else if (step.type === "skip") {
setActiveEdge({ from: step.from, to: step.vertex });
- setMessage(`${step.vertex} is already visited — not pushed again`);
+ setMessage(`${step.vertex} is already visited, not pushed again`);
} else if (step.type === "skip-visited") {
- setMessage(`Pop ${step.vertex} — already visited earlier via another path, skip`);
+ setMessage(`Pop ${step.vertex}, already visited earlier via another path, skip`);
}
i++;
diff --git a/app/visualizer/graph/traversal/dfs/content.jsx b/app/visualizer/graph/traversal/dfs/content.jsx
index 2a1db57..deda98b 100755
--- a/app/visualizer/graph/traversal/dfs/content.jsx
+++ b/app/visualizer/graph/traversal/dfs/content.jsx
@@ -56,7 +56,7 @@ const WalkthroughDiagram = () => {
))}
- visit order: A, B, D, E, C — one path fully explored before backtracking
+ visit order: A, B, D, E, C, one path fully explored before backtracking
);
@@ -66,10 +66,10 @@ 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.`,
+ `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 = [
@@ -82,12 +82,12 @@ const Content = () => {
"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" },
+ { points: "Stop when the stack is empty, since 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." },
+ { points: "Time Complexity: O(V + E), since 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 (
diff --git a/app/visualizer/graph/traversal/dfs/quiz.jsx b/app/visualizer/graph/traversal/dfs/quiz.jsx
index 5a7073c..d9b2857 100755
--- a/app/visualizer/graph/traversal/dfs/quiz.jsx
+++ b/app/visualizer/graph/traversal/dfs/quiz.jsx
@@ -20,7 +20,7 @@ const DfsQuiz = () => {
question: "What data structure does DFS use to control visit order, and why?",
options: [
"A queue, because first-in-first-out order matches DFS's behavior",
- "A stack, because last-in-first-out order means the most recently discovered vertex is explored next — which is exactly 'go deeper'",
+ "A stack, because last-in-first-out order means the most recently discovered vertex is explored next, which is exactly 'go deeper'",
"A priority queue sorted by degree",
"No auxiliary structure is needed"
],
@@ -31,7 +31,7 @@ const DfsQuiz = () => {
question: "In the iterative (explicit stack) version of DFS, when is a vertex marked visited?",
options: [
"The moment it's pushed onto the stack",
- "When it's popped off the stack — not when it's pushed",
+ "When it's popped off the stack, not when it's pushed",
"Only after all its neighbors have been pushed",
"DFS never marks vertices visited"
],
@@ -42,7 +42,7 @@ const DfsQuiz = () => {
question: "How can DFS be implemented without an explicit stack data structure?",
options: [
"It's impossible without an explicit stack",
- "Recursion — the call stack itself plays the role of the stack",
+ "Recursion, since the call stack itself plays the role of the stack",
"By using a queue instead",
"By sorting all vertices first"
],
@@ -58,7 +58,7 @@ const DfsQuiz = () => {
"Sorting an array of numbers"
],
correctAnswer: 1,
- explanation: "DFS's backtracking structure — fully exploring one branch before returning to try another — is exactly what cycle detection and topological sorting rely on."
+ explanation: "DFS's backtracking structure, fully exploring one branch before returning to try another, is exactly what cycle detection and topological sorting rely on."
}
];
diff --git a/app/visualizer/linkedList/operations/merge/content.jsx b/app/visualizer/linkedList/operations/merge/content.jsx
index dd8a0f9..c4952fe 100755
--- a/app/visualizer/linkedList/operations/merge/content.jsx
+++ b/app/visualizer/linkedList/operations/merge/content.jsx
@@ -25,7 +25,7 @@ const Content = () => {
const overview = [
`Merging two linked lists means weaving their nodes together into a single ordered chain. The common technique is a two-pointer walk: look at both current heads, pick whichever is smaller, attach it to the result, and move that pointer forward.`,
- `This trick only pays off when both lists were already sorted going in — you end up with a fully sorted merged list without ever needing to run a separate sort afterward.`
+ `This trick only pays off when both lists were already sorted going in: you end up with a fully sorted merged list without ever needing to run a separate sort afterward.`
];
const mergeSteps = [
diff --git a/app/visualizer/linkedList/operations/reverse/content.jsx b/app/visualizer/linkedList/operations/reverse/content.jsx
index ed2cee5..88fd04e 100755
--- a/app/visualizer/linkedList/operations/reverse/content.jsx
+++ b/app/visualizer/linkedList/operations/reverse/content.jsx
@@ -24,7 +24,7 @@ const Content = () => {
}, []);
const overview = [
- `Reversing a linked list is really just flipping every pointer so it points backward instead of forward — once that's done, what used to be the tail is now the head.`,
+ `Reversing a linked list is really just flipping every pointer so it points backward instead of forward. Once that's done, what used to be the tail is now the head.`,
`It shows up constantly as a building block in other algorithms, and it's the go-to way to process a list back-to-front without allocating any extra space for a copy.`
];
diff --git a/app/visualizer/linkedList/operations/traversal/content.jsx b/app/visualizer/linkedList/operations/traversal/content.jsx
index b8d7d7f..f5da810 100755
--- a/app/visualizer/linkedList/operations/traversal/content.jsx
+++ b/app/visualizer/linkedList/operations/traversal/content.jsx
@@ -25,7 +25,7 @@ const Content = () => {
const overview = [
`Traversing a linked list is simply following the chain: start at the head, visit each node once, and keep hopping to the next pointer until you hit the end.`,
- `It sounds trivial, but it's the mechanism underneath almost everything else you'd do with a list — searching for a value, printing every element, or running any per-node logic all rely on visiting nodes in order.`,
+ `It sounds trivial, but it's the mechanism underneath almost everything else you'd do with a list: searching for a value, printing every element, or running any per-node logic all rely on visiting nodes in order.`,
`It's worth getting comfortable with early, since deletion, reversal, and cycle detection all build directly on the same idea of walking the list node by node.`,
];
@@ -124,7 +124,7 @@ const Content = () => {
))}
- Key Insight: Traversal is the basis for all linked list operations—ensure you visit every node, and beware of cycles that can cause infinite loops.
+ Key Insight: Traversal is the basis for all linked list operations: ensure you visit every node, and beware of cycles that can cause infinite loops.
diff --git a/app/visualizer/linkedList/types/circular/content.jsx b/app/visualizer/linkedList/types/circular/content.jsx
index 8b2d647..97f5be6 100755
--- a/app/visualizer/linkedList/types/circular/content.jsx
+++ b/app/visualizer/linkedList/types/circular/content.jsx
@@ -131,9 +131,9 @@ const Content = () => {
}, []);
const overview = [
- `Take a regular linked list and change one thing: instead of the last node pointing to null, have it point right back to the first node. That's a circular linked list — a loop with no real end.`,
+ `Take a regular linked list and change one thing: instead of the last node pointing to null, have it point right back to the first node. That's a circular linked list, a loop with no real end.`,
`It can be built either as a singly-linked loop (one pointer per node) or a doubly-linked loop (two pointers per node). Because the chain never terminates, it's a natural fit for anything that needs to cycle repeatedly, like round-robin scheduling or a circular buffer.`,
- `Since there's no fixed "first" or "last" node anymore, you can start traversing from anywhere in the loop and eventually visit every node — handy for problems that are inherently cyclic rather than linear.`,
+ `Since there's no fixed "first" or "last" node anymore, you can start traversing from anywhere in the loop and eventually visit every node, handy for problems that are inherently cyclic rather than linear.`,
];
const basicOperations = [
diff --git a/app/visualizer/linkedList/types/doubly/content.jsx b/app/visualizer/linkedList/types/doubly/content.jsx
index 5318485..9890f15 100755
--- a/app/visualizer/linkedList/types/doubly/content.jsx
+++ b/app/visualizer/linkedList/types/doubly/content.jsx
@@ -24,7 +24,7 @@ const Content = () => {
}, []);
const overview = [
- `A doubly linked list gives every node two pointers instead of one — one pointing forward to the next node, and one pointing backward to the previous node. That extra backward link is what lets you walk the list in either direction.`,
+ `A doubly linked list gives every node two pointers instead of one: one pointing forward to the next node, and one pointing backward to the previous node. That extra backward link is what lets you walk the list in either direction.`,
`Because both a head and tail pointer are kept, you get O(1) access at either end. The chain of "next" pointers reads the list forward, while the chain of "previous" pointers reads it backward.`,
`The tradeoff is straightforward: you pay for an extra pointer per node in memory, but in exchange you get backward traversal and fast operations at both ends, which a singly linked list can't offer as cheaply.`,
];
diff --git a/app/visualizer/linkedList/types/singly/content.jsx b/app/visualizer/linkedList/types/singly/content.jsx
index 3c70ccb..24aae46 100755
--- a/app/visualizer/linkedList/types/singly/content.jsx
+++ b/app/visualizer/linkedList/types/singly/content.jsx
@@ -7,7 +7,7 @@ import InContentAd from "@/app/components/ads/InContentAd";
const ListDiagram = ({ nodes, highlight, keyPrefix }) => {
const boxSize = 40;
const gap = 36;
- const startX = 40;
+ const startX = 60;
const topPadding = 22;
const width = startX + Math.max(nodes.length, 1) * (boxSize + gap) + 34;
const height = boxSize + topPadding + 18;
@@ -35,7 +35,7 @@ const ListDiagram = ({ nodes, highlight, keyPrefix }) => {
head
-
+
null
@@ -61,7 +61,7 @@ const ListDiagram = ({ nodes, highlight, keyPrefix }) => {
head
{
}, []);
const overview = [
- `A singly linked list is a chain of nodes where each node holds a value and a single pointer to the node after it. There's no fixed size to worry about — nodes are created and linked in as needed, which is what makes insertion and deletion so cheap compared to an array.`,
+ `A singly linked list is a chain of nodes where each node holds a value and a single pointer to the node after it. There's no fixed size to worry about: nodes are created and linked in as needed, which is what makes insertion and deletion so cheap compared to an array.`,
`A head pointer marks where the chain starts, and the last node's pointer is simply null, marking where it ends. Adding or removing right at the head is O(1), but reaching some node in the middle means walking node-by-node from the start, which costs O(n).`,
`It's one of the simplest data structures around, which is exactly why it shows up as the foundation for stacks, queues, and even graph adjacency lists.`,
];
+ const memoryNotes = [
+ `Unlike an array, a linked list's nodes aren't stored in one contiguous block of memory. Each node is allocated separately, wherever the runtime finds room, and gets linked to the next one purely through its pointer, so the "list" only exists because of those pointers, not because of physical ordering in memory.`,
+ `That's the trade-off worth knowing: arrays are cache-friendly because reading array[i] and array[i+1] usually pulls both into the same cache line, while a linked list's scattered nodes mean each traversal step is likely a cache miss. It's part of why linked lists, despite matching or beating arrays on paper for insertion and deletion, can still lose to arrays in practice for pure iteration.`,
+ ];
+
+ const tailPointerNote = `A plain singly linked list only tracks head, which is why "Insertion at Tail" costs O(n): you have to walk the whole chain to find the last node before you can attach a new one. A common optimization is to also keep a tail pointer that always points at the last node. With that in hand, insertion at the tail drops to O(1) too, at the cost of a little extra bookkeeping (the tail pointer has to be updated on every tail insertion and, trickier, on deleting the last node).`;
+
+ const insertionAtTail = {
+ before: ["A", "B"],
+ after: ["A", "B", "C"],
+ highlight: 2,
+ };
+
+ const searchNote = `Searching means starting at head and following next pointers one node at a time, comparing each node's value against the target, until either a match is found or the chain runs out (next is null). There's no way to jump ahead or look backward, so in the worst case (value at the end, or not present at all) every node gets visited, making search O(n) just like deletion by value.`;
+
const basicOperations = [
{ name: "Insertion at Head", complexity: "O(1)", description: "Add new node at beginning by updating head pointer" },
{ name: "Insertion at Tail", complexity: "O(n)", description: "Traverse to end and add new node (O(1) with tail pointer)" },
@@ -198,6 +213,18 @@ const Content = () => {
+ {/* Memory Representation */}
+
+
+ Without a tail pointer, adding a node at the end means walking the whole chain first to find the current last node, then attaching the new one after it. That traversal is what makes this O(n) instead of O(1).
+
diff --git a/app/visualizer/queue/implementation/array/content.jsx b/app/visualizer/queue/implementation/array/content.jsx
index b629744..767c662 100755
--- a/app/visualizer/queue/implementation/array/content.jsx
+++ b/app/visualizer/queue/implementation/array/content.jsx
@@ -8,7 +8,7 @@ const Content = () => {
const paragraph = [
`The simplest way to build a queue is to back it with an array and track a front index and a rear index. As long as those two indices are updated correctly on every enqueue and dequeue, the array behaves like a proper FIFO queue.`,
- `The catch with a plain array is wasted space at the front once you've dequeued a few elements — the circular-array trick fixes that by letting the rear index wrap back around to index 0 once it hits the end.`,
+ `The catch with a plain array is wasted space at the front once you've dequeued a few elements: the circular-array trick fixes that by letting the rear index wrap back around to index 0 once it hits the end.`,
`Queues are widely used in scenarios like printer job scheduling, call center systems, and network packet handling where order preservation is crucial.`,
];
diff --git a/app/visualizer/queue/implementation/linkedList/content.jsx b/app/visualizer/queue/implementation/linkedList/content.jsx
index 7cb903e..b07f1ec 100755
--- a/app/visualizer/queue/implementation/linkedList/content.jsx
+++ b/app/visualizer/queue/implementation/linkedList/content.jsx
@@ -7,7 +7,7 @@ const Content = () => {
const { theme } = useTheme();
const paragraph = [
- `Building a queue on top of a linked list sidesteps the fixed-capacity problem that array-backed queues have — nodes get allocated on demand, so the queue can keep growing as long as there's memory available.`,
+ `Building a queue on top of a linked list sidesteps the fixed-capacity problem that array-backed queues have: nodes get allocated on demand, so the queue can keep growing as long as there's memory available.`,
`Two pointers are all it takes to manage it: a front pointer that tracks the node to dequeue next, and a rear pointer that tracks where new nodes get attached during an enqueue.`,
`Linked list queues are particularly useful when the maximum size isn't known in advance or when frequent insertions/deletions are required.`,
];
diff --git a/app/visualizer/queue/operations/enqueue-dequeue/content.jsx b/app/visualizer/queue/operations/enqueue-dequeue/content.jsx
index ed47e29..583f454 100755
--- a/app/visualizer/queue/operations/enqueue-dequeue/content.jsx
+++ b/app/visualizer/queue/operations/enqueue-dequeue/content.jsx
@@ -4,11 +4,111 @@ 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";
+
+const QueueDiagram = ({ values, highlight, keyPrefix }) => {
+ const boxSize = 40;
+ const gap = 8;
+ const paddingX = 8;
+ const topPadding = 34;
+ const width = Math.max(values.length, 1) * (boxSize + gap) - gap + paddingX * 2;
+ const height = boxSize + topPadding + 10;
+
+ const boxX = (idx) => paddingX + idx * (boxSize + gap);
+ const boxY = topPadding;
+ const cx = (idx) => boxX(idx) + boxSize / 2;
+
+ const colorFor = (idx) => (highlight === idx ? "#10b981" : "#3b82f6");
+
+ if (values.length === 0) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+};
+
const Content = () => {
const { theme } = useTheme();
const paragraph = [
- `A queue works exactly like a line of people waiting: whoever joins first at the back gets served first at the front. In data-structure terms, new items go in at the rear through enqueue, and items come out from the front through dequeue — first in, first out.`,
+ `A queue works exactly like a line of people waiting: whoever joins first at the back gets served first at the front. In data-structure terms, new items go in at the rear through enqueue, and items come out from the front through dequeue, first in, first out.`,
`The space complexity is O(n) where n is the number of elements in the queue, as it needs to store all elements.`,
`Queues are fundamental in computer science and are used in various applications like CPU scheduling, disk scheduling, handling interrupts, breadth-first search, and any scenario where you need to maintain order of processing.`,
];
@@ -20,12 +120,6 @@ const Content = () => {
{ points : "Add the new element at the rear position" },
];
- const opeartionEnqueue = [
- { points : "Before Enqueue: [10, 20, 30]" },
- { points : "Enqueue(40): Add 40 to the rear" },
- { points : "Enqueue(40): Add 40 to the rear" },
- ];
-
const dequeue = [
{ points : "Check if the queue is empty" },
{ points : "If empty, return underflow error" },
@@ -34,12 +128,6 @@ const Content = () => {
{ points : "Return the accessed data" },
];
- const operationDequeue = [
- { points : "Before Dequeue: [10, 20, 30, 40]" },
- { points : "Dequeue(): Remove and return 10" },
- { points : "After Dequeue: [20, 30, 40]" },
- ];
-
const complexity = [
{ points : "Enqueue Operation: O(1) - Constant time to add to the end" },
{ points : "Dequeue Operation: O(1) - Constant time to remove from the front" },
@@ -73,17 +161,15 @@ const Content = () => {
- Enqueue adds an element to the end (rear) of the queue. Example with queue: [10, 20, 30]
+ Enqueue adds an element to the end (rear) of the queue. The front pointer never moves, and the new element becomes the new rear.
-
-
- {opeartionEnqueue.map((item, index) => (
-
- {item.points}
-
- ))}
-
-
+
+
+
+
↓ enqueue(40) ↓
+
+
+
The new element always goes to the end of the queue.
@@ -98,20 +184,15 @@ const Content = () => {
- Dequeue removes and returns the element from the front (head) of the queue.
-
-
- Example with queue: [10, 20, 30, 40]
+ Dequeue removes and returns the element from the front (head) of the queue. The rear pointer never moves, and whichever element was second in line becomes the new front.
-
-
- {operationDequeue.map((item, index) => (
-
- {item.points}
-
- ))}
-
-
+
+
+
+
↓ dequeue() → returns 10 ↓
+
+
+
The oldest element (first one added) is always removed first.
@@ -213,4 +294,4 @@ const Content = () => {
);
};
- export default content;
\ No newline at end of file
+ export default Content;
\ No newline at end of file
diff --git a/app/visualizer/queue/operations/isfull/content.jsx b/app/visualizer/queue/operations/isfull/content.jsx
index eb93722..937347c 100755
--- a/app/visualizer/queue/operations/isfull/content.jsx
+++ b/app/visualizer/queue/operations/isfull/content.jsx
@@ -7,7 +7,7 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `isFull only matters for queues with a hard capacity limit, like array-backed ones. It tells you true when there's no room left for another enqueue, and false when there's still space — a guard that stops you from writing past the end of the underlying array.`,
+ `isFull only matters for queues with a hard capacity limit, like array-backed ones. It tells you true when there's no room left for another enqueue, and false when there's still space: a guard that stops you from writing past the end of the underlying array.`,
`The isFull operation is critical for robust queue implementations in fixed-capacity scenarios, ensuring data integrity by preventing buffer overflow conditions in system programming and embedded applications.`,
];
diff --git a/app/visualizer/queue/operations/peek-front/content.jsx b/app/visualizer/queue/operations/peek-front/content.jsx
index 7c87e99..19a2268 100755
--- a/app/visualizer/queue/operations/peek-front/content.jsx
+++ b/app/visualizer/queue/operations/peek-front/content.jsx
@@ -7,7 +7,7 @@ const Content = () => {
const { theme } = useTheme();
const paragraph = [
- `Peek front (sometimes just called front) lets you look at whatever's sitting at the head of the queue — the next thing due to be dequeued — without actually taking it out. Nothing about the queue changes; you're just reading its current state.`,
+ `Peek front (sometimes just called front) lets you look at whatever's sitting at the head of the queue (the next thing due to be dequeued) without actually taking it out. Nothing about the queue changes; you're just reading its current state.`,
`The peek front operation is essential for non-destructive queue inspection, enabling more flexible queue processing patterns while maintaining FIFO order. It's particularly valuable in scenarios where decision-making depends on the next item's properties without committing to its removal.`,
];
diff --git a/app/visualizer/queue/types/circular/content.jsx b/app/visualizer/queue/types/circular/content.jsx
index cbbe9c0..db48832 100755
--- a/app/visualizer/queue/types/circular/content.jsx
+++ b/app/visualizer/queue/types/circular/content.jsx
@@ -7,7 +7,7 @@ const Content = () => {
const { theme } = useTheme();
const paragraph = [
- `A circular queue takes a regular array-based queue and wraps its rear index back to the beginning once it hits the end — instead of a straight line, the underlying array is treated like a loop.`,
+ `A circular queue takes a regular array-based queue and wraps its rear index back to the beginning once it hits the end: instead of a straight line, the underlying array is treated like a loop.`,
`That one change fixes the biggest annoyance with a plain array queue: slots freed up by earlier dequeues no longer go to waste. It keeps every operation running in constant time, which is why circular queues show up so often in fixed-size buffers, like the ones used in low-level or real-time systems.`,
];
@@ -221,4 +221,4 @@ const Content = () => {
);
};
- export default content;
\ No newline at end of file
+ export default Content;
\ No newline at end of file
diff --git a/app/visualizer/queue/types/deque/content.jsx b/app/visualizer/queue/types/deque/content.jsx
index 571d490..3804b32 100755
--- a/app/visualizer/queue/types/deque/content.jsx
+++ b/app/visualizer/queue/types/deque/content.jsx
@@ -7,7 +7,7 @@ const Content = () => {
const { theme } = useTheme();
const paragraph = [
- `A deque drops the "only one end" restriction of a normal queue — you can insert or remove elements at both the front and the rear, and both directions stay O(1).`,
+ `A deque drops the "only one end" restriction of a normal queue: you can insert or remove elements at both the front and the rear, and both directions stay O(1).`,
`Because it can act like a stack from one end and a queue from the other, a deque is genuinely a hybrid of the two. That flexibility is exactly why it turns up in algorithms that need fast access to both ends of a dataset at once.`,
];
diff --git a/app/visualizer/queue/types/priority/content.jsx b/app/visualizer/queue/types/priority/content.jsx
index 126a4c7..33ac8bc 100755
--- a/app/visualizer/queue/types/priority/content.jsx
+++ b/app/visualizer/queue/types/priority/content.jsx
@@ -7,8 +7,8 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `A priority queue throws out the "first come, first served" rule that a normal queue follows. Every element carries a priority, and whichever element has the most urgent priority gets dequeued next — it doesn't matter how long it's been sitting there.`,
- `What makes it so useful is that it always hands you the most important item on demand, which is exactly what a lot of algorithms need. Under the hood it's usually built on a heap, though a balanced BST works too — which one you pick depends on how the application balances insertion speed against extraction speed.`,
+ `A priority queue throws out the "first come, first served" rule that a normal queue follows. Every element carries a priority, and whichever element has the most urgent priority gets dequeued next; it doesn't matter how long it's been sitting there.`,
+ `What makes it so useful is that it always hands you the most important item on demand, which is exactly what a lot of algorithms need. Under the hood it's usually built on a heap, though a balanced BST works too, and which one you pick depends on how the application balances insertion speed against extraction speed.`,
];
const characteristic = [
diff --git a/app/visualizer/queue/types/singleEnded/content.jsx b/app/visualizer/queue/types/singleEnded/content.jsx
index 5e15842..39a4f83 100755
--- a/app/visualizer/queue/types/singleEnded/content.jsx
+++ b/app/visualizer/queue/types/singleEnded/content.jsx
@@ -7,7 +7,7 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `A single-ended queue is what most people just mean when they say "queue" — insertion only happens at the rear, removal only happens at the front, and that one-directional flow is what keeps the ordering strictly first-in, first-out.`,
+ `A single-ended queue is what most people just mean when they say "queue": insertion only happens at the rear, removal only happens at the front, and that one-directional flow is what keeps the ordering strictly first-in, first-out.`,
`That predictability is the whole point. Plenty of algorithms and system designs depend on knowing that items get processed in exactly the order they arrived, and a single-ended queue is the simplest structure that guarantees it.`,
];
diff --git a/app/visualizer/searching/linearsearch/content.jsx b/app/visualizer/searching/linearsearch/content.jsx
index dca0f8e..ae2a126 100755
--- a/app/visualizer/searching/linearsearch/content.jsx
+++ b/app/visualizer/searching/linearsearch/content.jsx
@@ -98,7 +98,7 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `Linear Search doesn't assume anything about the list's order — it just starts at the beginning and checks one element at a time until it either finds a match or runs out of elements to check. Simple, but it works on any list, sorted or not.`,
+ `Linear Search doesn't assume anything about the list's order. It just starts at the beginning and checks one element at a time until it either finds a match or runs out of elements to check. Simple, but it works on any list, sorted or not.`,
`Imagine you have a list of numbers: [5, 3, 8, 1, 9] and you want to find the number 8.`,
`If the number is not in the list (e.g., searching for 10), the search ends without success.`,
`Linear Search is easy to understand but can be slow for large lists compared to faster methods like Binary Search.`,
diff --git a/app/visualizer/sorting/insertionsort/content.jsx b/app/visualizer/sorting/insertionsort/content.jsx
index 45e27b1..2973d3a 100755
--- a/app/visualizer/sorting/insertionsort/content.jsx
+++ b/app/visualizer/sorting/insertionsort/content.jsx
@@ -140,7 +140,7 @@ const Content = () => {
const paragraph = [
- `Insertion Sort grows a sorted section of the array one element at a time, which is basically how most people sort a hand of playing cards — pick up the next card and slide it into the spot where it belongs among the cards you've already arranged.`,
+ `Insertion Sort grows a sorted section of the array one element at a time, which is basically how most people sort a hand of playing cards: pick up the next card and slide it into the spot where it belongs among the cards you've already arranged.`,
`The algorithm maintains a "sorted sublist" that grows with each iteration.`,
`Insertion Sort is often used when the data is nearly sorted (where it approaches O(n) time) or when the dataset is small. Some hybrid algorithms like TimSort use Insertion Sort for small subarrays due to its low overhead.`,
];
diff --git a/app/visualizer/sorting/quicksort/content.jsx b/app/visualizer/sorting/quicksort/content.jsx
index 4d89699..216ad36 100755
--- a/app/visualizer/sorting/quicksort/content.jsx
+++ b/app/visualizer/sorting/quicksort/content.jsx
@@ -206,7 +206,7 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `Quick Sort picks one element as a 'pivot' and uses it to split the rest of the array into two groups — everything smaller goes left of the pivot, everything larger goes right. Each of those groups then gets the same treatment recursively, until the whole array falls into place.`,
+ `Quick Sort picks one element as a 'pivot' and uses it to split the rest of the array into two groups: everything smaller goes left of the pivot, everything larger goes right. Each of those groups then gets the same treatment recursively, until the whole array falls into place.`,
`The log n factor comes from the division steps when partitions are balanced. The n² occurs when the pivot selection consistently creates unbalanced partitions.`,
`Quick Sort is O(log n) space complexity for the call stack in the average case, but can degrade to O(n) in the worst case with unbalanced partitions. It is generally considered an in-place algorithm as it doesn't require significant additional space.`,
`Quick Sort is the algorithm of choice for most standard library sorting implementations (like C's qsort, Java's Arrays.sort for primitives) due to its excellent average-case performance. It's particularly effective for large datasets that fit in memory.`,
diff --git a/app/visualizer/stack/implementation/usingArray/content.jsx b/app/visualizer/stack/implementation/usingArray/content.jsx
index bbb7bf1..4263a22 100755
--- a/app/visualizer/stack/implementation/usingArray/content.jsx
+++ b/app/visualizer/stack/implementation/usingArray/content.jsx
@@ -1,50 +1,94 @@
"use client";
import React from "react";
-import { motion } from "framer-motion";
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";
+
+const CAPACITY = 6;
+
+const StackArrayDiagram = ({ values, keyPrefix }) => {
+ const boxSize = 40;
+ const gap = 8;
+ const paddingX = 8;
+ const topPadding = 28;
+ const width = CAPACITY * (boxSize + gap) - gap + paddingX * 2;
+ const height = boxSize + topPadding + 22;
+
+ const boxX = (idx) => paddingX + idx * (boxSize + gap);
+ const boxY = topPadding;
+ const cx = (idx) => boxX(idx) + boxSize / 2;
+ const topIndex = values.length - 1;
+
+ return (
+
+ );
+};
+
const Content = () => {
const { theme } = useTheme();
- const push = [
- { points: "Check if stack is full" },
- { points: 'If full, return "Stack Overflow"' },
- { points: "Increment top pointer" },
- { points: "Store element at array[top]" },
- ];
-
- const pop = [
- { points: "Check if stack is empty" },
- { points: 'If empty, return "Stack Underflow"' },
- { points: "Access element at array[top]" },
- { points: "Decrement top pointer" },
- { points: "Return the element" },
- ];
-
- const peek = [
- { points: "Check if stack is empty" },
- { points: "If empty, return null" },
- { points: "Return array[top] without removal" },
- ];
-
- const isEmpty = [
- { points: "Return true if top pointer is -1" },
- { points: "Return false otherwise" },
- ];
-
- const initialize = [
- { points: "Create an empty array to store elements" },
- { points: "Initialize top pointer/index to -1" },
- { points: "Optional: Set maximum size limit" },
- ];
-
- const isFull = [
- { points: "Return true if top equals (max_size - 1)" },
- { points: "Return false otherwise" },
- ];
-
return (
@@ -60,85 +104,84 @@ const Content = () => {
- A stack follows LIFO — Last In, First Out — meaning whatever you pushed most recently is the first thing that comes back out. Backing it with an array is the most direct way to build one, since push and pop just work on the array's last index in constant time.
+ A stack follows LIFO (Last In, First Out), meaning whatever you pushed most recently is the first thing that comes back out. Backing it with an array is the most direct way to build one, since push and pop just work on the array's last index in constant time.
+ If the array isn't already at capacity, the top pointer is incremented first, then the new value is written at that index, so top always marks the most recently added element.
+
+ The element at array[top] is read and returned, then the top pointer is decremented; the value itself is left in the array, just no longer considered part of the stack.
+
+
+
+
↓ pop() → returns 7 ↓
+
+
+
+
+
+
+
+ peek()
+
+
+ Returns array[top] without touching the pointer, so the stack is left exactly as it was, useful for checking what's on top before deciding whether to pop.
+
+
+
+
+
+
+
+
+
+ isEmpty() & isFull()
+
+
+ Both are just pointer comparisons: isEmpty() is true when top equals -1, and isFull() is true when top reaches the array's last valid index.
+
+
+
+
isEmpty() → true
+
+
+
+
isFull() → true
+
diff --git a/app/visualizer/stack/implementation/usingLinkedList/content.jsx b/app/visualizer/stack/implementation/usingLinkedList/content.jsx
index 5940557..12dac3f 100755
--- a/app/visualizer/stack/implementation/usingLinkedList/content.jsx
+++ b/app/visualizer/stack/implementation/usingLinkedList/content.jsx
@@ -6,63 +6,125 @@ import React from "react";
import { useTheme } from "@/app/contexts/ThemeContext";
import InContentAd from "@/app/components/ads/InContentAd";
+const StackListDiagram = ({ nodes, highlight, keyPrefix }) => {
+ const boxSize = 40;
+ const gap = 36;
+ const startX = 56;
+ const topPadding = 22;
+ const width = startX + Math.max(nodes.length, 1) * (boxSize + gap) + 34;
+ const height = boxSize + topPadding + 18;
+
+ const boxX = (idx) => startX + idx * (boxSize + gap);
+ const boxY = topPadding;
+ const cy = boxY + boxSize / 2;
+
+ const colorFor = (idx) => (highlight === idx ? "#10b981" : "#3b82f6");
+
+ if (nodes.length === 0) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+};
+
const Content = () => {
const { theme } = useTheme();
const paragraph = [
- `Building a stack with a linked list instead of an array gets you the same LIFO (Last In, First Out) behavior, but without a fixed capacity — every push allocates a fresh node, so the stack can keep growing as long as memory allows.`,
- ];
-
- const opeartions = [
- {
- points: "Initialize Stack",
- subpoints: [
- "Create a head pointer initialized to null.",
- "Optional: Maintain a size counter initialized to 0.",
- ],
- },
- {
- points: "push()",
- subpoints: [
- "Create a new node with the given data.",
- "Set new node's next pointer to current head.",
- "Update head to point to the new node.",
- "Increment size counter (if maintained).",
- ],
- },
- {
- points: "pop()",
- subpoints: [
- "Check if stack is empty (head is null).",
- `If empty, return "Stack Underflow".`,
- "Store current head node in a temporary variabl.",
- "Update head to point to the next node.",
- "Decrement size counter (if maintained).",
- "Return data from the temporary node.",
- ],
- },
- ];
-
- const helper = [
- {
- points: "peek()",
- subpoints: [
- "Check if stack is empty (head is null).",
- "If empty, return null.",
- "Return data from head node without removal.",
- ],
- },
- {
- points: "isEmpty()",
- subpoints: ["Return true if head is null.", "Return false otherwise."],
- },
- {
- points: "size()",
- subpoints: [
- "If size counter is maintained, return its value.",
- "Otherwise, traverse the list and count nodes.",
- ],
- },
+ `Building a stack with a linked list instead of an array gets you the same LIFO (Last In, First Out) behavior, but without a fixed capacity: every push allocates a fresh node, so the stack can keep growing as long as memory allows.`,
];
return (
@@ -85,79 +147,78 @@ const Content = () => {
- {/* Algorithmic Steps */}
+ {/* Initialize */}
- Algorithmic Steps
+ Initialize
+
+ A top pointer is created and set to null, meaning there are no nodes yet. Some implementations also keep a size counter, initialized to 0, so size() doesn't need to walk the whole list.
+
+
+
+
+
-
- {/* Stack Basic Operations */}
-
-
- Stack Basic Operations
-
+ {/* push() */}
+
+
+
+ push()
+
+
+ A new node is created pointing to whatever top currently points to, then top is repointed to the new node. Nothing else in the list is touched, which is why this runs in O(1).
+
+ If top is null there's nothing to remove, so pop reports "Stack Underflow". Otherwise the data at top is saved, top is moved to point at the next node, and the saved data is returned. The old top node itself is left for garbage collection.
+
+
+
+
↓ pop() → returns 7 ↓
+
+
+
- {/* Stack Helper Operations */}
-
-
- Stack Helper Operations
-
+ {/* peek() */}
+
+
+
+ peek()
+
+
+ Returns the data at the top node without moving the top pointer, so the stack is left exactly as it was. If top is null, it returns null instead.
+
+ isEmpty() is just a null check on top. size() is O(1) if a counter is maintained on every push/pop, or O(n) if it has to walk the whole list counting nodes instead.
+
- );
-};
-
-export default StackVisualizer;
\ No newline at end of file
+export default StackVisualizer;
diff --git a/app/visualizer/stack/isempty/content.jsx b/app/visualizer/stack/isempty/content.jsx
index d41df60..b5b666b 100755
--- a/app/visualizer/stack/isempty/content.jsx
+++ b/app/visualizer/stack/isempty/content.jsx
@@ -8,7 +8,7 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `isEmpty just tells you whether there's anything on the stack at all. It exists so you can guard pop() and peek() calls — checking first avoids trying to read or remove from a stack that has nothing in it.`,
+ `isEmpty just tells you whether there's anything on the stack at all. It exists so you can guard pop() and peek() calls: checking first avoids trying to read or remove from a stack that has nothing in it.`,
`The isEmpty operation is a simple but crucial part of stack implementation, ensuring safe stack manipulation and preventing runtime errors.`,
];
@@ -191,4 +191,4 @@ const Content = () => {
);
};
- export default content;
\ No newline at end of file
+ export default Content;
\ No newline at end of file
diff --git a/app/visualizer/stack/isfull/content.jsx b/app/visualizer/stack/isfull/content.jsx
index ee3ccd6..00f6293 100755
--- a/app/visualizer/stack/isfull/content.jsx
+++ b/app/visualizer/stack/isfull/content.jsx
@@ -8,7 +8,7 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `isFull tells you whether a stack has run out of room to accept another push. It only really matters for a fixed-capacity stack like an array-backed one — a linked-list stack can just keep allocating nodes, so it rarely needs this check.`,
+ `isFull tells you whether a stack has run out of room to accept another push. It only really matters for a fixed-capacity stack like an array-backed one; a linked-list stack can just keep allocating nodes, so it rarely needs this check.`,
`The Is Full operation is crucial when working with fixed-size stacks to prevent overflow errors. While not needed for dynamically-sized stacks, it's an essential safety check in many system-level implementations.`,
];
diff --git a/app/visualizer/stack/peek/content.jsx b/app/visualizer/stack/peek/content.jsx
index 3f70e6f..aa3cf60 100755
--- a/app/visualizer/stack/peek/content.jsx
+++ b/app/visualizer/stack/peek/content.jsx
@@ -8,7 +8,7 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `Peek gives you a look at whatever's currently on top of the stack, but it leaves the stack exactly as it was — nothing gets popped.`,
+ `Peek gives you a look at whatever's currently on top of the stack, but it leaves the stack exactly as it was: nothing gets popped.`,
`The peek operation is useful when you need to inspect the top element before deciding whether to pop it or push another element onto the stack.`,
];
diff --git a/app/visualizer/stack/polish/postfix/content.jsx b/app/visualizer/stack/polish/postfix/content.jsx
index 0f473e6..a3acfcd 100755
--- a/app/visualizer/stack/polish/postfix/content.jsx
+++ b/app/visualizer/stack/polish/postfix/content.jsx
@@ -8,7 +8,7 @@ const Content = () => {
const paragraph = [
`In postfix notation, also known as Reverse Polish Notation, you write the operator right after its two operands instead of between them.`,
- `So the everyday expression 3 + 4 becomes 3 4 + once converted. There's no ambiguity about order of operations here — the position of each operator in the string already tells you exactly when to apply it, so parentheses become unnecessary.`,
+ `So the everyday expression 3 + 4 becomes 3 4 + once converted. There's no ambiguity about order of operations here: the position of each operator in the string already tells you exactly when to apply it, so parentheses become unnecessary.`,
`Note: Higher precedence means the operation will happen first. When operators have equal precedence, they are evaluated left-to-right (except for exponentiation which is right-to-left).`,
];
@@ -169,4 +169,4 @@ const Content = () => {
);
};
- export default content;
\ No newline at end of file
+ export default Content;
\ No newline at end of file
diff --git a/app/visualizer/stack/push-pop/animation.jsx b/app/visualizer/stack/push-pop/animation.jsx
index 652be02..afe6672 100755
--- a/app/visualizer/stack/push-pop/animation.jsx
+++ b/app/visualizer/stack/push-pop/animation.jsx
@@ -33,7 +33,7 @@ const StackVisualizer = () => {
setStack(next);
};
- // Runs the moment a new "top" box mounts in the DOM — a reliable
+ // Runs the moment a new "top" box mounts in the DOM: a reliable
// drop-in entrance that doesn't depend on any surrounding state timing.
const animateDropIn = (el) => {
if (!el || el.dataset.dropped) return;
diff --git a/app/visualizer/stack/push-pop/content.jsx b/app/visualizer/stack/push-pop/content.jsx
index eb20b07..7a02636 100755
--- a/app/visualizer/stack/push-pop/content.jsx
+++ b/app/visualizer/stack/push-pop/content.jsx
@@ -117,7 +117,7 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `Push and pop are the only two moves a stack really needs. Push adds a value on top, pop takes the top value back off, and because of that, whatever went on last is always the first thing to come off — that's the LIFO (Last In, First Out) rule in action.`,
+ `Push and pop are the only two moves a stack really needs. Push adds a value on top, pop takes the top value back off, and because of that, whatever went on last is always the first thing to come off: that's the LIFO (Last In, First Out) rule in action.`,
`Push and Pop operations are fundamental to stack functionality. While simple to implement, stacks are powerful data structures used in many algorithms and system designs.`,
];
diff --git a/app/visualizer/trees/advanced/b-trees/animation.jsx b/app/visualizer/trees/advanced/b-trees/animation.jsx
index f862bf9..c9af02e 100755
--- a/app/visualizer/trees/advanced/b-trees/animation.jsx
+++ b/app/visualizer/trees/advanced/b-trees/animation.jsx
@@ -3,7 +3,7 @@ import React, { useState } from "react";
import { gsap } from "gsap";
import { Plus, Shuffle, RotateCcw } from "lucide-react";
-const T = 2; // minimum degree — max keys per node = 2T-1 = 3, max children = 2T = 4
+const T = 2; // minimum degree: max keys per node = 2T-1 = 3, max children = 2T = 4
class BTreeNode {
constructor(leaf) {
@@ -31,7 +31,7 @@ const splitChild = (parent, i, events) => {
parent.keys.splice(i, 0, midKey);
events.push({
- text: `node [${[...leftKeys, midKey, ...newChild.keys].join(", ")}] was full — split it, moving median ${midKey} up`,
+ text: `node [${[...leftKeys, midKey, ...newChild.keys].join(", ")}] was full, so split it, moving median ${midKey} up`,
nodes: [fullChild, newChild, parent],
});
};
@@ -155,10 +155,10 @@ const BTreeVisualizer = () => {
setTimeout(() => {
setRoot(newRoot);
if (events.length === 0) {
- setMessage(`Inserted ${value} into a leaf node that had room — no split needed`);
+ setMessage(`Inserted ${value} into a leaf node that had room, so no split was needed`);
setHighlightNodes([]);
} else {
- setMessage(`Inserted ${value} — ${events.map((e) => e.text).join("; then ")}`);
+ setMessage(`Inserted ${value}: ${events.map((e) => e.text).join("; then ")}`);
setHighlightNodes(events.flatMap((e) => e.nodes));
setTimeout(() => setHighlightNodes([]), 1400);
}
@@ -368,7 +368,7 @@ const BTreeVisualizer = () => {
) : (
- No tree yet — insert a value or generate a random tree
+ No tree yet, insert a value or generate a random tree
- Node [10, 20, 30] is full — inserting 25 triggers a split
+ Node [10, 20, 30] is full: inserting 25 triggers a split
) : (
- No Fenwick tree yet — build one over a random array
+ No Fenwick tree yet, build one over a random array
)}
diff --git a/app/visualizer/trees/advanced/fenwick-tree/content.jsx b/app/visualizer/trees/advanced/fenwick-tree/content.jsx
index d1e8daa..7b3753d 100755
--- a/app/visualizer/trees/advanced/fenwick-tree/content.jsx
+++ b/app/visualizer/trees/advanced/fenwick-tree/content.jsx
@@ -47,23 +47,23 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `A Fenwick Tree (also called a Binary Indexed Tree, or BIT) solves the same core problem a Segment Tree does — prefix/range sums with fast point updates — but with a much smaller footprint: one plain array, no explicit tree nodes or pointers at all. The "tree" is implicit, encoded entirely in the binary representation of each index.`,
- `Every index i in the BIT array is responsible for a range of the original array whose length is exactly the value of i's lowest set bit (its "lowbit"). Index 6 in binary is 110, whose lowest set bit is 2, so BIT[6] covers a range of 2 elements. Index 8 is 1000, whose lowest set bit is 8, so BIT[8] covers all 8 elements up to it. This single bit trick is the entire structure — no recursion, no children pointers, just index arithmetic.`,
+ `A Fenwick Tree (also called a Binary Indexed Tree, or BIT) solves the same core problem a Segment Tree does, prefix/range sums with fast point updates, but with a much smaller footprint: one plain array, no explicit tree nodes or pointers at all. The "tree" is implicit, encoded entirely in the binary representation of each index.`,
+ `Every index i in the BIT array is responsible for a range of the original array whose length is exactly the value of i's lowest set bit (its "lowbit"). Index 6 in binary is 110, whose lowest set bit is 2, so BIT[6] covers a range of 2 elements. Index 8 is 1000, whose lowest set bit is 8, so BIT[8] covers all 8 elements up to it. This single bit trick is the entire structure: no recursion, no children pointers, just index arithmetic.`,
`Moving between indices uses that same lowbit value: adding it to an index walks up toward larger ranges (used when propagating a point update to every range that includes it), and subtracting it walks down toward smaller ranges (used when accumulating a prefix sum). Both walks take exactly O(log n) steps, because each step clears or sets one more bit in the index.`,
- `The tradeoff for this smaller footprint is flexibility: a Fenwick Tree's range query only works by combining prefix sums (range[l,r] = prefix(r) - prefix(l-1)), which requires the underlying operation to have an inverse. That works great for sum, but not for operations like minimum or maximum, which don't have an inverse — a Segment Tree is needed for those instead.`,
+ `The tradeoff for this smaller footprint is flexibility: a Fenwick Tree's range query only works by combining prefix sums (range[l,r] = prefix(r) - prefix(l-1)), which requires the underlying operation to have an inverse. That works great for sum, but not for operations like minimum or maximum, which don't have an inverse, so a Segment Tree is needed for those instead.`,
];
const algorithm = [
{ points: "Build: start with an all-zero BIT array, then apply a point update for every element of the input array" },
{
- points: "Point Update(index, delta) — add delta to the element at index:",
+ points: "Point Update(index, delta): add delta to the element at index:",
subpoints: [
"Convert to 1-indexed: i = index + 1",
"While i is within bounds: add delta to BIT[i], then move to the next responsible index with i += lowbit(i)",
],
},
{
- points: "Prefix Sum(index) — sum of everything from 0 to index:",
+ points: "Prefix Sum(index): sum of everything from 0 to index:",
subpoints: [
"Convert to 1-indexed: i = index + 1",
"While i > 0: add BIT[i] to the running total, then move down with i -= lowbit(i)",
@@ -74,8 +74,8 @@ const Content = () => {
const complexity = [
{ points: "Build: O(n log n) naively (n point updates), or O(n) with a direct construction trick." },
- { points: "Point Update: O(log n) — one walk upward through responsible ranges." },
- { points: "Prefix/Range Query: O(log n) — one walk downward accumulating partial sums." },
+ { points: "Point Update: O(log n), one walk upward through responsible ranges." },
+ { points: "Prefix/Range Query: O(log n), one walk downward accumulating partial sums." },
];
return (
diff --git a/app/visualizer/trees/advanced/fenwick-tree/quiz.jsx b/app/visualizer/trees/advanced/fenwick-tree/quiz.jsx
index 67bf740..3121000 100755
--- a/app/visualizer/trees/advanced/fenwick-tree/quiz.jsx
+++ b/app/visualizer/trees/advanced/fenwick-tree/quiz.jsx
@@ -14,13 +14,13 @@ const FenwickQuiz = () => {
"It's chosen randomly during construction"
],
correctAnswer: 1,
- explanation: "BIT[i] covers exactly lowbit(i) elements ending at position i — e.g. index 6 (binary 110) has lowbit 2, so it covers 2 elements."
+ explanation: "BIT[i] covers exactly lowbit(i) elements ending at position i, e.g. index 6 (binary 110) has lowbit 2, so it covers 2 elements."
},
{
question: "During a point update, how does the algorithm move from one BIT index to the next?",
options: ["i = i * 2", "i += lowbit(i)", "i -= lowbit(i)", "i = i / 2"],
correctAnswer: 1,
- explanation: "Adding the lowbit moves to the next larger range that also needs to include this update — this is how the update propagates upward through all responsible ranges."
+ explanation: "Adding the lowbit moves to the next larger range that also needs to include this update: this is how the update propagates upward through all responsible ranges."
},
{
question: "During a prefix sum query, how does the algorithm move from one BIT index to the next?",
@@ -48,7 +48,7 @@ const FenwickQuiz = () => {
"Because Fenwick trees can only store one number total"
],
correctAnswer: 1,
- explanation: "Subtraction only works to 'undo' addition. There's no way to 'subtract out' a minimum from a combined minimum, so that trick — and therefore a plain Fenwick Tree — doesn't work for min/max; a Segment Tree is used instead."
+ explanation: "Subtraction only works to 'undo' addition. There's no way to 'subtract out' a minimum from a combined minimum, so that trick, and therefore a plain Fenwick Tree, doesn't work for min/max; a Segment Tree is used instead."
}
];
diff --git a/app/visualizer/trees/advanced/prefix-tree/animation.jsx b/app/visualizer/trees/advanced/prefix-tree/animation.jsx
index f716c2f..9c251c6 100755
--- a/app/visualizer/trees/advanced/prefix-tree/animation.jsx
+++ b/app/visualizer/trees/advanced/prefix-tree/animation.jsx
@@ -139,9 +139,9 @@ const PrefixTreeVisualizer = () => {
if (createdPrefixes.length === 0 && alreadyExisted) {
setMessage(`"${word}" is already in the trie`);
} else if (createdPrefixes.length === 0) {
- setMessage(`"${word}" existed only as a prefix of another word — now marked complete too`);
+ setMessage(`"${word}" existed only as a prefix of another word, now marked complete too`);
} else {
- setMessage(`Inserted "${word}" — created ${createdPrefixes.length} new node${createdPrefixes.length === 1 ? "" : "s"}`);
+ setMessage(`Inserted "${word}": created ${createdPrefixes.length} new node${createdPrefixes.length === 1 ? "" : "s"}`);
}
setHighlightCreated(createdPrefixes);
setTimeout(() => setHighlightCreated([]), 1300);
@@ -164,16 +164,16 @@ const PrefixTreeVisualizer = () => {
const revealStep = () => {
if (i < steps.length) {
setHighlightPath(steps.slice(0, i + 1));
- setMessage(`Following '${word[i]}' — path so far: "${steps[i]}"`);
+ setMessage(`Following '${word[i]}', path so far: "${steps[i]}"`);
i++;
setTimeout(revealStep, STEP_DELAY);
} else {
if (brokeAt) {
- setMessage(`"${word}" isn't in the trie — no branch for prefix "${brokeAt}"`);
+ setMessage(`"${word}" isn't in the trie: no branch for prefix "${brokeAt}"`);
} else if (found) {
- setMessage(`Found "${word}" — it's a complete word in the trie`);
+ setMessage(`Found "${word}": it's a complete word in the trie`);
} else {
- setMessage(`"${word}" is only a prefix of other words — not itself stored as a complete word`);
+ setMessage(`"${word}" is only a prefix of other words, not itself stored as a complete word`);
}
setBusy(false);
}
@@ -373,7 +373,7 @@ const PrefixTreeVisualizer = () => {
) : (
- No trie yet — insert a word or generate random words
+ No trie yet, insert a word or generate random words
)}
diff --git a/app/visualizer/trees/advanced/prefix-tree/content.jsx b/app/visualizer/trees/advanced/prefix-tree/content.jsx
index 0c8b324..89c5d68 100755
--- a/app/visualizer/trees/advanced/prefix-tree/content.jsx
+++ b/app/visualizer/trees/advanced/prefix-tree/content.jsx
@@ -76,16 +76,16 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `A Trie (pronounced "try", from re-"trie"-val, and also called a Prefix Tree) stores a set of strings by turning shared prefixes into shared paths through the tree. Unlike a BST, a trie doesn't store a whole key per node — each edge represents a single character, and the string you get by reading the characters from the root down to any node is that node's prefix. A small boolean flag on a node marks whether the prefix ending there is also a complete word that was inserted.`,
- `This "shared paths for shared prefixes" structure is what makes tries so effective for prefix-based operations: "cat" and "car" and "cart" all share the same "ca"/"car" path and only branch where they actually differ. That single property is why tries are the standard structure behind autocomplete and search-as-you-type suggestions, spell-checkers, and IP routing tables (where the longest matching prefix determines the route) — all workloads built entirely around "find everything starting with X."`,
- `Both insertion and search walk one character at a time from the root: insertion follows existing edges where they already exist and creates new nodes only where the path doesn't yet exist, then marks the final node as end-of-word. Search does the same walk — if it ever needs an edge that doesn't exist, the word (or prefix) definitely isn't in the trie; if it reaches the end of the word, it still has to check that node's end-of-word flag, because the path existing only proves the string is a prefix of something, not that it was inserted as its own word.`,
- `Both operations run in O(L) time, where L is the length of the word — not O(log n) or O(n) in terms of how many words are already stored. A hash set can also do exact lookups in roughly that time, but it can't efficiently answer "what words start with this prefix" the way a trie can, since a matching prefix's subtree already holds exactly the words that share it.`,
+ `A Trie (pronounced "try", from re-"trie"-val, and also called a Prefix Tree) stores a set of strings by turning shared prefixes into shared paths through the tree. Unlike a BST, a trie doesn't store a whole key per node: each edge represents a single character, and the string you get by reading the characters from the root down to any node is that node's prefix. A small boolean flag on a node marks whether the prefix ending there is also a complete word that was inserted.`,
+ `This "shared paths for shared prefixes" structure is what makes tries so effective for prefix-based operations: "cat" and "car" and "cart" all share the same "ca"/"car" path and only branch where they actually differ. That single property is why tries are the standard structure behind autocomplete and search-as-you-type suggestions, spell-checkers, and IP routing tables (where the longest matching prefix determines the route), all workloads built entirely around "find everything starting with X."`,
+ `Both insertion and search walk one character at a time from the root: insertion follows existing edges where they already exist and creates new nodes only where the path doesn't yet exist, then marks the final node as end-of-word. Search does the same walk: if it ever needs an edge that doesn't exist, the word (or prefix) definitely isn't in the trie; if it reaches the end of the word, it still has to check that node's end-of-word flag, because the path existing only proves the string is a prefix of something, not that it was inserted as its own word.`,
+ `Both operations run in O(L) time, where L is the length of the word, not O(log n) or O(n) in terms of how many words are already stored. A hash set can also do exact lookups in roughly that time, but it can't efficiently answer "what words start with this prefix" the way a trie can, since a matching prefix's subtree already holds exactly the words that share it.`,
];
const algorithm = [
{ points: "Insertion: start at the root; for each character in the word, follow the matching child edge if it exists, or create a new node for it if it doesn't; after the last character, mark that node as end-of-word" },
- { points: "Search: start at the root; for each character, follow the matching child edge — if it's missing at any point, the word isn't in the trie" },
- { points: "If every character is matched, the word is in the trie only if the final node's end-of-word flag is set — otherwise it's merely a prefix of other stored words" },
+ { points: "Search: start at the root; for each character, follow the matching child edge; if it's missing at any point, the word isn't in the trie" },
+ { points: "If every character is matched, the word is in the trie only if the final node's end-of-word flag is set; otherwise it's merely a prefix of other stored words" },
];
const complexity = [
diff --git a/app/visualizer/trees/advanced/prefix-tree/quiz.jsx b/app/visualizer/trees/advanced/prefix-tree/quiz.jsx
index 9cf0cbb..e59ede5 100755
--- a/app/visualizer/trees/advanced/prefix-tree/quiz.jsx
+++ b/app/visualizer/trees/advanced/prefix-tree/quiz.jsx
@@ -20,12 +20,12 @@ const PrefixTreeQuiz = () => {
"Because both words are the same length"
],
correctAnswer: 1,
- explanation: "A trie only branches where words actually differ — 'cat' and 'car' both follow c -> a and only split into separate 't' and 'r' children."
+ explanation: "A trie only branches where words actually differ: 'cat' and 'car' both follow c -> a and only split into separate 't' and 'r' children."
},
{
question: "If searching for a word successfully follows an edge for every character, why isn't that enough to confirm the word is in the trie?",
options: [
- "It actually is enough — nothing else needs to be checked",
+ "It actually is enough, nothing else needs to be checked",
"Because reaching that node only proves the string is a prefix of something stored; you still need to check that node's end-of-word flag",
"Because trie searches are always inaccurate",
"Because you'd need to check every other node in the tree too"
@@ -42,7 +42,7 @@ const PrefixTreeQuiz = () => {
"There's no real difference between the two"
],
correctAnswer: 1,
- explanation: "Once you walk to the node for a prefix, everything below it in the trie is, by construction, every stored word that starts with that prefix — a hash set has no equivalent structure to exploit."
+ explanation: "Once you walk to the node for a prefix, everything below it in the trie is, by construction, every stored word that starts with that prefix, while a hash set has no equivalent structure to exploit."
},
{
question: "What is the time complexity of inserting or searching for a word of length L in a trie?",
diff --git a/app/visualizer/trees/advanced/red-black/animation.jsx b/app/visualizer/trees/advanced/red-black/animation.jsx
index b7bbfa9..d353f94 100755
--- a/app/visualizer/trees/advanced/red-black/animation.jsx
+++ b/app/visualizer/trees/advanced/red-black/animation.jsx
@@ -52,7 +52,7 @@ const fixInsertRB = (root, z, events) => {
uncle.color = "BLACK";
grandparent.color = "RED";
events.push({
- text: `uncle ${uncle.value} is red — recolor ${parent.value} and ${uncle.value} to black, ${grandparent.value} to red`,
+ text: `uncle ${uncle.value} is red, so recolor ${parent.value} and ${uncle.value} to black, ${grandparent.value} to red`,
touched: [parent.value, uncle.value, grandparent.value],
});
z = grandparent;
@@ -60,7 +60,7 @@ const fixInsertRB = (root, z, events) => {
if (z === parent.right) {
z = parent;
root = rotateLeftRB(root, z);
- events.push({ text: `zig-zag shape — rotate left at ${z.value}`, touched: [z.value] });
+ events.push({ text: `zig-zag shape, rotate left at ${z.value}`, touched: [z.value] });
}
z.parent.color = "BLACK";
grandparent.color = "RED";
@@ -77,7 +77,7 @@ const fixInsertRB = (root, z, events) => {
uncle.color = "BLACK";
grandparent.color = "RED";
events.push({
- text: `uncle ${uncle.value} is red — recolor ${parent.value} and ${uncle.value} to black, ${grandparent.value} to red`,
+ text: `uncle ${uncle.value} is red, so recolor ${parent.value} and ${uncle.value} to black, ${grandparent.value} to red`,
touched: [parent.value, uncle.value, grandparent.value],
});
z = grandparent;
@@ -85,7 +85,7 @@ const fixInsertRB = (root, z, events) => {
if (z === parent.left) {
z = parent;
root = rotateRightRB(root, z);
- events.push({ text: `zig-zag shape — rotate right at ${z.value}`, touched: [z.value] });
+ events.push({ text: `zig-zag shape, rotate right at ${z.value}`, touched: [z.value] });
}
z.parent.color = "BLACK";
grandparent.color = "RED";
@@ -177,13 +177,13 @@ const RedBlackVisualizer = () => {
setTimeout(() => {
setRoot(newRoot);
if (wasEmpty) {
- setMessage(`Inserted ${value} as the root — colored black`);
+ setMessage(`Inserted ${value} as the root, colored black`);
setHighlightNodes([]);
} else if (events.length === 0) {
- setMessage(`Inserted ${value} as a red leaf — its parent was black, so no fixup was needed`);
+ setMessage(`Inserted ${value} as a red leaf; its parent was black, so no fixup was needed`);
setHighlightNodes([]);
} else {
- setMessage(`Inserted ${value} — ${events.map((e) => e.text).join("; then ")}`);
+ setMessage(`Inserted ${value}: ${events.map((e) => e.text).join("; then ")}`);
const touched = events.flatMap((e) => e.touched);
setHighlightNodes(touched);
setTimeout(() => setHighlightNodes([]), 1400);
@@ -392,7 +392,7 @@ const RedBlackVisualizer = () => {
) : (
- No tree yet — insert a value or generate a random tree
+ No tree yet, insert a value or generate a random tree
)}
diff --git a/app/visualizer/trees/advanced/red-black/content.jsx b/app/visualizer/trees/advanced/red-black/content.jsx
index e6fc31b..6efc261 100755
--- a/app/visualizer/trees/advanced/red-black/content.jsx
+++ b/app/visualizer/trees/advanced/red-black/content.jsx
@@ -52,17 +52,17 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `A Red-Black Tree is a self-balancing Binary Search Tree that keeps itself roughly balanced by coloring every node either red or black and enforcing a small set of rules about how those colors can be arranged. Unlike an AVL tree, which balances strictly by height, a Red-Black Tree balances by color — and that looser constraint is exactly what makes it cheaper to maintain.`,
- `The five rules (the "red-black properties") together guarantee that the longest possible root-to-leaf path is never more than twice as long as the shortest one. That's a weaker balance guarantee than AVL's, but it's enough to keep every operation at O(log n), and it means Red-Black Trees need at most one or two rotations per insertion or deletion — compared to AVL, which can cascade rotations back up the tree. This is why Red-Black Trees are the default choice inside C++'s \`std::map\`/\`std::set\`, Java's \`TreeMap\`/\`TreeSet\`, and the Linux kernel's process scheduler and virtual memory management.`,
- `Insertion always starts the same way a plain BST insertion does, and the new node is always colored red. Coloring it red (rather than black) means it can never violate the "same black-height on every path" rule by itself — the only rule a fresh red leaf can break is "a red node can't have a red parent." If that happens, a fixup procedure runs, walking back up the tree recoloring nodes and, when recoloring alone isn't enough, performing at most two rotations to restore all five properties.`,
- `Insertion (with fixup) needs O(1) extra space beyond the recursion/loop state — only a constant number of pointers get rewired or recolored, no matter how large the tree is.`,
+ `A Red-Black Tree is a self-balancing Binary Search Tree that keeps itself roughly balanced by coloring every node either red or black and enforcing a small set of rules about how those colors can be arranged. Unlike an AVL tree, which balances strictly by height, a Red-Black Tree balances by color, and that looser constraint is exactly what makes it cheaper to maintain.`,
+ `The five rules (the "red-black properties") together guarantee that the longest possible root-to-leaf path is never more than twice as long as the shortest one. That's a weaker balance guarantee than AVL's, but it's enough to keep every operation at O(log n), and it means Red-Black Trees need at most one or two rotations per insertion or deletion, compared to AVL, which can cascade rotations back up the tree. This is why Red-Black Trees are the default choice inside C++'s \`std::map\`/\`std::set\`, Java's \`TreeMap\`/\`TreeSet\`, and the Linux kernel's process scheduler and virtual memory management.`,
+ `Insertion always starts the same way a plain BST insertion does, and the new node is always colored red. Coloring it red (rather than black) means it can never violate the "same black-height on every path" rule by itself: the only rule a fresh red leaf can break is "a red node can't have a red parent." If that happens, a fixup procedure runs, walking back up the tree recoloring nodes and, when recoloring alone isn't enough, performing at most two rotations to restore all five properties.`,
+ `Insertion (with fixup) needs O(1) extra space beyond the recursion/loop state: only a constant number of pointers get rewired or recolored, no matter how large the tree is.`,
];
const properties = [
{ title: "1. Every node is red or black", body: "Each node stores one extra bit of information: its color." },
{ title: "2. The root is always black", body: "If an insertion colors the root red, it's flipped back to black once the fixup finishes." },
{ title: "3. Every NIL leaf is black", body: "The (implicit) null children at the bottom of the tree are treated as black leaves." },
- { title: "4. No red node has a red child", body: "Two reds can never appear back-to-back on any path — this is the rule a new red insertion can violate." },
+ { title: "4. No red node has a red child", body: "Two reds can never appear back-to-back on any path; this is the rule a new red insertion can violate." },
{ title: "5. Equal black-height on every path", body: "Every path from a node to any of its descendant NIL leaves passes through the same number of black nodes." },
];
@@ -171,7 +171,7 @@ const Content = () => {
- A right rotation at 10 fixes it — black-height unchanged
+ A right rotation at 10 fixes it, black-height unchanged
diff --git a/app/visualizer/trees/advanced/red-black/quiz.jsx b/app/visualizer/trees/advanced/red-black/quiz.jsx
index f88a1c3..69c6a9a 100755
--- a/app/visualizer/trees/advanced/red-black/quiz.jsx
+++ b/app/visualizer/trees/advanced/red-black/quiz.jsx
@@ -7,15 +7,15 @@ const RedBlackQuiz = () => {
const questions = [
{
question: "What color is the root of a Red-Black Tree always required to be?",
- options: ["Red", "Black", "Either — it doesn't matter", "It alternates on every insertion"],
+ options: ["Red", "Black", "Either, it doesn't matter", "It alternates on every insertion"],
correctAnswer: 1,
- explanation: "Property 2 requires the root to always be black — if an insertion's fixup ever colors it red, it's flipped back to black at the very end."
+ explanation: "Property 2 requires the root to always be black; if an insertion's fixup ever colors it red, it's flipped back to black at the very end."
},
{
question: "What color is a newly inserted node given, and why?",
options: [
"Black, so it never violates the black-height rule",
- "Red, because a red node can't violate the equal-black-height rule by itself — the only rule it might break is having a red parent",
+ "Red, because a red node can't violate the equal-black-height rule by itself; the only rule it might break is having a red parent",
"It's colored randomly",
"It takes on its parent's color"
],
@@ -28,10 +28,10 @@ const RedBlackQuiz = () => {
"Rotate immediately",
"Recolor the parent and uncle to black and the grandparent to red, then continue checking from the grandparent",
"Delete the uncle",
- "Nothing — a red uncle means no violation exists"
+ "Nothing, a red uncle means no violation exists"
],
correctAnswer: 1,
- explanation: "A red uncle means recoloring three nodes fixes the local violation without any rotation — but it can push a new violation up to the grandparent, so the fixup continues from there."
+ explanation: "A red uncle means recoloring three nodes fixes the local violation without any rotation, but it can push a new violation up to the grandparent, so the fixup continues from there."
},
{
question: "Why do Red-Black Trees generally need fewer rotations per insertion than AVL trees?",
@@ -48,7 +48,7 @@ const RedBlackQuiz = () => {
question: "What is the worst-case time complexity of insertion, deletion, and search in a Red-Black Tree?",
options: ["O(1)", "O(log n)", "O(n)", "O(n log n)"],
correctAnswer: 1,
- explanation: "The red-black properties guarantee the longest root-to-leaf path is never more than twice the shortest, which caps the height — and every operation — at O(log n)."
+ explanation: "The red-black properties guarantee the longest root-to-leaf path is never more than twice the shortest, which caps the height, and every operation, at O(log n)."
}
];
diff --git a/app/visualizer/trees/advanced/segment-trees/animation.jsx b/app/visualizer/trees/advanced/segment-trees/animation.jsx
index 43e5ff8..d9e117b 100755
--- a/app/visualizer/trees/advanced/segment-trees/animation.jsx
+++ b/app/visualizer/trees/advanced/segment-trees/animation.jsx
@@ -79,7 +79,7 @@ const STEP_DELAY = 500;
const SegmentTreeVisualizer = () => {
const [arr, setArr] = useState(null);
const [root, setRoot] = useState(null);
- const [message, setMessage] = useState("No segment tree yet — build one over a random array");
+ const [message, setMessage] = useState("No segment tree yet, build one over a random array");
const [updateIndex, setUpdateIndex] = useState("");
const [updateValue, setUpdateValue] = useState("");
const [queryL, setQueryL] = useState("");
@@ -135,7 +135,7 @@ const SegmentTreeVisualizer = () => {
let i = 0;
const revealStep = () => {
setHighlightPath(path.slice(0, i + 1));
- setMessage(`Updating index ${index} to ${value} — recomputing range [${path[i]}]`);
+ setMessage(`Updating index ${index} to ${value}, recomputing range [${path[i]}]`);
i++;
if (i < path.length) {
setTimeout(revealStep, STEP_DELAY);
@@ -143,7 +143,7 @@ const SegmentTreeVisualizer = () => {
setTimeout(() => {
setRoot(clonedRoot);
setArr(newArr);
- setMessage(`Index ${index} updated to ${value} — sums recomputed along the path to the root`);
+ setMessage(`Index ${index} updated to ${value}, sums recomputed along the path to the root`);
setTimeout(() => setHighlightPath([]), 900);
setBusy(false);
}, STEP_DELAY);
@@ -172,8 +172,8 @@ const SegmentTreeVisualizer = () => {
const revealStep = () => {
const step = touched[i];
setQueryHighlights((prev) => ({ ...prev, [step.key]: step.type }));
- const verb = step.type === "include" ? "fully inside range — add its sum" : step.type === "skip" ? "fully outside range — skip it" : "partially overlaps — decompose into children";
- setMessage(`Visiting [${step.key.replace(":", ", ")}] — ${verb}`);
+ const verb = step.type === "include" ? "fully inside range, add its sum" : step.type === "skip" ? "fully outside range, skip it" : "partially overlaps, decompose into children";
+ setMessage(`Visiting [${step.key.replace(":", ", ")}]: ${verb}`);
i++;
if (i < touched.length) {
setTimeout(revealStep, STEP_DELAY);
@@ -191,7 +191,7 @@ const SegmentTreeVisualizer = () => {
if (busy) return;
setArr(null);
setRoot(null);
- setMessage("No segment tree yet — build one over a random array");
+ setMessage("No segment tree yet, build one over a random array");
setUpdateIndex("");
setUpdateValue("");
setQueryL("");
@@ -431,7 +431,7 @@ const SegmentTreeVisualizer = () => {
) : (
- No tree yet — build one over a random array
+ No tree yet, build one over a random array
)}
diff --git a/app/visualizer/trees/advanced/segment-trees/content.jsx b/app/visualizer/trees/advanced/segment-trees/content.jsx
index 6911ce4..262dd01 100755
--- a/app/visualizer/trees/advanced/segment-trees/content.jsx
+++ b/app/visualizer/trees/advanced/segment-trees/content.jsx
@@ -65,10 +65,10 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `A Segment Tree answers a question that neither a plain array nor a running-total (prefix-sum) array handles well at the same time: "what's the sum (or min, max, gcd...) of elements from index l to r?", while also supporting fast updates to individual elements. A prefix-sum array answers range queries in O(1), but a single update forces you to recompute every prefix after it — O(n) per update. A segment tree gets both operations down to O(log n).`,
- `Every node in a segment tree represents a contiguous range of the underlying array. Leaves represent single elements; every internal node represents the union of its two children's ranges, and stores the combined result of some associative operation (sum, min, max, etc.) over that whole range — computed once and cached, not recomputed from scratch on every query.`,
- `A range query works by decomposing the query range into a small number of these pre-combined node ranges. Starting from the root, if a node's range falls entirely outside the query, it's skipped. If it falls entirely inside the query, its cached value is used directly — no need to look at its children at all. Only when a node's range partially overlaps the query does the search recurse into both children. This decomposition never needs more than O(log n) nodes to cover any range.`,
- `A point update walks straight from the root down to the one leaf that needs to change, updates it, and then recomputes each ancestor's cached value on the way back up — exactly one path of length O(log n), touching nothing else in the tree.`,
+ `A Segment Tree answers a question that neither a plain array nor a running-total (prefix-sum) array handles well at the same time: "what's the sum (or min, max, gcd...) of elements from index l to r?", while also supporting fast updates to individual elements. A prefix-sum array answers range queries in O(1), but a single update forces you to recompute every prefix after it, O(n) per update. A segment tree gets both operations down to O(log n).`,
+ `Every node in a segment tree represents a contiguous range of the underlying array. Leaves represent single elements; every internal node represents the union of its two children's ranges, and stores the combined result of some associative operation (sum, min, max, etc.) over that whole range, computed once and cached, not recomputed from scratch on every query.`,
+ `A range query works by decomposing the query range into a small number of these pre-combined node ranges. Starting from the root, if a node's range falls entirely outside the query, it's skipped. If it falls entirely inside the query, its cached value is used directly, no need to look at its children at all. Only when a node's range partially overlaps the query does the search recurse into both children. This decomposition never needs more than O(log n) nodes to cover any range.`,
+ `A point update walks straight from the root down to the one leaf that needs to change, updates it, and then recomputes each ancestor's cached value on the way back up, exactly one path of length O(log n), touching nothing else in the tree.`,
];
const algorithm = [
@@ -85,9 +85,9 @@ const Content = () => {
];
const complexity = [
- { points: "Build: O(n) — every node is computed exactly once." },
- { points: "Range Query: O(log n) — at most a constant number of nodes per level are visited." },
- { points: "Point Update: O(log n) — exactly one root-to-leaf path is touched." },
+ { points: "Build: O(n), every node is computed exactly once." },
+ { points: "Range Query: O(log n), at most a constant number of nodes per level are visited." },
+ { points: "Point Update: O(log n), exactly one root-to-leaf path is touched." },
];
return (
diff --git a/app/visualizer/trees/advanced/segment-trees/quiz.jsx b/app/visualizer/trees/advanced/segment-trees/quiz.jsx
index 7cc5207..0452598 100755
--- a/app/visualizer/trees/advanced/segment-trees/quiz.jsx
+++ b/app/visualizer/trees/advanced/segment-trees/quiz.jsx
@@ -25,35 +25,35 @@ const SegmentTreeQuiz = () => {
"A pointer to every leaf in the tree"
],
correctAnswer: 1,
- explanation: "Every node — leaf or internal — represents a range; internal nodes cache the combined result of their two children's ranges so it never needs recomputing from scratch."
+ explanation: "Every node, whether leaf or internal, represents a range; internal nodes cache the combined result of their two children's ranges so it never needs recomputing from scratch."
},
{
question: "During a range query, what happens when a node's range falls entirely inside the query range?",
options: [
"The query fails",
- "Its cached value is used directly — there's no need to look at its children at all",
+ "Its cached value is used directly, so there's no need to look at its children at all",
"It must still recurse into both children to double-check",
"It's skipped entirely"
],
correctAnswer: 1,
- explanation: "This is exactly what makes segment trees fast — a single cached value stands in for an entire subrange, which is what keeps the number of visited nodes at O(log n) instead of O(n)."
+ explanation: "This is exactly what makes segment trees fast: a single cached value stands in for an entire subrange, which is what keeps the number of visited nodes at O(log n) instead of O(n)."
},
{
question: "What happens on a point update?",
options: [
"The entire tree is rebuilt from scratch",
- "Only the path from the root down to the affected leaf is touched — the leaf's value changes, then every ancestor's cached value is recomputed",
+ "Only the path from the root down to the affected leaf is touched: the leaf's value changes, then every ancestor's cached value is recomputed",
"Only the leaf is updated; ancestors are left stale until the next query",
"All leaves are updated"
],
correctAnswer: 1,
- explanation: "Updating one element only invalidates the O(log n) ancestors whose ranges include that index — everything else in the tree stays correct and untouched."
+ explanation: "Updating one element only invalidates the O(log n) ancestors whose ranges include that index; everything else in the tree stays correct and untouched."
},
{
question: "What is the time complexity of building a segment tree over n elements?",
options: ["O(1)", "O(log n)", "O(n)", "O(n log n)"],
correctAnswer: 2,
- explanation: "Every node — there are O(n) of them in total — is computed exactly once during the build, giving O(n) overall."
+ explanation: "Every node (there are O(n) of them in total) is computed exactly once during the build, giving O(n) overall."
}
];
diff --git a/app/visualizer/trees/algorithms/lowest-common-ancestor/animation.jsx b/app/visualizer/trees/algorithms/lowest-common-ancestor/animation.jsx
index bf4a8a0..42d02bd 100755
--- a/app/visualizer/trees/algorithms/lowest-common-ancestor/animation.jsx
+++ b/app/visualizer/trees/algorithms/lowest-common-ancestor/animation.jsx
@@ -35,8 +35,8 @@ const collectValues = (node, out = []) => {
return out;
};
-// Walks from the root toward the split point where p and q's paths diverge —
-// that split point is the LCA — recording, at each step, every value in the
+// Walks from the root toward the split point where p and q's paths diverge,
+// that split point is the LCA, recording, at each step, every value in the
// subtree ruled out by that comparison.
const lcaWithElimination = (root, p, q) => {
const path = [];
@@ -152,9 +152,9 @@ const LcaVisualizer = () => {
setHighlightPath((prev) => [...prev, current]);
setDimmedValues((prev) => [...prev, ...(eliminatedPerStep[step] || [])]);
if (step < path.length - 1) {
- setMessage(`Both ${p} and ${q} are on the same side of ${current} — descending further`);
+ setMessage(`Both ${p} and ${q} are on the same side of ${current}, descending further`);
} else {
- setMessage(`${p} and ${q} split apart at ${current} — this is the LCA`);
+ setMessage(`${p} and ${q} split apart at ${current}: this is the LCA`);
}
step++;
@@ -388,7 +388,7 @@ const LcaVisualizer = () => {
) : (
- No tree yet — insert a value or generate a random tree
+ No tree yet: insert a value or generate a random tree
)}
diff --git a/app/visualizer/trees/algorithms/lowest-common-ancestor/content.jsx b/app/visualizer/trees/algorithms/lowest-common-ancestor/content.jsx
index 8c61d6d..d105f0f 100755
--- a/app/visualizer/trees/algorithms/lowest-common-ancestor/content.jsx
+++ b/app/visualizer/trees/algorithms/lowest-common-ancestor/content.jsx
@@ -72,9 +72,9 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `The Lowest Common Ancestor of two nodes A and B is the deepest node in the tree that has both A and B as descendants (a node counts as its own descendant, so LCA(A, A) is just A). It's "lowest" in the sense of being as far from the root as possible while still being an ancestor of both — the point where the paths from A and B up to the root first merge.`,
- `On a general binary tree (no ordering guarantee), finding the LCA needs to search the whole tree: recursively check the left and right subtrees for A and B. If a node's left subtree contains one of them and its right subtree contains the other, that node is the split point — the LCA. If only one subtree contains either of them, the LCA must be further down inside that subtree.`,
- `A Binary Search Tree's ordering turns this into something far cheaper. Starting at the root and comparing both values against the current node: if both A and B are smaller, the LCA must be somewhere in the left subtree (skip the right entirely). If both are larger, it must be in the right subtree. The moment they're no longer both on the same side — one is smaller (or equal) and the other is larger (or equal) — the paths to A and B have just diverged, and the current node is the LCA. No backtracking, no exploring the "wrong" subtree at all.`,
+ `The Lowest Common Ancestor of two nodes A and B is the deepest node in the tree that has both A and B as descendants (a node counts as its own descendant, so LCA(A, A) is just A). It's "lowest" in the sense of being as far from the root as possible while still being an ancestor of both: the point where the paths from A and B up to the root first merge.`,
+ `On a general binary tree (no ordering guarantee), finding the LCA needs to search the whole tree: recursively check the left and right subtrees for A and B. If a node's left subtree contains one of them and its right subtree contains the other, that node is the split point: the LCA. If only one subtree contains either of them, the LCA must be further down inside that subtree.`,
+ `A Binary Search Tree's ordering turns this into something far cheaper. Starting at the root and comparing both values against the current node: if both A and B are smaller, the LCA must be somewhere in the left subtree (skip the right entirely). If both are larger, it must be in the right subtree. The moment they're no longer both on the same side (one is smaller-or-equal and the other is larger-or-equal), the paths to A and B have just diverged, and the current node is the LCA. No backtracking, no exploring the "wrong" subtree at all.`,
`LCA queries show up any time a "closest shared point" needs to be found across two positions in a hierarchy: git's merge-base command (the common ancestor commit two branches diverged from), routing decisions in network topology trees, and evolutionary/phylogenetic trees (the most recent common ancestor of two species). When the same tree needs many repeated LCA queries, the answers are often precomputed into an O(1)-per-query structure using techniques built on top of range-minimum queries.`,
];
@@ -85,7 +85,7 @@ const Content = () => {
subpoints: [
"If both are smaller, move to the left child",
"If both are larger, move to the right child",
- "Otherwise (one is smaller-or-equal and the other is larger-or-equal), the current node is the LCA — stop",
+ "Otherwise (one is smaller-or-equal and the other is larger-or-equal), the current node is the LCA, so stop",
],
},
{ points: "Repeat until the split point is found" },
@@ -134,7 +134,7 @@ const Content = () => {
- LCA(1, 6) — paths to 1 and 6 split apart at node 3
+ LCA(1, 6): paths to 1 and 6 split apart at node 3
diff --git a/app/visualizer/trees/algorithms/lowest-common-ancestor/quiz.jsx b/app/visualizer/trees/algorithms/lowest-common-ancestor/quiz.jsx
index 0558642..7828a10 100755
--- a/app/visualizer/trees/algorithms/lowest-common-ancestor/quiz.jsx
+++ b/app/visualizer/trees/algorithms/lowest-common-ancestor/quiz.jsx
@@ -14,7 +14,7 @@ const LcaQuiz = () => {
"The root of the tree, always"
],
correctAnswer: 1,
- explanation: "LCA(A, B) is the deepest (furthest from the root) node that still has both A and B as descendants — the point where their paths up to the root first merge."
+ explanation: "LCA(A, B) is the deepest (furthest from the root) node that still has both A and B as descendants: the point where their paths up to the root first merge."
},
{
question: "On a Binary Search Tree, if both target values are smaller than the current node's value, what does that tell you?",
@@ -25,7 +25,7 @@ const LcaQuiz = () => {
"Nothing can be concluded"
],
correctAnswer: 1,
- explanation: "If both values are smaller, both nodes live in the left subtree, so the LCA must be there too — the BST ordering means the right subtree never needs to be checked."
+ explanation: "If both values are smaller, both nodes live in the left subtree, so the LCA must be there too; the BST ordering means the right subtree never needs to be checked."
},
{
question: "At what point does the BST LCA algorithm stop and declare the current node the answer?",
@@ -33,15 +33,15 @@ const LcaQuiz = () => {
"As soon as it reaches a leaf",
"The moment the two target values are no longer both on the same side of the current node",
"After visiting every node in the tree",
- "It never stops early — it always reaches a leaf"
+ "It never stops early; it always reaches a leaf"
],
correctAnswer: 1,
- explanation: "That's exactly the split point — one target is on the left, the other on the right (or one equals the current node) — meaning their paths diverge right here."
+ explanation: "That's exactly the split point: one target is on the left, the other on the right (or one equals the current node), meaning their paths diverge right here."
},
{
question: "Why doesn't the general (non-BST) LCA algorithm get to skip checking one whole subtree the way the BST version does?",
options: [
- "It doesn't need to — general trees are always small",
+ "It doesn't need to, since general trees are always small",
"Without an ordering guarantee, there's no way to know in advance which subtree contains a given value, so both must be searched",
"General trees don't have subtrees",
"It's actually just as fast as the BST version in every case"
@@ -53,7 +53,7 @@ const LcaQuiz = () => {
question: "What is the time complexity of the BST-specific LCA algorithm in the worst case (a completely skewed tree)?",
options: ["O(1)", "O(log n)", "O(n)", "O(n log n)"],
correctAnswer: 2,
- explanation: "Just like BST search, insertion, and deletion, a degenerate (linked-list-shaped) tree makes every operation — including LCA — degrade to O(n)."
+ explanation: "Just like BST search, insertion, and deletion, a degenerate (linked-list-shaped) tree makes every operation, including LCA, degrade to O(n)."
}
];
diff --git a/app/visualizer/trees/algorithms/serialize-deserialize/animation.jsx b/app/visualizer/trees/algorithms/serialize-deserialize/animation.jsx
index 40b27ab..ca0d36a 100755
--- a/app/visualizer/trees/algorithms/serialize-deserialize/animation.jsx
+++ b/app/visualizer/trees/algorithms/serialize-deserialize/animation.jsx
@@ -18,7 +18,7 @@ const insertNode = (node, value) => {
return node;
};
-// Preorder traversal that records a "null" token for every empty child —
+// Preorder traversal that records a "null" token for every empty child;
// that's what lets deserialization know exactly where each subtree ends.
const serializeWithSteps = (root) => {
const steps = [];
@@ -251,9 +251,9 @@ const SerializeDeserializeVisualizer = () => {
setSerializedDisplay(acc.join(","));
if (s.value !== null) {
setVisitedSource((prev) => new Set(prev).add(s.value));
- setMessage(`Visited ${s.value} — appended to the output string`);
+ setMessage(`Visited ${s.value}, appended to the output string`);
} else {
- setMessage("Hit an empty child — appended a null marker");
+ setMessage("Hit an empty child, appended a null marker");
}
i++;
if (i < steps.length) {
@@ -261,7 +261,7 @@ const SerializeDeserializeVisualizer = () => {
} else {
setTimeout(() => {
setSerializedInput(acc.join(","));
- setMessage("Serialization complete — this string fully encodes the tree's shape and values");
+ setMessage("Serialization complete: this string fully encodes the tree's shape and values");
setBusy(false);
}, STEP_DELAY);
}
@@ -293,16 +293,16 @@ const SerializeDeserializeVisualizer = () => {
const s = steps[i];
if (s.value !== null) {
setRevealedRecon((prev) => new Set(prev).add(s.value));
- setMessage(`Read token "${s.token}" — created node ${s.value}`);
+ setMessage(`Read token "${s.token}", created node ${s.value}`);
} else {
- setMessage(`Read token "null" — no node here`);
+ setMessage(`Read token "null", no node here`);
}
i++;
if (i < steps.length) {
setTimeout(reveal, STEP_DELAY);
} else {
setTimeout(() => {
- setMessage("Deserialization complete — the tree has been fully reconstructed from the string");
+ setMessage("Deserialization complete: the tree has been fully reconstructed from the string");
setBusy(false);
}, STEP_DELAY);
}
@@ -411,7 +411,7 @@ const SerializeDeserializeVisualizer = () => {
visited={visitedSource}
ringColor="#f59e0b"
gradId="ser-src"
- emptyText="No tree yet — insert a value or generate a random tree"
+ emptyText="No tree yet: insert a value or generate a random tree"
/>
{
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.`,
+ `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, and 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.`,
];
@@ -92,14 +92,14 @@ const Content = () => {
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",
+ "If it's a null marker, this subtree is empty, so 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: "Time Complexity: O(n) for both serialize and deserialize, since 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." },
];
diff --git a/app/visualizer/trees/algorithms/serialize-deserialize/quiz.jsx b/app/visualizer/trees/algorithms/serialize-deserialize/quiz.jsx
index 5e6f214..f5c18e5 100755
--- a/app/visualizer/trees/algorithms/serialize-deserialize/quiz.jsx
+++ b/app/visualizer/trees/algorithms/serialize-deserialize/quiz.jsx
@@ -21,17 +21,17 @@ const SerializeDeserializeQuiz = () => {
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",
+ "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."
+ 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",
+ "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"
],
@@ -47,7 +47,7 @@ const SerializeDeserializeQuiz = () => {
"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."
+ 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?",
diff --git a/app/visualizer/trees/algorithms/tree-diameter/animation.jsx b/app/visualizer/trees/algorithms/tree-diameter/animation.jsx
index b7143bd..1490f55 100755
--- a/app/visualizer/trees/algorithms/tree-diameter/animation.jsx
+++ b/app/visualizer/trees/algorithms/tree-diameter/animation.jsx
@@ -23,7 +23,7 @@ const height = (node) => (node ? 1 + Math.max(height(node.left), height(node.rig
// Post-order pass: at every node, the longest path *through* it is
// leftHeight + rightHeight edges. The overall diameter is the max of that
-// value across every node — not necessarily the one at the root.
+// value across every node, not necessarily the one at the root.
const computeDiameter = (root) => {
const steps = [];
let best = { diameter: -1, node: null };
@@ -134,7 +134,7 @@ const DiameterVisualizer = () => {
const revealStep = () => {
const s = steps[step];
setComputedHeights((prev) => ({ ...prev, [s.value]: s.h }));
- setMessage(`height(${s.value}) = ${s.h} — longest path through ${s.value} spans ${s.diameter} edge${s.diameter === 1 ? "" : "s"}`);
+ setMessage(`height(${s.value}) = ${s.h}: longest path through ${s.value} spans ${s.diameter} edge${s.diameter === 1 ? "" : "s"}`);
step++;
if (step < steps.length) {
@@ -365,7 +365,7 @@ const DiameterVisualizer = () => {
) : (
- No tree yet — insert a value or generate a random tree
+ No tree yet, insert a value or generate a random tree
)}
diff --git a/app/visualizer/trees/algorithms/tree-diameter/content.jsx b/app/visualizer/trees/algorithms/tree-diameter/content.jsx
index 4af861d..fb81745 100755
--- a/app/visualizer/trees/algorithms/tree-diameter/content.jsx
+++ b/app/visualizer/trees/algorithms/tree-diameter/content.jsx
@@ -78,14 +78,14 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `The diameter of a tree is the number of edges on the longest path between any two nodes. That path doesn't have to pass through the root — it can start and end anywhere, and in most trees it actually cuts through some node in the middle where two deep subtrees meet.`,
- `The key insight: for any single node, the longest path that passes *through* it is the height of its left subtree plus the height of its right subtree — one leg going down each side. Checking every node this way and keeping the largest total automatically finds the true diameter, because whichever node happens to be the meeting point of the two longest branches will produce the biggest sum.`,
- `This means diameter can be computed in a single post-order traversal: recursively find the height of the left and right subtrees first, use them to compute this node's own height (1 + the taller side) and its "path-through" value (left height + right height), then update a running maximum. No repeated re-traversal is needed — every node's height is computed exactly once and reused by its parent.`,
+ `The diameter of a tree is the number of edges on the longest path between any two nodes. That path doesn't have to pass through the root: it can start and end anywhere, and in most trees it actually cuts through some node in the middle where two deep subtrees meet.`,
+ `The key insight: for any single node, the longest path that passes *through* it is the height of its left subtree plus the height of its right subtree, one leg going down each side. Checking every node this way and keeping the largest total automatically finds the true diameter, because whichever node happens to be the meeting point of the two longest branches will produce the biggest sum.`,
+ `This means diameter can be computed in a single post-order traversal: recursively find the height of the left and right subtrees first, use them to compute this node's own height (1 + the taller side) and its "path-through" value (left height + right height), then update a running maximum. No repeated re-traversal is needed, since every node's height is computed exactly once and reused by its parent.`,
`Diameter shows up whenever "the two most distant points in a hierarchy" matters: the worst-case latency between two nodes in a network topology tree, the longest chain of dependencies in a build graph, or simply describing how "spread out" or "stringy" versus "bushy" a tree's shape is.`,
];
const algorithm = [
- { points: "Run a post-order traversal — process both children before the current node" },
+ { points: "Run a post-order traversal, processing both children before the current node" },
{
points: "At each node, using the already-computed heights of its children:",
subpoints: [
@@ -93,12 +93,12 @@ const Content = () => {
"The longest path through this node = left child height + right child height",
],
},
- { points: "Track the maximum path-through value seen across every node — that maximum is the diameter" },
+ { points: "Track the maximum path-through value seen across every node: that maximum is the diameter" },
];
const complexity = [
- { points: "Time Complexity: O(n) — every node's height is computed exactly once in a single traversal." },
- { points: "Space Complexity: O(h) — recursion stack depth equals the tree's height (O(log n) balanced, O(n) skewed)." },
+ { points: "Time Complexity: O(n), since every node's height is computed exactly once in a single traversal." },
+ { points: "Space Complexity: O(h), where recursion stack depth equals the tree's height (O(log n) balanced, O(n) skewed)." },
];
return (
@@ -139,7 +139,7 @@ const Content = () => {
diff --git a/app/visualizer/trees/algorithms/tree-diameter/page.jsx b/app/visualizer/trees/algorithms/tree-diameter/page.jsx
index 50a8efd..62a644c 100755
--- a/app/visualizer/trees/algorithms/tree-diameter/page.jsx
+++ b/app/visualizer/trees/algorithms/tree-diameter/page.jsx
@@ -14,7 +14,7 @@ import { MODULE_MAPS } from "@/lib/modulesMap";
export const metadata = {
title: "Tree Diameter | Animation and Explanation",
description:
- "Learn how to find the diameter of a binary tree — the longest path between any two nodes — using a single post-order traversal, with an interactive visualizer, code examples in JavaScript, C, Python, and Java, and a quiz.",
+ "Learn how to find the diameter of a binary tree (the longest path between any two nodes) using a single post-order traversal, with an interactive visualizer, code examples in JavaScript, C, Python, and Java, and a quiz.",
keywords: [
"Tree Diameter",
"Diameter of a Binary Tree",
diff --git a/app/visualizer/trees/algorithms/tree-diameter/quiz.jsx b/app/visualizer/trees/algorithms/tree-diameter/quiz.jsx
index 23bd4f2..d60e5e4 100755
--- a/app/visualizer/trees/algorithms/tree-diameter/quiz.jsx
+++ b/app/visualizer/trees/algorithms/tree-diameter/quiz.jsx
@@ -14,7 +14,7 @@ const DiameterQuiz = () => {
"The number of leaf nodes"
],
correctAnswer: 1,
- explanation: "Diameter is the length (in edges) of the longest path between any two nodes in the tree — that path doesn't have to pass through the root."
+ explanation: "Diameter is the length (in edges) of the longest path between any two nodes in the tree; that path doesn't have to pass through the root."
},
{
question: "For a single node, how is the length of the longest path passing through it calculated?",
@@ -25,23 +25,23 @@ const DiameterQuiz = () => {
"The depth of the node from the root"
],
correctAnswer: 1,
- explanation: "A path through a node has one leg going into the left subtree and one into the right subtree — its length is leftHeight + rightHeight edges."
+ explanation: "A path through a node has one leg going into the left subtree and one into the right subtree, so its length is leftHeight + rightHeight edges."
},
{
question: "Does the diameter path always pass through the root?",
options: [
"Yes, always",
- "No — the longest path can turn at any node, wherever the two deepest subtrees happen to meet",
+ "No, the longest path can turn at any node, wherever the two deepest subtrees happen to meet",
"Only in a Binary Search Tree",
"Only when the tree is perfectly balanced"
],
correctAnswer: 1,
- explanation: "The diameter is the maximum path-through value across every node, not just the root — in an unbalanced tree, the widest point is often far from the root."
+ explanation: "The diameter is the maximum path-through value across every node, not just the root; in an unbalanced tree, the widest point is often far from the root."
},
{
question: "Why can the diameter be computed in a single post-order traversal instead of recomputing height repeatedly?",
options: [
- "It can't — repeated height computation is required",
+ "It can't, since repeated height computation is required",
"Each node's height is computed once and reused by its parent, since children are processed before parents",
"Diameter doesn't depend on height at all",
"Post-order traversal skips the leaves"
diff --git a/app/visualizer/trees/algorithms/tree-isomorphism/animation.jsx b/app/visualizer/trees/algorithms/tree-isomorphism/animation.jsx
index 35f2d72..a7b9725 100755
--- a/app/visualizer/trees/algorithms/tree-isomorphism/animation.jsx
+++ b/app/visualizer/trees/algorithms/tree-isomorphism/animation.jsx
@@ -247,7 +247,7 @@ const IsomorphismVisualizer = () => {
setTreeA(parseLevelOrder(inputA));
setTreeB(parseLevelOrder(inputB));
clearResult();
- setMessage("Trees built — click Check Isomorphism");
+ setMessage("Trees built, click Check Isomorphism");
};
const loadExample = (key) => {
@@ -294,7 +294,7 @@ const IsomorphismVisualizer = () => {
} else {
setTimeout(() => {
setVerdict("isomorphic");
- setMessage("Trees are isomorphic — one becomes the other by swapping children at some nodes");
+ setMessage("Trees are isomorphic: one becomes the other by swapping children at some nodes");
setBusy(false);
}, STEP_DELAY);
}
@@ -306,7 +306,7 @@ const IsomorphismVisualizer = () => {
setMismatchB(result.mismatch.bValue);
setVerdict("not-isomorphic");
setMessage(
- `Trees are not isomorphic — mismatch at ${result.mismatch.aValue ?? "∅"} vs ${result.mismatch.bValue ?? "∅"}`
+ `Trees are not isomorphic: mismatch at ${result.mismatch.aValue ?? "∅"} vs ${result.mismatch.bValue ?? "∅"}`
);
setBusy(false);
}, STEP_DELAY);
@@ -319,7 +319,7 @@ const IsomorphismVisualizer = () => {
return (
- Check whether two trees are isomorphic — identical in structure once children can be freely swapped
+ Check whether two trees are isomorphic, identical in structure once children can be freely swapped
diff --git a/app/visualizer/trees/algorithms/tree-isomorphism/content.jsx b/app/visualizer/trees/algorithms/tree-isomorphism/content.jsx
index c9249d4..e31a15d 100755
--- a/app/visualizer/trees/algorithms/tree-isomorphism/content.jsx
+++ b/app/visualizer/trees/algorithms/tree-isomorphism/content.jsx
@@ -87,15 +87,15 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `Two binary trees are isomorphic if one can be transformed into the other by swapping the left and right children at any number of nodes — the values and the overall branching structure must match, but the left/right arrangement at each node is allowed to flip. It's a looser notion of "the same tree" than exact structural equality: two trees that look like mirror images of each other in places can still be isomorphic.`,
+ `Two binary trees are isomorphic if one can be transformed into the other by swapping the left and right children at any number of nodes: the values and the overall branching structure must match, but the left/right arrangement at each node is allowed to flip. It's a looser notion of "the same tree" than exact structural equality: two trees that look like mirror images of each other in places can still be isomorphic.`,
`The check is a recursive comparison of two nodes at a time, starting from both roots. If both are empty, they match. If exactly one is empty, or their values differ, the trees aren't isomorphic. Otherwise, the node's children are compared two ways: "straight" (left-with-left, right-with-right) and "flipped" (left-with-right, right-with-left). If either pairing matches all the way down, the current pair of nodes is isomorphic.`,
- `This differs from an exact structural-equality check only in that extra flipped attempt — without it, this is the same algorithm used to test whether two trees are identical. The flip is what makes isomorphism forgiving of left/right orientation while still being strict about values and shape.`,
+ `This differs from an exact structural-equality check only in that extra flipped attempt: without it, this is the same algorithm used to test whether two trees are identical. The flip is what makes isomorphism forgiving of left/right orientation while still being strict about values and shape.`,
`Isomorphism checks are common wherever "same shape regardless of arbitrary left/right layout choices" matters: comparing parse trees or expression trees generated by different tools, verifying that a serialized tree round-trips correctly regardless of child ordering, and canonicalizing tree-shaped data before hashing or deduplication.`,
];
const algorithm = [
{ points: "If both nodes are null, they match" },
- { points: "If exactly one is null, or their values differ, the trees are not isomorphic — stop" },
+ { points: "If exactly one is null, or their values differ, the trees are not isomorphic: stop" },
{
points: "Otherwise, compare children two ways:",
subpoints: [
@@ -107,8 +107,8 @@ const Content = () => {
];
const complexity = [
- { points: "Time Complexity: O(n) in the best case, up to O(4^n) in the worst case for the naive version — in practice close to O(n) for most trees since a mismatch usually short-circuits early." },
- { points: "Space Complexity: O(h) — recursion stack depth equals the smaller tree's height." },
+ { points: "Time Complexity: O(n) in the best case, up to O(4^n) in the worst case for the naive version, though in practice close to O(n) for most trees since a mismatch usually short-circuits early." },
+ { points: "Space Complexity: O(h), where recursion stack depth equals the smaller tree's height." },
];
return (
@@ -149,7 +149,7 @@ const Content = () => {
- Tree B is Tree A with children swapped at nodes 1 and 3 — they're isomorphic
+ Tree B is Tree A with children swapped at nodes 1 and 3, so they're isomorphic
diff --git a/app/visualizer/trees/algorithms/tree-isomorphism/page.jsx b/app/visualizer/trees/algorithms/tree-isomorphism/page.jsx
index 77ce355..35b9efa 100755
--- a/app/visualizer/trees/algorithms/tree-isomorphism/page.jsx
+++ b/app/visualizer/trees/algorithms/tree-isomorphism/page.jsx
@@ -14,7 +14,7 @@ import { MODULE_MAPS } from "@/lib/modulesMap";
export const metadata = {
title: "Tree Isomorphism | Animation and Explanation",
description:
- "Learn how to check whether two binary trees are isomorphic — identical once children can be freely swapped at any node — with an interactive visualizer, code examples in JavaScript, C, Python, and Java, and a quiz.",
+ "Learn how to check whether two binary trees are isomorphic (identical once children can be freely swapped at any node) with an interactive visualizer, code examples in JavaScript, C, Python, and Java, and a quiz.",
keywords: [
"Tree Isomorphism",
"Binary Tree Isomorphism",
diff --git a/app/visualizer/trees/algorithms/tree-isomorphism/quiz.jsx b/app/visualizer/trees/algorithms/tree-isomorphism/quiz.jsx
index 19aacbd..fe1dc31 100755
--- a/app/visualizer/trees/algorithms/tree-isomorphism/quiz.jsx
+++ b/app/visualizer/trees/algorithms/tree-isomorphism/quiz.jsx
@@ -14,14 +14,14 @@ const IsomorphismQuiz = () => {
"They contain the same set of values, in any arrangement"
],
correctAnswer: 1,
- explanation: "Isomorphism allows the left/right children at any node to be swapped — the trees just need to end up with matching values and branching structure after those swaps."
+ explanation: "Isomorphism allows the left/right children at any node to be swapped: the trees just need to end up with matching values and branching structure after those swaps."
},
{
question: "When comparing two nodes during the isomorphism check, what happens if exactly one of them is null?",
options: [
"They're automatically considered isomorphic",
"The algorithm swaps children and tries again",
- "The trees are not isomorphic — one has a node here and the other doesn't",
+ "The trees are not isomorphic, since one has a node here and the other doesn't",
"The null node is treated as a wildcard that matches anything"
],
correctAnswer: 2,
@@ -31,7 +31,7 @@ const IsomorphismQuiz = () => {
question: "What two comparisons does the algorithm try at each pair of nodes?",
options: [
"Only left-with-left and right-with-right",
- "Straight (left-left, right-right) and flipped (left-right, right-left) — if either succeeds, the nodes match",
+ "Straight (left-left, right-right) and flipped (left-right, right-left): if either succeeds, the nodes match",
"It compares every node in tree A against every node in tree B",
"It only compares the root nodes"
],
@@ -52,13 +52,13 @@ const IsomorphismQuiz = () => {
{
question: "In the worst case, why can the naive isomorphism check be more expensive than a simple equality check?",
options: [
- "It never is — they're always the same cost",
+ "It never is, since they're always the same cost",
"At every node it may need to try both the straight and flipped pairings, multiplying the work down each branch",
"It has to sort both trees first",
"It rebuilds both trees from scratch on each call"
],
correctAnswer: 1,
- explanation: "Every extra flip attempt branches the recursion further, which is why the worst-case cost is higher than a single-pass equality check — though most real trees short-circuit well before that."
+ explanation: "Every extra flip attempt branches the recursion further, which is why the worst-case cost is higher than a single-pass equality check, though most real trees short-circuit well before that."
}
];
diff --git a/app/visualizer/trees/applications/decision-trees/animation.jsx b/app/visualizer/trees/applications/decision-trees/animation.jsx
index 408c223..47be01a 100755
--- a/app/visualizer/trees/applications/decision-trees/animation.jsx
+++ b/app/visualizer/trees/applications/decision-trees/animation.jsx
@@ -189,7 +189,7 @@ const DecisionTreeVisualizer = () => {
setRevealed((prev) => new Set(prev).add(step.nodeId));
if (step.type === "split") {
setThresholds((prev) => [...prev, step.threshold]);
- setMessage(`Split on value ≤ ${step.threshold} — impurity ${step.impurity.toFixed(2)} → weighted ${step.weighted.toFixed(2)} after the split`);
+ setMessage(`Split on value ≤ ${step.threshold}, impurity ${step.impurity.toFixed(2)} → weighted ${step.weighted.toFixed(2)} after the split`);
} else {
setMessage(`Leaf: predict "${step.node.prediction}" (${step.node.count} sample${step.node.count === 1 ? "" : "s"}, impurity ${step.node.impurity.toFixed(2)})`);
}
diff --git a/app/visualizer/trees/applications/decision-trees/content.jsx b/app/visualizer/trees/applications/decision-trees/content.jsx
index 8009b44..0daf0a9 100755
--- a/app/visualizer/trees/applications/decision-trees/content.jsx
+++ b/app/visualizer/trees/applications/decision-trees/content.jsx
@@ -80,10 +80,10 @@ 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.`,
+ `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, using the standard measure of 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 = [
@@ -101,7 +101,7 @@ const Content = () => {
];
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: "Time Complexity: O(n · f · log n) to build, where n is the number of samples and f the number of features, since 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." },
];
@@ -143,7 +143,7 @@ const Content = () => {
- Predicting "play outside?" from temperature — two splits fully separate the classes
+ Predicting "play outside?" from temperature: two splits fully separate the classes
diff --git a/app/visualizer/trees/applications/decision-trees/quiz.jsx b/app/visualizer/trees/applications/decision-trees/quiz.jsx
index ee7a647..7bd4518 100755
--- a/app/visualizer/trees/applications/decision-trees/quiz.jsx
+++ b/app/visualizer/trees/applications/decision-trees/quiz.jsx
@@ -20,7 +20,7 @@ const DecisionTreeQuiz = () => {
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 is perfectly pure, since every example belongs to the same class",
"The group has the maximum possible mixture of classes",
"The group's feature values are all zero"
],
@@ -41,7 +41,7 @@ const DecisionTreeQuiz = () => {
{
question: "When does the recursive splitting process stop and create a leaf?",
options: [
- "It never stops — trees are always infinite",
+ "It never stops, since 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"
diff --git a/app/visualizer/trees/applications/heap-sort/animation.jsx b/app/visualizer/trees/applications/heap-sort/animation.jsx
index 2544ff1..777aa85 100755
--- a/app/visualizer/trees/applications/heap-sort/animation.jsx
+++ b/app/visualizer/trees/applications/heap-sort/animation.jsx
@@ -6,7 +6,7 @@ import ArrayGenerator from "@/app/components/ui/randomArray";
import CustomArrayInput from "@/app/components/ui/customArrayInput";
// Builds a max-heap in place, then repeatedly moves the root (the largest
-// remaining value) to the end of the array and shrinks the heap by one —
+// remaining value) to the end of the array and shrinks the heap by one,
// recording every comparison, swap, and boundary change along the way.
const heapSortSteps = (input) => {
const a = [...input];
@@ -33,7 +33,7 @@ const heapSortSteps = (input) => {
steps.push({ array: [...a], type: "swap", indices: [i, largest], heapSize, message: `Swap ${a[i]} and ${a[largest]} to restore the heap property` });
[a[i], a[largest]] = [a[largest], a[i]];
- steps.push({ array: [...a], type: "swapped", indices: [i, largest], heapSize, message: "Swapped — continue sifting down" });
+ steps.push({ array: [...a], type: "swapped", indices: [i, largest], heapSize, message: "Swapped, continue sifting down" });
i = largest;
}
};
@@ -41,10 +41,10 @@ const heapSortSteps = (input) => {
for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
siftDown(n, i);
}
- steps.push({ array: [...a], type: "heap-built", indices: [], heapSize: n, message: "Max-heap built — the largest value is now at the root" });
+ steps.push({ array: [...a], type: "heap-built", indices: [], heapSize: n, message: "Max-heap built: the largest value is now at the root" });
for (let end = n - 1; end > 0; end--) {
- steps.push({ array: [...a], type: "swap", indices: [0, end], heapSize: end + 1, message: `Move the root ${a[0]} to index ${end} — it's the largest value left in the heap` });
+ steps.push({ array: [...a], type: "swap", indices: [0, end], heapSize: end + 1, message: `Move the root ${a[0]} to index ${end}, since it's the largest value left in the heap` });
[a[0], a[end]] = [a[end], a[0]];
steps.push({ array: [...a], type: "sorted", indices: [end], heapSize: end, message: `${a[end]} is now in its final sorted position` });
siftDown(end, 0);
@@ -118,7 +118,7 @@ const HeapSortVisualizer = () => {
setHeapSize(newArray.length);
setSorted(false);
resetStats();
- setMessage("Array loaded — click Start Heap Sort");
+ setMessage("Array loaded, click Start Heap Sort");
};
const reset = () => {
@@ -360,7 +360,7 @@ const HeapSortVisualizer = () => {
) : (
- {sorted ? "Heap is empty — every element has been extracted in sorted order" : "The heap view fills in once sorting starts"}
+ {sorted ? "Heap is empty, every element has been extracted in sorted order" : "The heap view fills in once sorting starts"}
)}
diff --git a/app/visualizer/trees/applications/heap-sort/content.jsx b/app/visualizer/trees/applications/heap-sort/content.jsx
index 72882ed..e2deb8f 100755
--- a/app/visualizer/trees/applications/heap-sort/content.jsx
+++ b/app/visualizer/trees/applications/heap-sort/content.jsx
@@ -66,7 +66,7 @@ const WalkthroughDiagram = () => {
transition={{ delay: 1.1, duration: 0.5 }}
className="fill-gray-500 dark:fill-gray-400"
>
- array: [9, 5, 8, 2, 4] — the same heap, stored flat
+ array: [9, 5, 8, 2, 4], the same heap, stored flat
);
@@ -76,10 +76,10 @@ 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.`,
+ `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 = [
@@ -93,18 +93,18 @@ const Content = () => {
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",
+ "Shrink the heap size by one, since 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" },
+ { points: "Stop once the heap size reaches 1, since 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." },
+ { points: "Best Case: O(n log n), since building the heap is O(n), and each of the n extractions costs O(log n)." },
+ { points: "Average Case: O(n log n), the 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), since sorting happens in place within the array." },
];
return (
@@ -152,7 +152,7 @@ const Content = () => {
- Root — always the maximum
+ Root, always the maximum
diff --git a/app/visualizer/trees/applications/heap-sort/quiz.jsx b/app/visualizer/trees/applications/heap-sort/quiz.jsx
index 0442263..eeadb3e 100755
--- a/app/visualizer/trees/applications/heap-sort/quiz.jsx
+++ b/app/visualizer/trees/applications/heap-sort/quiz.jsx
@@ -22,10 +22,10 @@ const HeapSortQuiz = () => {
"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"
+ "Children aren't derivable from the index, so 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."
+ 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?",
@@ -43,7 +43,7 @@ const HeapSortQuiz = () => {
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",
+ "Nothing, since the array is already sorted at that point",
"The swapped element is sifted down"
],
correctAnswer: 1,
@@ -55,7 +55,7 @@ const HeapSortQuiz = () => {
"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"
+ "It isn't more predictable, since 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."
diff --git a/app/visualizer/trees/applications/huffman-coding/animation.jsx b/app/visualizer/trees/applications/huffman-coding/animation.jsx
index 1d8ecf1..3e5d41d 100755
--- a/app/visualizer/trees/applications/huffman-coding/animation.jsx
+++ b/app/visualizer/trees/applications/huffman-coding/animation.jsx
@@ -136,7 +136,7 @@ const HuffmanVisualizer = () => {
setTimeout(() => {
setRoot(finalRoot);
setCodes(computeCodes(finalRoot));
- setMessage("Only one distinct character — it gets the trivial code \"0\"");
+ setMessage("Only one distinct character, so it gets the trivial code \"0\"");
setBusy(false);
}, STEP_DELAY);
return;
@@ -159,7 +159,7 @@ const HuffmanVisualizer = () => {
setTimeout(() => {
setRoot(finalRoot);
setCodes(computeCodes(finalRoot));
- setMessage("Huffman tree complete — codes assigned by root-to-leaf path (0 = left, 1 = right)");
+ setMessage("Huffman tree complete: codes assigned by root-to-leaf path (0 = left, 1 = right)");
setBusy(false);
}, STEP_DELAY);
}
diff --git a/app/visualizer/trees/applications/huffman-coding/content.jsx b/app/visualizer/trees/applications/huffman-coding/content.jsx
index 508f165..8df1663 100755
--- a/app/visualizer/trees/applications/huffman-coding/content.jsx
+++ b/app/visualizer/trees/applications/huffman-coding/content.jsx
@@ -77,10 +77,10 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `Huffman coding compresses data by giving frequent symbols short binary codes and rare symbols longer ones — the opposite of fixed-width encodings like ASCII, where every character costs the same 8 bits regardless of how often it appears. The codes it produces are prefix-free: no character's code is a prefix of another's, which means a stream of bits can be decoded unambiguously without any separators between codes.`,
+ `Huffman coding compresses data by giving frequent symbols short binary codes and rare symbols longer ones, the opposite of fixed-width encodings like ASCII, where every character costs the same 8 bits regardless of how often it appears. The codes it produces are prefix-free: no character's code is a prefix of another's, which means a stream of bits can be decoded unambiguously without any separators between codes.`,
`The codes come directly out of a binary tree built specifically for this purpose. Every leaf holds one symbol, and a symbol's code is the sequence of left/right turns (0s and 1s) on the path from the root down to its leaf. Because frequent symbols end up near the root and rare ones get pushed deeper, frequent symbols naturally get shorter codes.`,
- `That tree is built greedily, bottom-up: start with every symbol as its own single-node tree, weighted by its frequency. Repeatedly take the two trees with the smallest total weight and merge them under a new parent node (weight = the sum of the two), putting the result back into the pool. After enough merges only one tree remains — the Huffman tree — and it's provably optimal: no other prefix-free code achieves a shorter total encoded length for that exact frequency distribution.`,
- `Huffman coding is a building block inside many real compression formats — it's the final entropy-coding stage in DEFLATE (used by ZIP and gzip), JPEG, and MP3, typically applied after some other transform has already reduced redundancy in the data.`,
+ `That tree is built greedily, bottom-up: start with every symbol as its own single-node tree, weighted by its frequency. Repeatedly take the two trees with the smallest total weight and merge them under a new parent node (weight = the sum of the two), putting the result back into the pool. After enough merges only one tree remains, the Huffman tree, and it's provably optimal: no other prefix-free code achieves a shorter total encoded length for that exact frequency distribution.`,
+ `Huffman coding is a building block inside many real compression formats: it's the final entropy-coding stage in DEFLATE (used by ZIP and gzip), JPEG, and MP3, typically applied after some other transform has already reduced redundancy in the data.`,
];
const algorithm = [
@@ -94,11 +94,11 @@ const Content = () => {
"Insert the merged tree back into the queue",
],
},
- { points: "Assign each symbol's code by reading the path from the root to its leaf — 0 for left, 1 for right" },
+ { points: "Assign each symbol's code by reading the path from the root to its leaf, using 0 for left and 1 for right" },
];
const complexity = [
- { points: "Time Complexity: O(n log n), where n is the number of distinct symbols — each of the n-1 merges costs O(log n) with a priority queue." },
+ { points: "Time Complexity: O(n log n), where n is the number of distinct symbols, since each of the n-1 merges costs O(log n) with a priority queue." },
{ points: "Space Complexity: O(n) for the tree and the code table." },
];
diff --git a/app/visualizer/trees/applications/syntax-trees/animation.jsx b/app/visualizer/trees/applications/syntax-trees/animation.jsx
index ff1b5b0..e1d3fb5 100755
--- a/app/visualizer/trees/applications/syntax-trees/animation.jsx
+++ b/app/visualizer/trees/applications/syntax-trees/animation.jsx
@@ -32,7 +32,7 @@ const tokenize = (input) => {
};
// Standard recursive-descent parser respecting + - (lowest precedence),
-// * / (higher precedence), and parentheses — grammar:
+// * / (higher precedence), and parentheses. Grammar:
// expression := term (('+'|'-') term)*
// term := factor (('*'|'/') factor)*
// factor := NUMBER | '(' expression ')'
@@ -194,7 +194,7 @@ const SyntaxTreeVisualizer = () => {
setTraversals({ prefix: toPrefix(tree), infix: toInfix(tree), postfix: toPostfix(tree) });
setError("");
clearResult();
- setMessage("Parsed into a syntax tree — operators are internal nodes, numbers are leaves");
+ setMessage("Parsed into a syntax tree: operators are internal nodes, numbers are leaves");
} catch (e) {
setError(e.message);
setRoot(null);
@@ -222,7 +222,7 @@ const SyntaxTreeVisualizer = () => {
} else {
setTimeout(() => {
setActiveIds([]);
- setMessage(`Evaluation complete — result is ${steps[steps.length - 1].result}`);
+ setMessage(`Evaluation complete: result is ${steps[steps.length - 1].result}`);
setBusy(false);
}, STEP_DELAY);
}
diff --git a/app/visualizer/trees/applications/syntax-trees/content.jsx b/app/visualizer/trees/applications/syntax-trees/content.jsx
index f09249c..384f38f 100755
--- a/app/visualizer/trees/applications/syntax-trees/content.jsx
+++ b/app/visualizer/trees/applications/syntax-trees/content.jsx
@@ -64,9 +64,9 @@ const Content = () => {
const paragraphs = [
`A syntax tree (also called an expression tree, or more generally an Abstract Syntax Tree/AST) captures the grammatical structure of an expression or piece of code, rather than its raw text. For arithmetic expressions specifically, every operator becomes an internal node with its operands as children, and every literal value becomes a leaf. The shape of the tree, not the order characters appear on the page, is what encodes precedence and grouping.`,
- `Building one from an infix expression like "3 + 4 * (2 - 1)" requires a parser that respects operator precedence and parentheses — multiplication and division bind tighter than addition and subtraction, and anything inside parentheses is parsed as its own self-contained sub-expression first. A common approach is recursive-descent parsing: one function handles addition/subtraction terms, which calls another handling multiplication/division factors, which calls another handling numbers and parenthesized groups — the recursive call structure mirrors the grammar's precedence levels directly.`,
- `Once built, the tree makes evaluation and notation conversion almost mechanical, because each is just a different tree traversal. Evaluating the expression is a post-order traversal: recursively compute both children's values first, then apply the operator to combine them — by the time an operator node is processed, both its operands are already known. Reading the tree with a pre-order traversal produces prefix notation, and post-order traversal produces postfix notation; both eliminate the need for parentheses or precedence rules entirely, since the tree structure alone determines evaluation order.`,
- `Syntax trees are the intermediate representation nearly every compiler and interpreter builds after parsing source code, before generating machine code or bytecode from it. The same idea powers calculator apps, spreadsheet formula engines, and query planners in databases — anywhere text needs to become something with an unambiguous, machine-processable structure.`,
+ `Building one from an infix expression like "3 + 4 * (2 - 1)" requires a parser that respects operator precedence and parentheses: multiplication and division bind tighter than addition and subtraction, and anything inside parentheses is parsed as its own self-contained sub-expression first. A common approach is recursive-descent parsing: one function handles addition/subtraction terms, which calls another handling multiplication/division factors, which calls another handling numbers and parenthesized groups, and this recursive call structure mirrors the grammar's precedence levels directly.`,
+ `Once built, the tree makes evaluation and notation conversion almost mechanical, because each is just a different tree traversal. Evaluating the expression is a post-order traversal: recursively compute both children's values first, then apply the operator to combine them, since by the time an operator node is processed, both its operands are already known. Reading the tree with a pre-order traversal produces prefix notation, and post-order traversal produces postfix notation; both eliminate the need for parentheses or precedence rules entirely, since the tree structure alone determines evaluation order.`,
+ `Syntax trees are the intermediate representation nearly every compiler and interpreter builds after parsing source code, before generating machine code or bytecode from it. The same idea powers calculator apps, spreadsheet formula engines, and query planners in databases: anywhere text needs to become something with an unambiguous, machine-processable structure.`,
];
const algorithm = [
@@ -84,8 +84,8 @@ const Content = () => {
];
const complexity = [
- { points: "Parsing Time: O(n) — each token is consumed exactly once by the recursive-descent parser." },
- { points: "Evaluation Time: O(n) — each node in the tree is visited exactly once during the post-order traversal." },
+ { points: "Parsing Time: O(n), since each token is consumed exactly once by the recursive-descent parser." },
+ { points: "Evaluation Time: O(n), since each node in the tree is visited exactly once during the post-order traversal." },
{ points: "Space Complexity: O(n) for the tree, plus O(h) recursion stack depth for parsing and evaluating." },
];
@@ -127,7 +127,7 @@ const Content = () => {
- The syntax tree for 3 + 4 * (2 - 1) — its shape alone encodes precedence, no parentheses needed
+ The syntax tree for 3 + 4 * (2 - 1): its shape alone encodes precedence, no parentheses needed
diff --git a/app/visualizer/trees/applications/syntax-trees/quiz.jsx b/app/visualizer/trees/applications/syntax-trees/quiz.jsx
index d40980b..c698086 100755
--- a/app/visualizer/trees/applications/syntax-trees/quiz.jsx
+++ b/app/visualizer/trees/applications/syntax-trees/quiz.jsx
@@ -14,24 +14,24 @@ const SyntaxTreeQuiz = () => {
"The tree has no leaves, only internal nodes"
],
correctAnswer: 1,
- explanation: "Every operator becomes an internal node with its operands as children, while literal values sit at the leaves — the tree's shape itself encodes structure."
+ explanation: "Every operator becomes an internal node with its operands as children, while literal values sit at the leaves, so the tree's shape itself encodes structure."
},
{
question: "How does a recursive-descent parser handle operator precedence (e.g. * binding tighter than +)?",
options: [
- "It doesn't — precedence must be added afterward",
+ "It doesn't: precedence must be added afterward",
"Separate parsing functions handle each precedence level, with higher-precedence operators parsed by functions called deeper in the recursion",
"It sorts all operators alphabetically",
"Precedence is looked up in a table after the whole tree is built"
],
correctAnswer: 1,
- explanation: "A function for +/- calls a function for */ which calls a function for factors/parentheses — that call structure directly mirrors the grammar's precedence levels."
+ explanation: "A function for +/- calls a function for */ which calls a function for factors/parentheses, and that call structure directly mirrors the grammar's precedence levels."
},
{
question: "Why must both children of an operator node be evaluated before the operator itself can be applied?",
options: [
- "They don't need to be — order doesn't matter",
- "The operator combines its operands' values, so both values must be known first — this is exactly what a post-order traversal guarantees",
+ "They don't need to be, since order doesn't matter",
+ "The operator combines its operands' values, so both values must be known first, which is exactly what a post-order traversal guarantees",
"Only the left child needs to be evaluated",
"Evaluation happens in pre-order, before the children"
],
@@ -47,7 +47,7 @@ const SyntaxTreeQuiz = () => {
"A sorted list of operands"
],
correctAnswer: 1,
- explanation: "Pre-order visits the operator before its operands, which is exactly what prefix notation looks like — the operator written before its arguments."
+ explanation: "Pre-order visits the operator before its operands, which is exactly what prefix notation looks like: the operator written before its arguments."
},
{
question: "Why don't prefix and postfix notation need parentheses to remove ambiguity?",
@@ -58,7 +58,7 @@ const SyntaxTreeQuiz = () => {
"Because addition and subtraction aren't allowed in those notations"
],
correctAnswer: 1,
- explanation: "Since every operator has a known, fixed number of operands and the traversal order is unambiguous, there's no other way to parse the sequence back — unlike infix notation, which needs precedence rules and parentheses."
+ explanation: "Since every operator has a known, fixed number of operands and the traversal order is unambiguous, there's no other way to parse the sequence back, unlike infix notation, which needs precedence rules and parentheses."
}
];
diff --git a/app/visualizer/trees/binaryTree/properties/animation.jsx b/app/visualizer/trees/binaryTree/properties/animation.jsx
index 40c58b0..3d50366 100755
--- a/app/visualizer/trees/binaryTree/properties/animation.jsx
+++ b/app/visualizer/trees/binaryTree/properties/animation.jsx
@@ -183,7 +183,7 @@ const TreePropertiesVisualizer = () => {
- No tree yet — insert a value or generate a random tree
+ No tree yet, insert a value or generate a random tree
)}
diff --git a/app/visualizer/trees/binaryTree/properties/content.jsx b/app/visualizer/trees/binaryTree/properties/content.jsx
index 649c710..265303e 100755
--- a/app/visualizer/trees/binaryTree/properties/content.jsx
+++ b/app/visualizer/trees/binaryTree/properties/content.jsx
@@ -110,9 +110,9 @@ const Content = () => {
const paragraphs = [
`A Binary Tree is a hierarchical structure built from nodes, where every node has at most two children, conventionally called the left and right child. That two-child limit is what separates it from a general tree, and it's what makes every property below meaningful and calculable.`,
- `Height and depth are the two measurements that come up constantly when reasoning about a tree's performance — most tree operations run in time proportional to the height, not the number of nodes, which is exactly why keeping a tree balanced matters so much.`,
+ `Height and depth are the two measurements that come up constantly when reasoning about a tree's performance: most tree operations run in time proportional to the height, not the number of nodes, which is exactly why keeping a tree balanced matters so much.`,
`Space Complexity for storing a binary tree with n nodes is O(n), since each node needs a fixed amount of memory (its value plus two child pointers) regardless of the tree's shape.`,
- `These structural properties aren't just theory — they're what a balancing algorithm (like in AVL or Red-Black trees) is actively trying to protect. A tree that's allowed to grow unchecked can degrade to the same O(n) worst case as a linked list.`,
+ `These structural properties aren't just theory: they're what a balancing algorithm (like in AVL or Red-Black trees) is actively trying to protect. A tree that's allowed to grow unchecked can degrade to the same O(n) worst case as a linked list.`,
];
const terminology = [
@@ -124,7 +124,7 @@ const Content = () => {
{ term: "Edge", def: "The connection/link between a parent and its child." },
{ term: "Depth of a node", def: "Number of edges from the root down to that node." },
{ term: "Height of a node", def: "Number of edges on the longest path from that node down to a leaf." },
- { term: "Height of the tree", def: "The height of the root node — the longest root-to-leaf path." },
+ { term: "Height of the tree", def: "The height of the root node, the longest root-to-leaf path." },
];
const nodeCountFormulas = [
@@ -137,7 +137,7 @@ const Content = () => {
const balanceComparison = [
{ points: "Balanced tree, n = 7 nodes → height = 2 (as close to log₂ 7 as possible)" },
{ points: "Skewed tree, n = 7 nodes → height = 6 (every node has exactly one child)" },
- { points: "Same node count, wildly different performance — height is what actually matters" },
+ { points: "Same node count, wildly different performance: height is what actually matters" },
];
return (
@@ -278,7 +278,7 @@ const Content = () => {
Since most tree operations (search, insert, delete) walk from the root
- down to a leaf, their time complexity is O(height) — O(log n) for a
+ down to a leaf, their time complexity is O(height): O(log n) for a
balanced tree, degrading to O(n) for a skewed one.
diff --git a/app/visualizer/trees/binaryTree/properties/page.jsx b/app/visualizer/trees/binaryTree/properties/page.jsx
index ed0330f..9a81775 100755
--- a/app/visualizer/trees/binaryTree/properties/page.jsx
+++ b/app/visualizer/trees/binaryTree/properties/page.jsx
@@ -14,7 +14,7 @@ import { MODULE_MAPS } from "@/lib/modulesMap";
export const metadata = {
title: "Binary Tree Properties | Height, Depth, Node Count & Balance Explained",
description:
- "Learn the core structural properties of Binary Trees — height, depth, level, node count formulas, leaf vs internal nodes, and why balance matters — with an interactive tree builder, code examples in JavaScript, C, Python, and Java, and a quiz.",
+ "Learn the core structural properties of Binary Trees, including height, depth, level, node count formulas, leaf vs internal nodes, and why balance matters, with an interactive tree builder, code examples in JavaScript, C, Python, and Java, and a quiz.",
keywords: [
"Binary Tree Properties",
"Binary Tree Height",
diff --git a/app/visualizer/trees/binaryTree/properties/quiz.jsx b/app/visualizer/trees/binaryTree/properties/quiz.jsx
index ea9bc70..2597812 100755
--- a/app/visualizer/trees/binaryTree/properties/quiz.jsx
+++ b/app/visualizer/trees/binaryTree/properties/quiz.jsx
@@ -32,7 +32,7 @@ const BinaryTreePropertiesQuiz = () => {
"A leaf always has two children"
],
correctAnswer: 1,
- explanation: "A leaf node is any node with no children at all — both its left and right pointers are null."
+ explanation: "A leaf node is any node with no children at all: both its left and right pointers are null."
},
{
question: "Why does a skewed (degenerate) binary tree perform worse than a balanced one?",
diff --git a/app/visualizer/trees/binaryTree/types/content.jsx b/app/visualizer/trees/binaryTree/types/content.jsx
index aa735b8..f0c55c8 100755
--- a/app/visualizer/trees/binaryTree/types/content.jsx
+++ b/app/visualizer/trees/binaryTree/types/content.jsx
@@ -194,13 +194,13 @@ const Content = () => {
title: "Full Binary Tree",
svgRef: fullSvg,
description:
- "In a Full Binary Tree, there's no such thing as a node with just one child — every node has either zero children or exactly two. That strict rule keeps the tree evenly shaped, with leaves sitting at the same level or one level apart, which makes it a good starting point for understanding how balanced trees behave.",
+ "In a Full Binary Tree, there's no such thing as a node with just one child: every node has either zero children or exactly two. That strict rule keeps the tree evenly shaped, with leaves sitting at the same level or one level apart, which makes it a good starting point for understanding how balanced trees behave.",
},
{
title: "Degenerate (Skewed) Tree",
svgRef: degenSvg,
description:
- "A Degenerate, or Skewed, Tree is what you get when every parent node has only a single child — at that point it's really just a linked list wearing a tree's name. This is the worst case for height, Θ(n), and it typically happens when you insert already-sorted data into a binary search tree with no rebalancing, which tanks the performance benefits a tree is supposed to give you.",
+ "A Degenerate, or Skewed, Tree is what you get when every parent node has only a single child, at which point it's really just a linked list wearing a tree's name. This is the worst case for height, Θ(n), and it typically happens when you insert already-sorted data into a binary search tree with no rebalancing, which tanks the performance benefits a tree is supposed to give you.",
},
{
title: "Complete Binary Tree",
diff --git a/app/visualizer/trees/binaryTree/types/quiz.jsx b/app/visualizer/trees/binaryTree/types/quiz.jsx
index dec8369..90cf20f 100755
--- a/app/visualizer/trees/binaryTree/types/quiz.jsx
+++ b/app/visualizer/trees/binaryTree/types/quiz.jsx
@@ -14,7 +14,7 @@ const BinaryTreeTypesQuiz = () => {
"Every node has exactly one child"
],
correctAnswer: 1,
- explanation: "A Full Binary Tree only requires that no node has exactly one child — every node has either zero children (a leaf) or two."
+ explanation: "A Full Binary Tree only requires that no node has exactly one child: every node has either zero children (a leaf) or two."
},
{
question: "A Degenerate (Skewed) Binary Tree behaves most like which other data structure?",
diff --git a/app/visualizer/trees/bst/avl/animation.jsx b/app/visualizer/trees/bst/avl/animation.jsx
index ee61047..081fb02 100755
--- a/app/visualizer/trees/bst/avl/animation.jsx
+++ b/app/visualizer/trees/bst/avl/animation.jsx
@@ -146,11 +146,11 @@ const AvlVisualizer = () => {
setRoot(newRoot);
if (rotations.length > 0) {
const r = rotations[rotations.length - 1];
- setMessage(`Inserted ${value} — ${r.type} case detected, rotated at node ${r.at} to restore balance`);
+ setMessage(`Inserted ${value}: ${r.type} case detected, rotated at node ${r.at} to restore balance`);
setRotatedValue(r.at);
setTimeout(() => setRotatedValue(null), 1300);
} else {
- setMessage(`Inserted ${value} — tree stayed balanced, no rotation needed`);
+ setMessage(`Inserted ${value}, tree stayed balanced, no rotation needed`);
}
setInputValue("");
setBusy(false);
@@ -373,7 +373,7 @@ const AvlVisualizer = () => {
) : (
- No tree yet — insert a value or generate a random tree
+ No tree yet, insert a value or generate a random tree
)}
diff --git a/app/visualizer/trees/bst/avl/content.jsx b/app/visualizer/trees/bst/avl/content.jsx
index db29c3c..55144c9 100755
--- a/app/visualizer/trees/bst/avl/content.jsx
+++ b/app/visualizer/trees/bst/avl/content.jsx
@@ -54,10 +54,10 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `A plain BST offers no guarantee about its shape — insert values in the wrong order and it degenerates into a linked list with O(n) operations (see BST Insertion). An AVL tree fixes this by adding one rule on top of the normal BST ordering: for every node, the heights of its left and right subtrees may differ by at most 1. This is called the balance factor, and it's recalculated bottom-up after every insertion or deletion.`,
- `Whenever an insertion pushes a node's balance factor to +2 or -2, the tree is out of balance and needs a rotation — a local rearrangement of a few pointers that restores the height rule without breaking the BST ordering. There are four possible imbalance shapes, and each has a matching fix: a single rotation for the two "straight-line" cases (Left-Left, Right-Right), and a double rotation for the two "zig-zag" cases (Left-Right, Right-Left).`,
- `Because at most O(log n) ancestors need their balance factor rechecked after an insertion, and each rotation touches only a constant number of pointers, a single insertion or deletion does at most one rotation (single or double) to restore balance — so the extra bookkeeping AVL trees do is cheap.`,
- `The payoff is that an AVL tree's height is always O(log n), no matter what order values are inserted in — unlike a plain BST, it can never degrade into a skewed shape. This makes search, insertion, and deletion all worst-case O(log n), not just average-case.`,
+ `A plain BST offers no guarantee about its shape: insert values in the wrong order and it degenerates into a linked list with O(n) operations (see BST Insertion). An AVL tree fixes this by adding one rule on top of the normal BST ordering: for every node, the heights of its left and right subtrees may differ by at most 1. This is called the balance factor, and it's recalculated bottom-up after every insertion or deletion.`,
+ `Whenever an insertion pushes a node's balance factor to +2 or -2, the tree is out of balance and needs a rotation, a local rearrangement of a few pointers that restores the height rule without breaking the BST ordering. There are four possible imbalance shapes, and each has a matching fix: a single rotation for the two "straight-line" cases (Left-Left, Right-Right), and a double rotation for the two "zig-zag" cases (Left-Right, Right-Left).`,
+ `Because at most O(log n) ancestors need their balance factor rechecked after an insertion, and each rotation touches only a constant number of pointers, a single insertion or deletion does at most one rotation (single or double) to restore balance, so the extra bookkeeping AVL trees do is cheap.`,
+ `The payoff is that an AVL tree's height is always O(log n), no matter what order values are inserted in. Unlike a plain BST, it can never degrade into a skewed shape. This makes search, insertion, and deletion all worst-case O(log n), not just average-case.`,
];
const cases = [
diff --git a/app/visualizer/trees/bst/avl/page.jsx b/app/visualizer/trees/bst/avl/page.jsx
index 4ff3a6d..8573c0a 100755
--- a/app/visualizer/trees/bst/avl/page.jsx
+++ b/app/visualizer/trees/bst/avl/page.jsx
@@ -14,7 +14,7 @@ import { MODULE_MAPS } from "@/lib/modulesMap";
export const metadata = {
title: "AVL Tree Balancing | Step-by-Step Rotation Animation & Explanation",
description:
- "Learn how AVL trees self-balance a Binary Search Tree with rotations — Left-Left, Right-Right, Left-Right, Right-Left — with an interactive visualizer, code examples in JavaScript, C, Python, and Java, and a quiz.",
+ "Learn how AVL trees self-balance a Binary Search Tree with rotations (Left-Left, Right-Right, Left-Right, Right-Left), with an interactive visualizer, code examples in JavaScript, C, Python, and Java, and a quiz.",
keywords: [
"AVL Tree",
"AVL Tree Balancing",
diff --git a/app/visualizer/trees/bst/avl/quiz.jsx b/app/visualizer/trees/bst/avl/quiz.jsx
index 2395ca1..8880d4a 100755
--- a/app/visualizer/trees/bst/avl/quiz.jsx
+++ b/app/visualizer/trees/bst/avl/quiz.jsx
@@ -14,7 +14,7 @@ const AvlQuiz = () => {
"Values must be inserted in sorted order"
],
correctAnswer: 1,
- explanation: "This is the AVL balance condition — for every node, |height(left) - height(right)| ≤ 1."
+ explanation: "This is the AVL balance condition: for every node, |height(left) - height(right)| ≤ 1."
},
{
question: "When does an AVL tree perform a rotation?",
@@ -22,7 +22,7 @@ const AvlQuiz = () => {
"After every single insertion, regardless of balance",
"Only when explicitly requested by the user",
"When a node's balance factor becomes +2 or -2 after an insert/delete",
- "Never — AVL trees don't use rotations"
+ "Never, AVL trees don't use rotations"
],
correctAnswer: 2,
explanation: "A rotation is only triggered when an operation pushes some node's balance factor outside the allowed [-1, 1] range."
@@ -42,7 +42,7 @@ const AvlQuiz = () => {
question: "What is the worst-case height of an AVL tree with n nodes?",
options: ["O(n)", "O(n log n)", "O(log n)", "O(1)"],
correctAnswer: 2,
- explanation: "The balance condition guarantees the height stays O(log n) no matter what order values are inserted in — this is the whole point of AVL trees."
+ explanation: "The balance condition guarantees the height stays O(log n) no matter what order values are inserted in, which is the whole point of AVL trees."
},
{
question: "Why do AVL trees exist when plain BSTs already support search, insert, and delete?",
@@ -53,7 +53,7 @@ const AvlQuiz = () => {
"AVL trees are always faster to build from scratch"
],
correctAnswer: 1,
- explanation: "A plain BST's performance depends entirely on insertion order. AVL trees pay a small extra cost on every insert/delete to guarantee the height — and therefore every operation — stays O(log n)."
+ explanation: "A plain BST's performance depends entirely on insertion order. AVL trees pay a small extra cost on every insert/delete to guarantee the height, and therefore every operation, stays O(log n)."
}
];
diff --git a/app/visualizer/trees/bst/deletion/animation.jsx b/app/visualizer/trees/bst/deletion/animation.jsx
index 4bf3be9..20e8126 100755
--- a/app/visualizer/trees/bst/deletion/animation.jsx
+++ b/app/visualizer/trees/bst/deletion/animation.jsx
@@ -144,10 +144,10 @@ const BstDeletionVisualizer = () => {
setSuccessorValue(successor);
setMessage(
successor !== null
- ? `Deleting ${value} — replacing it with in-order successor ${successor}`
+ ? `Deleting ${value}, replacing it with in-order successor ${successor}`
: hasLeft || hasRight
- ? `Deleting ${value} — promoting its only child`
- : `Deleting ${value} — it's a leaf, simply removed`
+ ? `Deleting ${value}, promoting its only child`
+ : `Deleting ${value}, it's a leaf, simply removed`
);
setTimeout(() => {
@@ -392,7 +392,7 @@ const BstDeletionVisualizer = () => {
) : (
- No tree yet — insert a value or generate a random tree
+ No tree yet, insert a value or generate a random tree
)}
diff --git a/app/visualizer/trees/bst/deletion/content.jsx b/app/visualizer/trees/bst/deletion/content.jsx
index 8656220..3e6a30b 100755
--- a/app/visualizer/trees/bst/deletion/content.jsx
+++ b/app/visualizer/trees/bst/deletion/content.jsx
@@ -54,16 +54,16 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `Deleting from a Binary Search Tree starts the same way insertion does — you search for the value by comparing and moving left or right — but once you find it, keeping the tree a valid BST afterward takes more care than insertion ever did, because removing a node can leave a gap that needs to be patched correctly.`,
- `There are exactly three shapes the node-to-delete can have, and each is handled differently: a leaf is simply removed, a node with one child is replaced by that child, and a node with two children is trickier — you can't just delete it without breaking the ordering, so you borrow a replacement value from elsewhere in the tree.`,
- `Deletion needs O(1) extra space beyond the recursion stack — no matter which of the three cases applies, only a constant number of pointers get rewired.`,
- `Repeated deletions (and insertions) can gradually unbalance a BST even if it started out balanced, which is exactly the problem self-balancing trees like AVL and Red-Black trees are designed to prevent — they perform extra rotation work on every insert/delete specifically to keep the height close to log n.`,
+ `Deleting from a Binary Search Tree starts the same way insertion does: you search for the value by comparing and moving left or right. But once you find it, keeping the tree a valid BST afterward takes more care than insertion ever did, because removing a node can leave a gap that needs to be patched correctly.`,
+ `There are exactly three shapes the node-to-delete can have, and each is handled differently: a leaf is simply removed, a node with one child is replaced by that child, and a node with two children is trickier, since you can't just delete it without breaking the ordering, so you borrow a replacement value from elsewhere in the tree.`,
+ `Deletion needs O(1) extra space beyond the recursion stack, since no matter which of the three cases applies, only a constant number of pointers get rewired.`,
+ `Repeated deletions (and insertions) can gradually unbalance a BST even if it started out balanced, which is exactly the problem self-balancing trees like AVL and Red-Black trees are designed to prevent: they perform extra rotation work on every insert/delete specifically to keep the height close to log n.`,
];
const cases = [
{
title: "Case 1: Deleting a leaf",
- body: "No children to worry about — just remove the node outright. The parent's pointer to it becomes null.",
+ body: "No children to worry about, just remove the node outright. The parent's pointer to it becomes null.",
},
{
title: "Case 2: Deleting a node with one child",
@@ -71,7 +71,7 @@ const Content = () => {
},
{
title: "Case 3: Deleting a node with two children",
- body: "Find the in-order successor (the smallest value in the right subtree), copy that value into the node being deleted, then delete the successor from its original spot — which is guaranteed to be a leaf or have only a right child, so it reduces to Case 1 or Case 2.",
+ body: "Find the in-order successor (the smallest value in the right subtree), copy that value into the node being deleted, then delete the successor from its original spot, which is guaranteed to be a leaf or have only a right child, so it reduces to Case 1 or Case 2.",
},
];
diff --git a/app/visualizer/trees/bst/deletion/page.jsx b/app/visualizer/trees/bst/deletion/page.jsx
index 077c0c1..9059da9 100755
--- a/app/visualizer/trees/bst/deletion/page.jsx
+++ b/app/visualizer/trees/bst/deletion/page.jsx
@@ -14,7 +14,7 @@ import { MODULE_MAPS } from "@/lib/modulesMap";
export const metadata = {
title: "Binary Search Tree Deletion | Step-by-Step Animation & Explanation",
description:
- "Learn how deletion works in a Binary Search Tree — leaf, one-child, and two-children cases, in-order successor replacement — with an interactive visualizer, code examples in JavaScript, C, Python, and Java, and a quiz.",
+ "Learn how deletion works in a Binary Search Tree, covering leaf, one-child, and two-children cases, plus in-order successor replacement, with an interactive visualizer, code examples in JavaScript, C, Python, and Java, and a quiz.",
keywords: [
"Binary Search Tree Deletion",
"BST Deletion",
diff --git a/app/visualizer/trees/bst/deletion/quiz.jsx b/app/visualizer/trees/bst/deletion/quiz.jsx
index 10126a1..500afb6 100755
--- a/app/visualizer/trees/bst/deletion/quiz.jsx
+++ b/app/visualizer/trees/bst/deletion/quiz.jsx
@@ -9,7 +9,7 @@ const BstDeletionQuiz = () => {
question: "How do you delete a leaf node from a BST?",
options: [
"Replace it with its parent",
- "Simply remove it — its parent's pointer becomes null",
+ "Simply remove it, its parent's pointer becomes null",
"Find its in-order successor",
"You can't delete a leaf"
],
@@ -58,7 +58,7 @@ const BstDeletionQuiz = () => {
"It does require handling that case again"
],
correctAnswer: 1,
- explanation: "Since the successor is found by going as far left as possible, it can't have a left child — so removing it is always the leaf case or the one-child case."
+ explanation: "Since the successor is found by going as far left as possible, it can't have a left child, so removing it is always the leaf case or the one-child case."
}
];
diff --git a/app/visualizer/trees/bst/insertion/animation.jsx b/app/visualizer/trees/bst/insertion/animation.jsx
index c596c1d..f8bd1ed 100755
--- a/app/visualizer/trees/bst/insertion/animation.jsx
+++ b/app/visualizer/trees/bst/insertion/animation.jsx
@@ -291,7 +291,7 @@ const BstInsertionVisualizer = () => {
) : (
- No tree yet — insert a value or generate a random tree
+ No tree yet, insert a value or generate a random tree
)}
diff --git a/app/visualizer/trees/bst/insertion/content.jsx b/app/visualizer/trees/bst/insertion/content.jsx
index ea48d24..de458b0 100755
--- a/app/visualizer/trees/bst/insertion/content.jsx
+++ b/app/visualizer/trees/bst/insertion/content.jsx
@@ -71,10 +71,10 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `A Binary Search Tree keeps every node's left subtree smaller and its right subtree larger, and insertion is what builds that ordering up one value at a time. To insert a new value, you walk down from the root exactly the way you'd search for it — go left when the value is smaller, go right when it's larger — and the moment you fall off the tree (hit a null pointer), that's where the new node gets attached.`,
- `Because every insertion is really just a failed search, the new node always becomes a leaf. Nothing above it has to move or shift — inserting into a BST never requires shuffling existing nodes around, only adding one new connection at the bottom.`,
+ `A Binary Search Tree keeps every node's left subtree smaller and its right subtree larger, and insertion is what builds that ordering up one value at a time. To insert a new value, you walk down from the root exactly the way you'd search for it: go left when the value is smaller, go right when it's larger, and the moment you fall off the tree (hit a null pointer), that's where the new node gets attached.`,
+ `Because every insertion is really just a failed search, the new node always becomes a leaf. Nothing above it has to move or shift, since inserting into a BST never requires shuffling existing nodes around, only adding one new connection at the bottom.`,
`Insertion needs O(1) extra space beyond the recursion stack, since it only ever creates a single new node.`,
- `The shape you end up with depends entirely on insertion order. Insert already-sorted data and you get a degenerate, linked-list-shaped tree (see Binary Tree Types) with Θ(n) height. Insert in a randomized order and the tree tends to stay close to balanced, keeping height near Θ(log n) — this is exactly why self-balancing trees like AVL exist.`,
+ `The shape you end up with depends entirely on insertion order. Insert already-sorted data and you get a degenerate, linked-list-shaped tree (see Binary Tree Types) with Θ(n) height. Insert in a randomized order and the tree tends to stay close to balanced, keeping height near Θ(log n), which is exactly why self-balancing trees like AVL exist.`,
];
const walkthrough = [
@@ -92,7 +92,7 @@ const Content = () => {
subpoints: [
"If smaller, move to the left child",
"If larger, move to the right child",
- "If equal, stop (duplicate — most BSTs ignore or reject it)",
+ "If equal, stop (duplicate, which most BSTs ignore or reject)",
],
},
{ points: "Repeat until you reach a null (empty) pointer" },
diff --git a/app/visualizer/trees/bst/insertion/quiz.jsx b/app/visualizer/trees/bst/insertion/quiz.jsx
index 3324374..19c37e7 100755
--- a/app/visualizer/trees/bst/insertion/quiz.jsx
+++ b/app/visualizer/trees/bst/insertion/quiz.jsx
@@ -15,7 +15,7 @@ const BstInsertionQuiz = () => {
question: "Where does a newly inserted value always end up in a BST?",
options: ["At the root", "As a leaf node", "As the left child of the root", "Wherever there's the most free space"],
correctAnswer: 1,
- explanation: "Insertion follows the same path a search would take until it falls off the tree (hits a null pointer) — that's always at some leaf position."
+ explanation: "Insertion follows the same path a search would take until it falls off the tree (hits a null pointer), which is always at some leaf position."
},
{
question: "Why doesn't inserting into a BST ever require moving existing nodes?",
@@ -26,7 +26,7 @@ const BstInsertionQuiz = () => {
"It does require moving nodes"
],
correctAnswer: 1,
- explanation: "Since insertion is just a search that stops at a null pointer, only one new connection is created — nothing else in the tree has to shift."
+ explanation: "Since insertion is just a search that stops at a null pointer, only one new connection is created, so nothing else in the tree has to shift."
},
{
question: "What is the time complexity of insertion in a balanced BST?",
@@ -43,7 +43,7 @@ const BstInsertionQuiz = () => {
"Insertion fails"
],
correctAnswer: 2,
- explanation: "Sorted input makes every new node attach as the only child of the previous one, producing a degenerate tree with O(n) height — and O(n) insertion time."
+ explanation: "Sorted input makes every new node attach as the only child of the previous one, producing a degenerate tree with O(n) height and O(n) insertion time."
}
];
diff --git a/app/visualizer/trees/bst/searching/animation.jsx b/app/visualizer/trees/bst/searching/animation.jsx
index a1f934d..8fbacd2 100755
--- a/app/visualizer/trees/bst/searching/animation.jsx
+++ b/app/visualizer/trees/bst/searching/animation.jsx
@@ -156,7 +156,7 @@ const BstSearchingVisualizer = () => {
setMessage(`Found ${value} after ${path.length} comparison${path.length === 1 ? "" : "s"}`);
} else {
setNotFoundValue(path[path.length - 1]);
- setMessage(`${value} isn't in the tree — search ended at ${path[path.length - 1]}`);
+ setMessage(`${value} isn't in the tree, search ended at ${path[path.length - 1]}`);
}
setBusy(false);
}, STEP_DELAY);
@@ -406,7 +406,7 @@ const BstSearchingVisualizer = () => {
) : (
- No tree yet — insert a value or generate a random tree
+ No tree yet, insert a value or generate a random tree
)}
diff --git a/app/visualizer/trees/bst/searching/content.jsx b/app/visualizer/trees/bst/searching/content.jsx
index 103d89e..ad267ce 100755
--- a/app/visualizer/trees/bst/searching/content.jsx
+++ b/app/visualizer/trees/bst/searching/content.jsx
@@ -72,10 +72,10 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `Searching a Binary Search Tree is just insertion's walk without the final "attach a node" step — start at the root, compare the target against the current node, and let the BST property tell you exactly which way to go: smaller means left, larger means right. Either you land on the value, or you eventually fall off the tree (hit a null pointer), which means it isn't there.`,
- `This is the entire reason a BST is useful in the first place — the sorted structure lets you eliminate an entire subtree with every comparison, the same way binary search eliminates half of a sorted array. That's what gets search down to O(log n) instead of the O(n) you'd need to scan an unsorted structure.`,
- `Search needs O(1) additional space (excluding the recursion call stack), since it never creates or modifies any nodes — it's a read-only walk.`,
- `Because search, insertion, and deletion all follow this same root-to-leaf comparison walk, they share the same best/worst-case complexity profile — which is exactly why keeping a BST balanced (see AVL trees) matters for all three operations, not just one.`,
+ `Searching a Binary Search Tree is just insertion's walk without the final "attach a node" step: start at the root, compare the target against the current node, and let the BST property tell you exactly which way to go, smaller means left, larger means right. Either you land on the value, or you eventually fall off the tree (hit a null pointer), which means it isn't there.`,
+ `This is the entire reason a BST is useful in the first place. The sorted structure lets you eliminate an entire subtree with every comparison, the same way binary search eliminates half of a sorted array. That's what gets search down to O(log n) instead of the O(n) you'd need to scan an unsorted structure.`,
+ `Search needs O(1) additional space (excluding the recursion call stack), since it never creates or modifies any nodes: it's a read-only walk.`,
+ `Because search, insertion, and deletion all follow this same root-to-leaf comparison walk, they share the same best/worst-case complexity profile, which is exactly why keeping a BST balanced (see AVL trees) matters for all three operations, not just one.`,
];
const walkthrough = [
@@ -89,7 +89,7 @@ const Content = () => {
{ points: "Searching for a value that isn't present follows the same path logic" },
{ points: "The walk continues left/right based on comparisons" },
{ points: "Eventually it reaches a null pointer instead of a matching node" },
- { points: "That null pointer means the value isn't in the tree — search stops there" },
+ { points: "That null pointer means the value isn't in the tree, so search stops there" },
];
const algorithm = [
@@ -97,7 +97,7 @@ const Content = () => {
{
points: "Compare the target value with the current node:",
subpoints: [
- "If equal, return the node — found",
+ "If equal, return the node (found)",
"If smaller, move to the left child",
"If larger, move to the right child",
],
diff --git a/app/visualizer/trees/bst/searching/quiz.jsx b/app/visualizer/trees/bst/searching/quiz.jsx
index 59a7822..dc73b66 100755
--- a/app/visualizer/trees/bst/searching/quiz.jsx
+++ b/app/visualizer/trees/bst/searching/quiz.jsx
@@ -20,7 +20,7 @@ const BstSearchingQuiz = () => {
"It always assumes the value exists"
],
correctAnswer: 1,
- explanation: "The search keeps following the comparison-based path until it either finds a match or falls off the tree onto a null pointer — that null is the 'not found' signal."
+ explanation: "The search keeps following the comparison-based path until it either finds a match or falls off the tree onto a null pointer. That null is the 'not found' signal."
},
{
question: "Why is searching a BST faster than searching an unsorted linked list?",
@@ -37,7 +37,7 @@ const BstSearchingQuiz = () => {
question: "What is the best-case time complexity for BST search?",
options: ["O(log n)", "O(n)", "O(1)", "O(n log n)"],
correctAnswer: 2,
- explanation: "If the target happens to be the root, it's found in a single comparison — O(1)."
+ explanation: "If the target happens to be the root, it's found in a single comparison: O(1)."
},
{
question: "Why do search, insertion, and deletion all share the same worst-case complexity in a BST?",
@@ -48,7 +48,7 @@ const BstSearchingQuiz = () => {
"They don't share the same complexity"
],
correctAnswer: 1,
- explanation: "Insertion and deletion both start with essentially the same search used for lookup, so all three are O(height) — which is why a skewed tree hurts all of them equally."
+ explanation: "Insertion and deletion both start with essentially the same search used for lookup, so all three are O(height), which is why a skewed tree hurts all of them equally."
}
];
diff --git a/app/visualizer/trees/traversal/in-order/animation.jsx b/app/visualizer/trees/traversal/in-order/animation.jsx
index 4f8a6b3..920a0af 100755
--- a/app/visualizer/trees/traversal/in-order/animation.jsx
+++ b/app/visualizer/trees/traversal/in-order/animation.jsx
@@ -139,7 +139,7 @@ const InOrderVisualizer = () => {
setTimeout(revealStep, STEP_DELAY);
} else {
setTimeout(() => {
- setMessage(`In-order traversal complete: [${sequence.join(", ")}] — sorted order, since this is a BST`);
+ setMessage(`In-order traversal complete: [${sequence.join(", ")}], in sorted order, since this is a BST`);
setCurrentValue(null);
setBusy(false);
}, STEP_DELAY);
@@ -341,7 +341,7 @@ const InOrderVisualizer = () => {
) : (
- No tree yet — insert a value or generate a random tree
+ No tree yet, insert a value or generate a random tree
)}
diff --git a/app/visualizer/trees/traversal/in-order/content.jsx b/app/visualizer/trees/traversal/in-order/content.jsx
index e4fc206..53b1244 100755
--- a/app/visualizer/trees/traversal/in-order/content.jsx
+++ b/app/visualizer/trees/traversal/in-order/content.jsx
@@ -64,19 +64,19 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `In-order traversal visits a node's left subtree, then the node itself, then its right subtree — the order is Left, Root, Right. The "in" refers to the root being processed in between its two children, rather than before or after both of them.`,
- `In-order traversal has one property that makes it stand out from pre-order and post-order: when run on a Binary Search Tree, it visits every value in strictly ascending sorted order. That falls directly out of the BST invariant — every value in a node's left subtree is smaller than the node, and every value in its right subtree is larger, so visiting left-root-right at every level naturally produces sorted output.`,
- `This is why in-order traversal is the standard way to read a BST's contents in sorted order without needing a separate sort step, and why it's used to validate whether a tree actually satisfies the BST property — if the in-order sequence isn't strictly increasing, the tree isn't a valid BST.`,
- `Traversal needs O(h) extra space for the recursion call stack, where h is the tree's height — O(log n) for a balanced tree, but O(n) in the worst case of a completely skewed tree.`,
+ `In-order traversal visits a node's left subtree, then the node itself, then its right subtree, so the order is Left, Root, Right. The "in" refers to the root being processed in between its two children, rather than before or after both of them.`,
+ `In-order traversal has one property that makes it stand out from pre-order and post-order: when run on a Binary Search Tree, it visits every value in strictly ascending sorted order. That falls directly out of the BST invariant: every value in a node's left subtree is smaller than the node, and every value in its right subtree is larger, so visiting left-root-right at every level naturally produces sorted output.`,
+ `This is why in-order traversal is the standard way to read a BST's contents in sorted order without needing a separate sort step, and why it's used to validate whether a tree actually satisfies the BST property. If the in-order sequence isn't strictly increasing, the tree isn't a valid BST.`,
+ `Traversal needs O(h) extra space for the recursion call stack, where h is the tree's height: O(log n) for a balanced tree, but O(n) in the worst case of a completely skewed tree.`,
];
const walkthrough = [
- { points: "Start at the root (8), but don't visit it yet — first go left" },
- { points: "At 3, go left again first: reach 1 (a leaf) — visit it: 1" },
- { points: "Back at 3, its left is done — visit 3: 1, 3" },
- { points: "Now go right from 3: reach 6 (a leaf) — visit it: 1, 3, 6" },
- { points: "Back at 8, its left subtree is fully done — visit 8: 1, 3, 6, 8" },
- { points: "Go right from 8: reach 10 — visit it: 1, 3, 6, 8, 10 (sorted!)" },
+ { points: "Start at the root (8), but don't visit it yet: first go left" },
+ { points: "At 3, go left again first: reach 1 (a leaf) and visit it: 1" },
+ { points: "Back at 3, its left is done: visit 3: 1, 3" },
+ { points: "Now go right from 3: reach 6 (a leaf) and visit it: 1, 3, 6" },
+ { points: "Back at 8, its left subtree is fully done: visit 8: 1, 3, 6, 8" },
+ { points: "Go right from 8: reach 10 and visit it: 1, 3, 6, 8, 10 (sorted!)" },
];
const algorithm = [
diff --git a/app/visualizer/trees/traversal/in-order/quiz.jsx b/app/visualizer/trees/traversal/in-order/quiz.jsx
index 48fc101..9f16608 100755
--- a/app/visualizer/trees/traversal/in-order/quiz.jsx
+++ b/app/visualizer/trees/traversal/in-order/quiz.jsx
@@ -31,7 +31,7 @@ const InOrderQuiz = () => {
{
question: "Why can in-order traversal be used to check whether a binary tree is a valid BST?",
options: [
- "It can't — validity must be checked another way",
+ "It can't: validity must be checked another way",
"Because a valid BST's in-order sequence is guaranteed to be strictly increasing; if it isn't, the tree violates the BST property",
"Because in-order traversal is faster than other traversals",
"Because in-order traversal always visits the root first"
diff --git a/app/visualizer/trees/traversal/level-order/animation.jsx b/app/visualizer/trees/traversal/level-order/animation.jsx
index 24ea6c5..88960c0 100755
--- a/app/visualizer/trees/traversal/level-order/animation.jsx
+++ b/app/visualizer/trees/traversal/level-order/animation.jsx
@@ -135,7 +135,7 @@ const LevelOrderVisualizer = () => {
setVisitedOrder([]);
setCurrentValue(null);
setQueueState([root.value]);
- setMessage("Starting level-order traversal — enqueue the root, then process level by level...");
+ setMessage("Starting level-order traversal: enqueue the root, then process level by level...");
let i = 0;
const revealStep = () => {
@@ -144,7 +144,7 @@ const LevelOrderVisualizer = () => {
setVisitedOrder((prev) => [...prev, value]);
setQueueState(queueAfter);
setMessage(
- `Dequeued ${value}${queueAfter.length ? ` — enqueued its children, queue is now [${queueAfter.join(", ")}]` : " — queue is now empty"}`
+ `Dequeued ${value}${queueAfter.length ? `, enqueued its children, queue is now [${queueAfter.join(", ")}]` : ", queue is now empty"}`
);
i++;
@@ -368,7 +368,7 @@ const LevelOrderVisualizer = () => {
) : (
- No tree yet — insert a value or generate a random tree
+ No tree yet, insert a value or generate a random tree
)}
diff --git a/app/visualizer/trees/traversal/level-order/content.jsx b/app/visualizer/trees/traversal/level-order/content.jsx
index a93cac1..4f7fc35 100755
--- a/app/visualizer/trees/traversal/level-order/content.jsx
+++ b/app/visualizer/trees/traversal/level-order/content.jsx
@@ -68,24 +68,24 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `Level-order traversal visits a tree one depth level at a time — every node at depth 0 (the root), then every node at depth 1, then every node at depth 2, and so on, left to right within each level. It's also called Breadth-First Search (BFS) on a tree, and unlike pre-order, in-order, and post-order, it isn't defined recursively — it's naturally iterative, built around a queue instead of the call stack.`,
- `The algorithm starts by enqueueing the root. Then it repeatedly dequeues the front node, visits it, and enqueues that node's children (left, then right). Because a queue is FIFO, nodes come back out in exactly the order they were discovered — which guarantees an entire level finishes before the next one starts.`,
+ `Level-order traversal visits a tree one depth level at a time: every node at depth 0 (the root), then every node at depth 1, then every node at depth 2, and so on, left to right within each level. It's also called Breadth-First Search (BFS) on a tree, and unlike pre-order, in-order, and post-order, it isn't defined recursively. It's naturally iterative, built around a queue instead of the call stack.`,
+ `The algorithm starts by enqueueing the root. Then it repeatedly dequeues the front node, visits it, and enqueues that node's children (left, then right). Because a queue is FIFO, nodes come back out in exactly the order they were discovered, guaranteeing that an entire level finishes before the next one starts.`,
`This queue-driven, level-by-level structure is why BFS is the traversal used whenever "closer" needs to mean something concrete: finding the shortest path in an unweighted tree/graph, computing a node's minimum depth, or serializing a tree in a way that's easy to reconstruct row by row (e.g. printing a tree layer by layer for display).`,
- `Level-order needs O(w) extra space for the queue, where w is the maximum width of the tree (the largest number of nodes at any single level) — this can be as large as O(n) for a wide, shallow tree, unlike the O(h) stack space used by the recursive DFS traversals.`,
+ `Level-order needs O(w) extra space for the queue, where w is the maximum width of the tree (the largest number of nodes at any single level). This can be as large as O(n) for a wide, shallow tree, unlike the O(h) stack space used by the recursive DFS traversals.`,
];
const walkthrough = [
{ points: "Enqueue the root, 8. Queue: [8]" },
{ points: "Dequeue 8, visit it, enqueue its children 3 and 10. Queue: [3, 10]" },
{ points: "Dequeue 3, visit it, enqueue its children 1 and 6. Queue: [10, 1, 6]" },
- { points: "Dequeue 10, visit it — it has no children. Queue: [1, 6]" },
- { points: "Dequeue 1, visit it — it has no children. Queue: [6]" },
- { points: "Dequeue 6, visit it — it has no children. Queue is now empty, traversal ends" },
- { points: "Final sequence: [8, 3, 10, 1, 6] — notice level 0, then all of level 1, then all of level 2" },
+ { points: "Dequeue 10, visit it; it has no children. Queue: [1, 6]" },
+ { points: "Dequeue 1, visit it; it has no children. Queue: [6]" },
+ { points: "Dequeue 6, visit it; it has no children. Queue is now empty, traversal ends" },
+ { points: "Final sequence: [8, 3, 10, 1, 6]: notice level 0, then all of level 1, then all of level 2" },
];
const algorithm = [
- { points: "If the root is null, return immediately — there's nothing to traverse" },
+ { points: "If the root is null, return immediately, since there's nothing to traverse" },
{ points: "Create a queue and enqueue the root" },
{
points: "While the queue isn't empty, repeat:",
diff --git a/app/visualizer/trees/traversal/level-order/quiz.jsx b/app/visualizer/trees/traversal/level-order/quiz.jsx
index 04e1afd..347fc7f 100755
--- a/app/visualizer/trees/traversal/level-order/quiz.jsx
+++ b/app/visualizer/trees/traversal/level-order/quiz.jsx
@@ -29,16 +29,16 @@ const LevelOrderQuiz = () => {
"Because it visits nodes in sorted order",
"Because it always finishes exploring one full level (one 'distance' from the root) before moving to the next, so the first time a target is reached is guaranteed to be via the shortest path",
"Because it uses less memory than DFS",
- "It isn't — DFS is used for that instead"
+ "It isn't; DFS is used for that instead"
],
correctAnswer: 1,
explanation: "BFS expands outward in rings of increasing distance from the root, so whichever node it reaches first is reached by the fewest possible edges."
},
{
question: "What is the worst-case space complexity of level-order traversal, in terms of the tree's maximum width w?",
- options: ["O(1)", "O(h) — the tree's height", "O(w), which can be as large as O(n)", "O(log n)"],
+ options: ["O(1)", "O(h), the tree's height", "O(w), which can be as large as O(n)", "O(log n)"],
correctAnswer: 2,
- explanation: "The queue can hold an entire level's worth of nodes at once, so it grows to the width of the widest level — up to O(n) for a wide, shallow tree."
+ explanation: "The queue can hold an entire level's worth of nodes at once, so it grows to the width of the widest level, up to O(n) for a wide, shallow tree."
}
];
diff --git a/app/visualizer/trees/traversal/morris/animation.jsx b/app/visualizer/trees/traversal/morris/animation.jsx
index 4e5d9bc..6598fda 100755
--- a/app/visualizer/trees/traversal/morris/animation.jsx
+++ b/app/visualizer/trees/traversal/morris/animation.jsx
@@ -46,7 +46,7 @@ const generateMorrisFrames = (originalRoot) => {
predecessorValue: null,
threads: [...activeThreads],
visitedSoFar: [...visited],
- message: `${curr.value} has no left child — visit it directly, then move right`,
+ message: `${curr.value} has no left child, so visit it directly, then move right`,
});
curr = curr.right;
} else {
@@ -61,7 +61,7 @@ const generateMorrisFrames = (originalRoot) => {
predecessorValue: pred.value,
threads: [...activeThreads],
visitedSoFar: [...visited],
- message: `Found in-order predecessor ${pred.value} of ${curr.value} — thread it back to ${curr.value}, then descend left`,
+ message: `Found in-order predecessor ${pred.value} of ${curr.value}: thread it back to ${curr.value}, then descend left`,
});
curr = curr.left;
} else {
@@ -76,7 +76,7 @@ const generateMorrisFrames = (originalRoot) => {
predecessorValue: pred.value,
threads: [...activeThreads],
visitedSoFar: [...visited],
- message: `Back at ${curr.value} via the thread from ${pred.value} — remove the thread, visit ${curr.value}, then move right`,
+ message: `Back at ${curr.value} via the thread from ${pred.value}: remove the thread, visit ${curr.value}, then move right`,
});
curr = curr.right;
}
@@ -88,7 +88,7 @@ const generateMorrisFrames = (originalRoot) => {
predecessorValue: null,
threads: [],
visitedSoFar: [...visited],
- message: `Morris traversal complete: [${visited.join(", ")}] — tree structure fully restored, no threads remain`,
+ message: `Morris traversal complete: [${visited.join(", ")}], tree structure fully restored, no threads remain`,
});
return frames;
@@ -453,7 +453,7 @@ const MorrisVisualizer = () => {
) : (
- No tree yet — insert a value or generate a random tree
+ No tree yet, insert a value or generate a random tree
)}
diff --git a/app/visualizer/trees/traversal/morris/content.jsx b/app/visualizer/trees/traversal/morris/content.jsx
index 1689a04..024c955 100755
--- a/app/visualizer/trees/traversal/morris/content.jsx
+++ b/app/visualizer/trees/traversal/morris/content.jsx
@@ -84,10 +84,10 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `Every traversal seen so far — pre-order, in-order, post-order, level-order — needs extra memory to remember "where to come back to": recursive traversals use the call stack, and level-order uses an explicit queue. Morris traversal is a clever technique that produces the exact same in-order sequence using O(1) extra space, no stack and no queue at all.`,
- `The trick is to temporarily repurpose the tree's own empty pointers to remember the way back. For any node with a left child, Morris traversal finds that node's in-order predecessor — the rightmost node in its left subtree — and threads a temporary link from the predecessor's (normally null) right pointer back to the current node. This thread is exactly the "return address" a stack frame or queue entry would otherwise store.`,
- `Once a node with a thread pointing at it is reached again, the algorithm recognizes the thread (its predecessor's right pointer already points at the current node), visits the node, and then removes the thread — restoring the original tree structure exactly as it was before traversal started. By the time the traversal finishes, no threads remain and the tree is completely unmodified.`,
- `Morris traversal needs O(1) extra space — no recursion, no explicit stack or queue — which is exactly why it's used in memory-constrained environments or when a tree needs to stay usable by other code while being traversed without paying any extra memory cost.`,
+ `Every traversal seen so far (pre-order, in-order, post-order, level-order) needs extra memory to remember "where to come back to": recursive traversals use the call stack, and level-order uses an explicit queue. Morris traversal is a clever technique that produces the exact same in-order sequence using O(1) extra space, no stack and no queue at all.`,
+ `The trick is to temporarily repurpose the tree's own empty pointers to remember the way back. For any node with a left child, Morris traversal finds that node's in-order predecessor, the rightmost node in its left subtree, and threads a temporary link from the predecessor's (normally null) right pointer back to the current node. This thread is exactly the "return address" a stack frame or queue entry would otherwise store.`,
+ `Once a node with a thread pointing at it is reached again, the algorithm recognizes the thread (its predecessor's right pointer already points at the current node), visits the node, and then removes the thread, restoring the original tree structure exactly as it was before traversal started. By the time the traversal finishes, no threads remain and the tree is completely unmodified.`,
+ `Morris traversal needs O(1) extra space, with no recursion and no explicit stack or queue, which is exactly why it's used in memory-constrained environments or when a tree needs to stay usable by other code while being traversed without paying any extra memory cost.`,
];
const algorithm = [
@@ -96,7 +96,7 @@ const Content = () => {
points: "While curr is not null, repeat:",
subpoints: [
"If curr has no left child: visit curr, then move curr to curr.right",
- "Otherwise, find curr's in-order predecessor — the rightmost node in curr's left subtree",
+ "Otherwise, find curr's in-order predecessor, the rightmost node in curr's left subtree",
"If the predecessor's right pointer is null: thread it to curr (predecessor.right = curr), then move curr to curr.left",
"If the predecessor's right pointer already points to curr: remove the thread (predecessor.right = null), visit curr, then move curr to curr.right",
],
@@ -105,7 +105,7 @@ const Content = () => {
const complexity = [
{ points: "Time Complexity: Each edge is traversed at most twice (once to create the thread, once to remove it) → O(n)." },
- { points: "Space Complexity: No recursion stack, no queue — only a couple of pointer variables → O(1)." },
+ { points: "Space Complexity: No recursion stack, no queue, only a couple of pointer variables → O(1)." },
];
return (
diff --git a/app/visualizer/trees/traversal/morris/quiz.jsx b/app/visualizer/trees/traversal/morris/quiz.jsx
index 71cd15a..f19d7e4 100755
--- a/app/visualizer/trees/traversal/morris/quiz.jsx
+++ b/app/visualizer/trees/traversal/morris/quiz.jsx
@@ -36,7 +36,7 @@ const MorrisQuiz = () => {
"The node visited immediately before it in pre-order"
],
correctAnswer: 1,
- explanation: "Following left once and then right as far as possible lands on the rightmost node of the left subtree — exactly the node that would be visited right before the current one in in-order."
+ explanation: "Following left once and then right as far as possible lands on the rightmost node of the left subtree, exactly the node that would be visited right before the current one in in-order."
},
{
question: "What happens to the tree's structure after a full Morris traversal completes?",
@@ -47,13 +47,13 @@ const MorrisQuiz = () => {
"Half of the nodes are deleted"
],
correctAnswer: 1,
- explanation: "Every thread that gets created is later detected and removed in a subsequent step, so once traversal finishes the tree looks exactly as it did before — no side effects remain."
+ explanation: "Every thread that gets created is later detected and removed in a subsequent step, so once traversal finishes the tree looks exactly as it did before, with no side effects remaining."
},
{
question: "What is the time complexity of Morris traversal, despite doing extra work to create and remove threads?",
options: ["O(log n)", "O(n)", "O(n log n)", "O(n²)"],
correctAnswer: 1,
- explanation: "Each edge is traversed at most twice (once while threading, once while removing the thread), which is still a constant amount of extra work per node — the total stays O(n)."
+ explanation: "Each edge is traversed at most twice (once while threading, once while removing the thread), which is still a constant amount of extra work per node, so the total stays O(n)."
}
];
diff --git a/app/visualizer/trees/traversal/post-order/animation.jsx b/app/visualizer/trees/traversal/post-order/animation.jsx
index 672fd31..acf97f3 100755
--- a/app/visualizer/trees/traversal/post-order/animation.jsx
+++ b/app/visualizer/trees/traversal/post-order/animation.jsx
@@ -139,7 +139,7 @@ const PostOrderVisualizer = () => {
setTimeout(revealStep, STEP_DELAY);
} else {
setTimeout(() => {
- setMessage(`Post-order traversal complete: [${sequence.join(", ")}] — the root is always visited last`);
+ setMessage(`Post-order traversal complete: [${sequence.join(", ")}], the root is always visited last`);
setCurrentValue(null);
setBusy(false);
}, STEP_DELAY);
@@ -341,7 +341,7 @@ const PostOrderVisualizer = () => {
) : (
- No tree yet — insert a value or generate a random tree
+ No tree yet, insert a value or generate a random tree
)}
diff --git a/app/visualizer/trees/traversal/post-order/content.jsx b/app/visualizer/trees/traversal/post-order/content.jsx
index b9d9435..9325f57 100755
--- a/app/visualizer/trees/traversal/post-order/content.jsx
+++ b/app/visualizer/trees/traversal/post-order/content.jsx
@@ -64,19 +64,19 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `Post-order traversal visits both of a node's subtrees before the node itself — the order is Left, Right, Root. It's called "post" because the root is processed after everything below it has already been dealt with, the exact opposite of pre-order.`,
+ `Post-order traversal visits both of a node's subtrees before the node itself, in the order Left, Right, Root. It's called "post" because the root is processed after everything below it has already been dealt with, the exact opposite of pre-order.`,
`That "children before parent" ordering makes post-order the natural choice whenever you need to fully finish with a node's descendants before touching the node itself. The two classic examples are deleting or freeing a tree's memory (you must free the children before the parent, otherwise you'd lose your only reference to them) and evaluating an expression tree (you can't compute an operator's result until both of its operand subtrees have already been evaluated).`,
`Post-order also mirrors how you'd write a postfix (Reverse Polish) expression from an expression tree: the operands come first and the operator comes last, e.g. \`3 4 +\` instead of \`3 + 4\`.`,
- `Traversal needs O(h) extra space for the recursion call stack, where h is the tree's height — O(log n) for a balanced tree, but O(n) in the worst case of a completely skewed tree.`,
+ `Traversal needs O(h) extra space for the recursion call stack, where h is the tree's height: O(log n) for a balanced tree, but O(n) in the worst case of a completely skewed tree.`,
];
const walkthrough = [
- { points: "Start at the root (8), but don't visit it yet — first fully process the left subtree" },
- { points: "At 3, also hold off — go left first: reach 1 (a leaf) — visit it: 1" },
- { points: "Back at 3, go right: reach 6 (a leaf) — visit it: 1, 6" },
- { points: "Both of 3's children are done — now visit 3: 1, 6, 3" },
- { points: "Back at 8, its left subtree is fully done — go right: reach 10 — visit it: 1, 6, 3, 10" },
- { points: "Both of 8's subtrees are done — finally visit 8 (the root, last): 1, 6, 3, 10, 8" },
+ { points: "Start at the root (8), but don't visit it yet; first fully process the left subtree" },
+ { points: "At 3, also hold off, go left first: reach 1 (a leaf), then visit it: 1" },
+ { points: "Back at 3, go right: reach 6 (a leaf), then visit it: 1, 6" },
+ { points: "Both of 3's children are done, so now visit 3: 1, 6, 3" },
+ { points: "Back at 8, its left subtree is fully done, so go right: reach 10, then visit it: 1, 6, 3, 10" },
+ { points: "Both of 8's subtrees are done, so finally visit 8 (the root, last): 1, 6, 3, 10, 8" },
];
const algorithm = [
diff --git a/app/visualizer/trees/traversal/post-order/quiz.jsx b/app/visualizer/trees/traversal/post-order/quiz.jsx
index 7f11305..45c0725 100755
--- a/app/visualizer/trees/traversal/post-order/quiz.jsx
+++ b/app/visualizer/trees/traversal/post-order/quiz.jsx
@@ -26,11 +26,11 @@ const PostOrderQuiz = () => {
"Post-order doesn't actually visit every node"
],
correctAnswer: 1,
- explanation: "If you freed a parent before its children, you'd lose the pointers needed to reach and free those children — post-order avoids that by always finishing children first."
+ explanation: "If you freed a parent before its children, you'd lose the pointers needed to reach and free those children, so post-order avoids that by always finishing children first."
},
{
question: "Post-order traversal corresponds to which notation for writing expression trees?",
- options: ["Infix notation", "Prefix (Polish) notation", "Postfix (Reverse Polish) notation", "None — traversal order doesn't relate to notation"],
+ options: ["Infix notation", "Prefix (Polish) notation", "Postfix (Reverse Polish) notation", "None, traversal order doesn't relate to notation"],
correctAnswer: 2,
explanation: "In postfix notation the operands come before the operator, exactly matching post-order's children-then-root order."
},
diff --git a/app/visualizer/trees/traversal/pre-order/animation.jsx b/app/visualizer/trees/traversal/pre-order/animation.jsx
index 2e59c6f..095229b 100755
--- a/app/visualizer/trees/traversal/pre-order/animation.jsx
+++ b/app/visualizer/trees/traversal/pre-order/animation.jsx
@@ -341,7 +341,7 @@ const PreOrderVisualizer = () => {
) : (
- No tree yet — insert a value or generate a random tree
+ No tree yet, insert a value or generate a random tree
)}
diff --git a/app/visualizer/trees/traversal/pre-order/content.jsx b/app/visualizer/trees/traversal/pre-order/content.jsx
index dcda1bf..c202e1d 100755
--- a/app/visualizer/trees/traversal/pre-order/content.jsx
+++ b/app/visualizer/trees/traversal/pre-order/content.jsx
@@ -63,10 +63,10 @@ const Content = () => {
const { theme } = useTheme();
const paragraphs = [
- `Pre-order traversal visits a node before either of its subtrees — the order is Root, then Left, then Right. It's called "pre" because the root is processed pre-emptively, ahead of anything below it, which makes it the natural way to reconstruct a tree's structure from scratch: whatever value you see first is guaranteed to be some subtree's root.`,
- `Because the root is always recorded before its children, pre-order output preserves enough structural information to rebuild the exact same tree (given the traversal is unambiguous, e.g. paired with node count or null markers). This is exactly why pre-order is the traversal used for serializing a tree to a file and for copying/cloning a tree — you can reconstruct it top-down as you read the sequence.`,
+ `Pre-order traversal visits a node before either of its subtrees, in the order Root, then Left, then Right. It's called "pre" because the root is processed pre-emptively, ahead of anything below it, which makes it the natural way to reconstruct a tree's structure from scratch: whatever value you see first is guaranteed to be some subtree's root.`,
+ `Because the root is always recorded before its children, pre-order output preserves enough structural information to rebuild the exact same tree (given the traversal is unambiguous, e.g. paired with node count or null markers). This is exactly why pre-order is the traversal used for serializing a tree to a file and for copying/cloning a tree, since you can reconstruct it top-down as you read the sequence.`,
`Pre-order also mirrors how you'd write a prefix (Polish notation) expression from an expression tree: the operator (root) comes before its operands (children), e.g. \`+ 3 4\` instead of \`3 + 4\`.`,
- `Traversal needs O(h) extra space for the recursion call stack, where h is the tree's height — O(log n) for a balanced tree, but O(n) in the worst case of a completely skewed tree.`,
+ `Traversal needs O(h) extra space for the recursion call stack, where h is the tree's height: O(log n) for a balanced tree, but O(n) in the worst case of a completely skewed tree.`,
];
const walkthrough = [
@@ -74,7 +74,7 @@ const Content = () => {
{ points: "Recurse into the left subtree, visiting its root first: 3" },
{ points: "Recurse further left: 1 (a leaf, so no children to descend into)" },
{ points: "Back up to 3, now recurse right: 6" },
- { points: "All of 8's left subtree is done — recurse into 8's right subtree: 10" },
+ { points: "All of 8's left subtree is done, so recurse into 8's right subtree: 10" },
{ points: "Final sequence: [8, 3, 1, 6, 10]" },
];
diff --git a/app/visualizer/trees/traversal/pre-order/quiz.jsx b/app/visualizer/trees/traversal/pre-order/quiz.jsx
index f643c9e..6219dac 100755
--- a/app/visualizer/trees/traversal/pre-order/quiz.jsx
+++ b/app/visualizer/trees/traversal/pre-order/quiz.jsx
@@ -30,7 +30,7 @@ const PreOrderQuiz = () => {
},
{
question: "Pre-order traversal corresponds to which notation for writing expression trees?",
- options: ["Infix notation", "Postfix (Reverse Polish) notation", "Prefix (Polish) notation", "None — traversal order doesn't relate to notation"],
+ options: ["Infix notation", "Postfix (Reverse Polish) notation", "Prefix (Polish) notation", "None, traversal order doesn't relate to notation"],
correctAnswer: 2,
explanation: "In prefix notation the operator comes before its operands, exactly matching pre-order's Root-then-children order."
},
diff --git a/public/sitemap-0.xml b/public/sitemap-0.xml
index 33be932..5426bbb 100755
--- a/public/sitemap-0.xml
+++ b/public/sitemap-0.xml
@@ -1,80 +1,80 @@
-https://dsavisualizer.in/blogs2026-08-10T19:29:15.834Z
-https://dsavisualizer.in/blogs/Content/dsaDifferent2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/blogs/Content/dsaWebDev2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/blogs/Content/timeRequired2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/blogs/Content/whatIsDS2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/cookies2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/dashboard2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/login2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/privacy2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/terms2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/graph/algorithms/dijkstra2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/graph/algorithms/kruskal2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/graph/algorithms/prim2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/graph/algorithms/topological-sort2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/graph/representation/adjacency-list2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/graph/representation/adjacency-matrix2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/graph/traversal/bfs2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/graph/traversal/dfs2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/linkedList/operations/comparison2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/linkedList/operations/deletion2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/linkedList/operations/insertion2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/linkedList/operations/merge2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/linkedList/operations/reverse2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/linkedList/operations/traversal2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/linkedList/types/circular2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/linkedList/types/doubly2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/linkedList/types/singly2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/queue/implementation/array2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/queue/implementation/linkedList2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/queue/operations/enqueue-dequeue2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/queue/operations/isempty2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/queue/operations/isfull2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/queue/operations/peek-front2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/queue/types/circular2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/queue/types/deque2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/queue/types/priority2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/queue/types/singleEnded2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/searching/binarysearch2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/searching/linearsearch2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/sorting/bubblesort2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/sorting/insertionsort2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/sorting/mergesort2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/sorting/quicksort2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/sorting/selectionsort2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/stack/implementation/usingArray2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/stack/implementation/usingLinkedList2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/stack/isempty2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/stack/isfull2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/stack/peek2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/stack/polish/postfix2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/stack/polish/prefix2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/stack/push-pop2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/advanced/b-trees2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/advanced/fenwick-tree2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/advanced/prefix-tree2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/advanced/red-black2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/advanced/segment-trees2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/algorithms/lowest-common-ancestor2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/algorithms/serialize-deserialize2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/algorithms/tree-diameter2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/algorithms/tree-isomorphism2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/applications/decision-trees2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/applications/heap-sort2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/applications/huffman-coding2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/applications/syntax-trees2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/binaryTree/properties2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/binaryTree/types2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/bst/avl2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/bst/deletion2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/bst/insertion2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/bst/searching2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/traversal/in-order2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/traversal/level-order2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/traversal/morris2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/traversal/post-order2026-08-10T19:29:15.842Z
-https://dsavisualizer.in/visualizer/trees/traversal/pre-order2026-08-10T19:29:15.842Z
+https://dsavisualizer.in/blogs2026-08-12T19:19:14.029Z
+https://dsavisualizer.in/blogs/Content/dsaDifferent2026-08-12T19:19:14.034Z
+https://dsavisualizer.in/blogs/Content/dsaWebDev2026-08-12T19:19:14.034Z
+https://dsavisualizer.in/blogs/Content/timeRequired2026-08-12T19:19:14.034Z
+https://dsavisualizer.in/blogs/Content/whatIsDS2026-08-12T19:19:14.034Z
+https://dsavisualizer.in/cookies2026-08-12T19:19:14.034Z
+https://dsavisualizer.in/dashboard2026-08-12T19:19:14.034Z
+https://dsavisualizer.in/login2026-08-12T19:19:14.034Z
+https://dsavisualizer.in/privacy2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/terms2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/graph/algorithms/dijkstra2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/graph/algorithms/kruskal2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/graph/algorithms/prim2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/graph/algorithms/topological-sort2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/graph/representation/adjacency-list2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/graph/representation/adjacency-matrix2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/graph/traversal/bfs2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/graph/traversal/dfs2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/linkedList/operations/comparison2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/linkedList/operations/deletion2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/linkedList/operations/insertion2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/linkedList/operations/merge2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/linkedList/operations/reverse2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/linkedList/operations/traversal2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/linkedList/types/circular2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/linkedList/types/doubly2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/linkedList/types/singly2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/queue/implementation/array2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/queue/implementation/linkedList2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/queue/operations/enqueue-dequeue2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/queue/operations/isempty2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/queue/operations/isfull2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/queue/operations/peek-front2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/queue/types/circular2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/queue/types/deque2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/queue/types/priority2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/queue/types/singleEnded2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/searching/binarysearch2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/searching/linearsearch2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/sorting/bubblesort2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/sorting/insertionsort2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/sorting/mergesort2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/sorting/quicksort2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/sorting/selectionsort2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/stack/implementation/usingArray2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/stack/implementation/usingLinkedList2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/stack/isempty2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/stack/isfull2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/stack/peek2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/stack/polish/postfix2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/stack/polish/prefix2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/stack/push-pop2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/advanced/b-trees2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/advanced/fenwick-tree2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/advanced/prefix-tree2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/advanced/red-black2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/advanced/segment-trees2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/algorithms/lowest-common-ancestor2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/algorithms/serialize-deserialize2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/algorithms/tree-diameter2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/algorithms/tree-isomorphism2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/applications/decision-trees2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/applications/heap-sort2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/applications/huffman-coding2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/applications/syntax-trees2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/binaryTree/properties2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/binaryTree/types2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/bst/avl2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/bst/deletion2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/bst/insertion2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/bst/searching2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/traversal/in-order2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/traversal/level-order2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/traversal/morris2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/traversal/post-order2026-08-12T19:19:14.035Z
+https://dsavisualizer.in/visualizer/trees/traversal/pre-order2026-08-12T19:19:14.035Z
\ No newline at end of file