diff --git a/app/visualizer/trees/bst/deletion/animation.jsx b/app/visualizer/trees/bst/deletion/animation.jsx new file mode 100755 index 0000000..4bf3be9 --- /dev/null +++ b/app/visualizer/trees/bst/deletion/animation.jsx @@ -0,0 +1,432 @@ +"use client"; +import React, { useState } from "react"; +import { gsap } from "gsap"; +import { Plus, Trash2, Shuffle, RotateCcw } from "lucide-react"; + +class TreeNode { + constructor(value) { + this.value = value; + this.left = null; + this.right = null; + } +} + +const insertNode = (node, value) => { + if (!node) return new TreeNode(value); + if (value < node.value) node.left = insertNode(node.left, value); + else if (value > node.value) node.right = insertNode(node.right, value); + return node; +}; + +const searchPath = (node, value, path) => { + if (!node) return null; + path.push(node.value); + if (value === node.value) return node; + if (value < node.value) return searchPath(node.left, value, path); + return searchPath(node.right, value, path); +}; + +const deleteNode = (node, value) => { + if (!node) return null; + if (value < node.value) { + node.left = deleteNode(node.left, value); + return node; + } + if (value > node.value) { + node.right = deleteNode(node.right, value); + return node; + } + if (!node.left && !node.right) return null; + if (!node.left) return node.right; + if (!node.right) return node.left; + + let successor = node.right; + while (successor.left) successor = successor.left; + node.value = successor.value; + node.right = deleteNode(node.right, successor.value); + return node; +}; + +const NODE_RADIUS = 22; +const LEVEL_HEIGHT = 78; + +const layoutTree = (node, depth = 0, x = 320, y = 40, nodes = [], edges = []) => { + if (!node) return { nodes, edges }; + const isLeaf = !node.left && !node.right; + const xOffset = Math.max(24, 110 / (depth + 1)); + + nodes.push({ value: node.value, x, y, depth, isLeaf, isRoot: depth === 0 }); + + if (node.left) { + const leftX = x - xOffset; + const leftY = y + LEVEL_HEIGHT; + edges.push({ + x1: x, + y1: y + NODE_RADIUS - 2, + x2: leftX, + y2: leftY - NODE_RADIUS + 2, + }); + layoutTree(node.left, depth + 1, leftX, leftY, nodes, edges); + } + if (node.right) { + const rightX = x + xOffset; + const rightY = y + LEVEL_HEIGHT; + edges.push({ + x1: x, + y1: y + NODE_RADIUS - 2, + x2: rightX, + y2: rightY - NODE_RADIUS + 2, + }); + layoutTree(node.right, depth + 1, rightX, rightY, nodes, edges); + } + + return { nodes, edges }; +}; + +const BstDeletionVisualizer = () => { + const [root, setRoot] = useState(null); + const [inputValue, setInputValue] = useState(""); + const [message, setMessage] = useState("Tree is empty"); + const [highlightPath, setHighlightPath] = useState([]); + const [deletingValue, setDeletingValue] = useState(null); + const [successorValue, setSuccessorValue] = useState(null); + const [busy, setBusy] = useState(false); + + const animateDropIn = (el) => { + if (!el || el.dataset.animated) return; + el.dataset.animated = "true"; + gsap.fromTo( + el, + { scale: 0, opacity: 0 }, + { scale: 1, opacity: 1, duration: 0.45, ease: "back.out(1.7)" } + ); + }; + + const handleInsert = () => { + const value = parseInt(inputValue, 10); + if (Number.isNaN(value)) { + setMessage("Please enter a valid number"); + return; + } + setRoot((prev) => insertNode(prev ? structuredClone(prev) : null, value)); + setMessage(`Inserted ${value}`); + setInputValue(""); + }; + + const handleDelete = () => { + const value = parseInt(inputValue, 10); + if (Number.isNaN(value)) { + setMessage("Please enter a valid number"); + return; + } + if (!root || busy) return; + + const path = []; + const target = searchPath(root, value, path); + if (!target) { + setMessage(`${value} isn't in the tree`); + setInputValue(""); + return; + } + + const hasLeft = !!target.left; + const hasRight = !!target.right; + let successor = null; + if (hasLeft && hasRight) { + let cur = target.right; + while (cur.left) cur = cur.left; + successor = cur.value; + } + + setBusy(true); + setHighlightPath(path); + setDeletingValue(value); + setSuccessorValue(successor); + setMessage( + successor !== null + ? `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` + ); + + setTimeout(() => { + setRoot((prev) => deleteNode(structuredClone(prev), value)); + setHighlightPath([]); + setDeletingValue(null); + setSuccessorValue(null); + setInputValue(""); + setBusy(false); + }, 900); + }; + + const generateRandomTree = () => { + const size = Math.floor(Math.random() * 5) + 5; + const values = Array.from({ length: size }, () => Math.floor(Math.random() * 100) + 1); + let newRoot = null; + values.forEach((v) => { + newRoot = insertNode(newRoot, v); + }); + setRoot(newRoot); + setMessage(`Generated a tree with ${size} random inserts`); + setHighlightPath([]); + setDeletingValue(null); + setSuccessorValue(null); + }; + + const reset = () => { + setRoot(null); + setInputValue(""); + setMessage("Tree is empty"); + setHighlightPath([]); + setDeletingValue(null); + setSuccessorValue(null); + }; + + const { nodes, edges } = root ? layoutTree(root) : { nodes: [], edges: [] }; + + const getSvgDimensions = () => { + if (nodes.length === 0) return { width: 600, height: 220 }; + const xValues = nodes.map((n) => n.x); + const yValues = nodes.map((n) => n.y); + const padding = 40; + return { + width: Math.max(600, Math.max(...xValues) - Math.min(...xValues) + padding * 2), + height: Math.max(220, Math.max(...yValues) + padding * 2), + }; + }; + const dims = getSvgDimensions(); + + return ( +
+

+ Delete a value and watch how its replacement (if any) is chosen +

+ +
+ {/* Controls */} +
+
+ setInputValue(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && handleDelete()} + placeholder="Enter a number" + disabled={busy} + className="flex-1 min-w-0 px-3 py-2 text-sm rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-neutral-900 text-gray-800 dark:text-gray-100 placeholder:text-gray-400 dark:placeholder:text-gray-500 outline-none focus:ring-2 focus:ring-blue-500/40 focus:border-blue-500 transition disabled:opacity-50" + /> + + +
+
+ + +
+
+ + {/* Visualization */} +
+
+ {message} +
+ +
+ {nodes.length > 0 ? ( + + + + + + + + + + + + + + + + + + + + {edges.map((edge, i) => { + const midY = (edge.y1 + edge.y2) / 2; + return ( + + ); + })} + + {nodes.map((node, i) => { + const isOnPath = highlightPath.includes(node.value); + const isDeleting = node.value === deletingValue; + const isSuccessor = node.value === successorValue; + return ( + + {node.isRoot && ( + + )} + {isOnPath && !isDeleting && ( + + )} + {isSuccessor && ( + + )} + + + {node.value} + + + + + d{node.depth} + + + ); + })} + + ) : ( +
+ No tree yet — insert a value or generate a random tree +
+ )} +
+ +
+ + + Internal node + + + + Leaf node + + + + Root + + + + Search path + + + + Being deleted + + + + In-order successor + +
+
+
+
+ ); +}; + +export default BstDeletionVisualizer; diff --git a/app/visualizer/trees/bst/deletion/code.js b/app/visualizer/trees/bst/deletion/code.js new file mode 100755 index 0000000..7d4b02e --- /dev/null +++ b/app/visualizer/trees/bst/deletion/code.js @@ -0,0 +1,180 @@ +const codeExamples = { + javascript: `// Binary Search Tree node +class TreeNode { + constructor(value) { + this.value = value; + this.left = null; + this.right = null; + } +} + +// Delete a value from the BST +function deleteNode(node, value) { + if (node === null) return null; + + if (value < node.value) { + node.left = deleteNode(node.left, value); + return node; + } + if (value > node.value) { + node.right = deleteNode(node.right, value); + return node; + } + + // Found the node to delete + if (node.left === null && node.right === null) return null; // Case 1: leaf + if (node.left === null) return node.right; // Case 2: only right child + if (node.right === null) return node.left; // Case 2: only left child + + // Case 3: two children — find in-order successor (min of right subtree) + let successor = node.right; + while (successor.left !== null) successor = successor.left; + + node.value = successor.value; + node.right = deleteNode(node.right, successor.value); + return node; +} + +// Usage example +let root = null; +[8, 3, 12, 10, 14].forEach((value) => { + root = insertNode(root, value); // see BST Insertion for insertNode +}); +root = deleteNode(root, 8);`, + + python: `# Binary Search Tree node +class TreeNode: + def __init__(self, value): + self.value = value + self.left = None + self.right = None + +# Delete a value from the BST +def delete_node(node, value): + if node is None: + return None + + if value < node.value: + node.left = delete_node(node.left, value) + return node + if value > node.value: + node.right = delete_node(node.right, value) + return node + + # Found the node to delete + if node.left is None and node.right is None: + return None # Case 1: leaf + if node.left is None: + return node.right # Case 2: only right child + if node.right is None: + return node.left # Case 2: only left child + + # Case 3: two children — find in-order successor (min of right subtree) + successor = node.right + while successor.left is not None: + successor = successor.left + + node.value = successor.value + node.right = delete_node(node.right, successor.value) + return node + +# Usage example +root = None +for value in [8, 3, 12, 10, 14]: + root = insert_node(root, value) # see BST Insertion for insert_node +root = delete_node(root, 8)`, + + c: `#include +#include + +typedef struct TreeNode { + int value; + struct TreeNode *left, *right; +} TreeNode; + +TreeNode* newNode(int value) { + TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode)); + node->value = value; + node->left = node->right = NULL; + return node; +} + +// Delete a value from the BST +TreeNode* deleteNode(TreeNode* node, int value) { + if (node == NULL) return NULL; + + if (value < node->value) { + node->left = deleteNode(node->left, value); + return node; + } + if (value > node->value) { + node->right = deleteNode(node->right, value); + return node; + } + + // Found the node to delete + if (node->left == NULL && node->right == NULL) { + free(node); + return NULL; // Case 1: leaf + } + if (node->left == NULL) { + TreeNode* right = node->right; + free(node); + return right; // Case 2: only right child + } + if (node->right == NULL) { + TreeNode* left = node->left; + free(node); + return left; // Case 2: only left child + } + + // Case 3: two children — find in-order successor (min of right subtree) + TreeNode* successor = node->right; + while (successor->left != NULL) successor = successor->left; + + node->value = successor->value; + node->right = deleteNode(node->right, successor->value); + return node; +}`, + + java: `class TreeNode { + int value; + TreeNode left, right; + + TreeNode(int value) { + this.value = value; + } +} + +public class BstDeletion { + + // Delete a value from the BST + static TreeNode deleteNode(TreeNode node, int value) { + if (node == null) return null; + + if (value < node.value) { + node.left = deleteNode(node.left, value); + return node; + } + if (value > node.value) { + node.right = deleteNode(node.right, value); + return node; + } + + // Found the node to delete + if (node.left == null && node.right == null) return null; // Case 1: leaf + if (node.left == null) return node.right; // Case 2: only right child + if (node.right == null) return node.left; // Case 2: only left child + + // Case 3: two children — find in-order successor (min of right subtree) + TreeNode successor = node.right; + while (successor.left != null) successor = successor.left; + + node.value = successor.value; + node.right = deleteNode(node.right, successor.value); + return node; + } +}`, +}; + +export default codeExamples; diff --git a/app/visualizer/trees/bst/deletion/content.jsx b/app/visualizer/trees/bst/deletion/content.jsx new file mode 100755 index 0000000..8656220 --- /dev/null +++ b/app/visualizer/trees/bst/deletion/content.jsx @@ -0,0 +1,286 @@ +"use client"; +import ComplexityGraph from "@/app/components/ui/graph"; +import { useTheme } from "@/app/contexts/ThemeContext"; +import DailyDSAEmbed from "@/app/components/ui/DailyDSAEmbed"; +import NewsletterEmbed from "@/app/components/ui/NewsletterEmbed"; +import InContentAd from "@/app/components/ads/InContentAd"; +import { motion } from "framer-motion"; + +const MiniTree = ({ nodes, edges, keyPrefix }) => { + const byId = Object.fromEntries(nodes.map((n) => [n.id, n])); + return ( + + {edges.map(([from, to], i) => ( + + ))} + {nodes.map((n, i) => ( + + {n.ring && ( + + )} + + + {n.id} + + + ))} + + ); +}; + +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.`, + ]; + + 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.", + }, + { + title: "Case 2: Deleting a node with one child", + body: "Splice the node out by connecting its parent directly to its single child, skipping over the deleted node entirely.", + }, + { + 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.", + }, + ]; + + const beforeNodes = [ + { id: "8", x: 100, y: 30, fill: "#dc2626", stroke: "#b91c1c", ring: "#ef4444" }, + { id: "3", x: 60, y: 80 }, + { id: "12", x: 140, y: 80 }, + { id: "10", x: 120, y: 130, fill: "#8b5cf6", stroke: "#7c3aed", ring: "#8b5cf6" }, + { id: "14", x: 160, y: 130 }, + ]; + const beforeEdges = [ + ["8", "3"], + ["8", "12"], + ["12", "10"], + ["12", "14"], + ]; + + const afterNodes = [ + { id: "10", x: 100, y: 30, fill: "#10b981", stroke: "#059669" }, + { id: "3", x: 60, y: 80 }, + { id: "12", x: 140, y: 80 }, + { id: "14", x: 160, y: 130 }, + ]; + const afterEdges = [ + ["10", "3"], + ["10", "12"], + ["12", "14"], + ]; + + const algorithm = [ + { points: "Search for the value the same way you would for lookup" }, + { + points: "Once found, check how many children the node has:", + subpoints: [ + "Zero children → remove it directly", + "One child → replace the node with that child", + "Two children → find the in-order successor, copy its value up, then delete the successor", + ], + }, + { points: "Return the (possibly modified) subtree to the parent call" }, + ]; + + const complexity = [ + { points: "Best/Average Case: Roughly balanced tree → O(log n)." }, + { points: "Worst Case: Degenerate/skewed tree → O(n)." }, + ]; + + return ( +
+
+ + +
+
+ {/* What is BST Deletion */} +
+

+ + What is BST Deletion? +

+
+

+ {paragraphs[0]} +

+
+
+ + {/* How Does It Work */} +
+

+ + How Does It Work? +

+
+

+ {paragraphs[1]} +

+
+ +
+ {cases.map((c) => ( +
+
+ {c.title} +
+
{c.body}
+
+ ))} +
+ +
+
+
+ Deleting 8 (two children) +
+ +
+
+
+ 10 (successor) takes its place +
+ +
+
+ +
+ + + Node being deleted + + + + In-order successor + + + + Successor's new position + +
+
+ + {/* Algorithm Steps */} +
+

+ + Algorithm Steps +

+
+
    + {algorithm.map((item, index) => ( +
  1. + {item.points} + {item.subpoints && ( +
      + {item.subpoints.map((subitem, subindex) => ( +
    • + {subitem} +
    • + ))} +
    + )} +
  2. + ))} +
+
+
+ + {/* Time Complexity */} +
+

+ + Time Complexity +

+
+
    + {complexity.map((item, index) => ( +
  • + + {item.points.split(":")[0]}: + + {item.points.split(":")[1]} +
  • + ))} +
+
+ +
+ Math.log2(n)} + averageCase={(n) => Math.log2(n)} + worstCase={(n) => n} + maxN={25} + /> +
+ + +
+ + {/* Space Complexity */} +
+

+ + Space Complexity +

+
+

+ {paragraphs[2]} +

+
+
+ + {/* Additional Info */} +
+
+
+

+ {paragraphs[3]} +

+
+
+
+
+ + +
+ ); +}; + +export default Content; diff --git a/app/visualizer/trees/bst/deletion/page.jsx b/app/visualizer/trees/bst/deletion/page.jsx new file mode 100755 index 0000000..89af3f3 --- /dev/null +++ b/app/visualizer/trees/bst/deletion/page.jsx @@ -0,0 +1,107 @@ +import Animation from "@/app/visualizer/trees/bst/deletion/animation"; +import Navbar from "@/app/components/navbarinner"; +import ModuleHeader from "@/app/components/modules/Header"; +import Footer from "@/app/components/footer"; +import BackToTop from "@/app/components/ui/backtotop"; +import ExploreOther from "@/app/components/ui/exploreOther"; +import CodeBlock from "@/app/components/modules/CodeBlock"; +import codeExamples from "./code"; +import Quiz from "@/app/visualizer/trees/bst/deletion/quiz"; +import Content from "@/app/visualizer/trees/bst/deletion/content"; +import ModuleCard from "@/app/components/ui/ModuleCard"; +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.", + keywords: [ + "Binary Search Tree Deletion", + "BST Deletion", + "BST Deletion Visualization", + "BST Deletion Algorithm", + "In-order Successor", + "Binary Search Tree Animation", + "Delete from BST", + "BST Deletion in JavaScript", + "BST Deletion in C", + "BST Deletion in Python", + "BST Deletion in Java", + "DSA Binary Search Tree", + "Learn Binary Search Trees", + "BST Quiz", + ], + robots: "index, follow", + openGraph: { + images: [ + { + url: "/og.png", + width: 1200, + height: 630, + alt: "Binary Search Tree Deletion Visualization", + }, + ], + }, +}; + +export default function Page() { + const paths = [ + { name: "Home", href: "/" }, + { name: "Visualizer", href: "/visualizer" }, + { name: "Trees : BST Deletion", href: "" }, + ]; + + return ( + <> +
+ +
+ +
+
+ + +
+ +
+ +
+ +
+

+ Test Your Knowledge before moving forward! +

+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ + +