From d4b86b535d595583a32416dbe873224af39315be Mon Sep 17 00:00:00 2001 From: Sohan Rout Date: Mon, 3 Aug 2026 15:32:41 +0530 Subject: [PATCH] Feat : added new modules to binary trees --- EnvExample.txt | 10 - app/visualizer/page.jsx | 2 +- .../trees/advanced/b-trees/animation.jsx | 396 ++++++++++++++ app/visualizer/trees/advanced/b-trees/code.js | 317 +++++++++++ .../trees/advanced/b-trees/content.jsx | 262 +++++++++ .../trees/advanced/b-trees/page.jsx | 111 ++++ .../trees/advanced/b-trees/quiz.jsx | 379 +++++++++++++ .../trees/advanced/prefix-tree/animation.jsx | 409 ++++++++++++++ .../trees/advanced/prefix-tree/code.js | 203 +++++++ .../trees/advanced/prefix-tree/content.jsx | 214 ++++++++ .../trees/advanced/prefix-tree/page.jsx | 110 ++++ .../trees/advanced/prefix-tree/quiz.jsx | 374 +++++++++++++ .../trees/advanced/red-black/animation.jsx | 424 +++++++++++++++ .../trees/advanced/red-black/code.js | 460 ++++++++++++++++ .../trees/advanced/red-black/content.jsx | 270 ++++++++++ .../trees/advanced/red-black/page.jsx | 111 ++++ .../trees/advanced/red-black/quiz.jsx | 374 +++++++++++++ app/visualizer/trees/bst/avl/page.jsx | 1 + .../trees/traversal/in-order/page.jsx | 1 + .../trees/traversal/level-order/page.jsx | 2 +- .../trees/traversal/morris/animation.jsx | 502 ++++++++++++++++++ app/visualizer/trees/traversal/morris/code.js | 214 ++++++++ .../trees/traversal/morris/content.jsx | 235 ++++++++ .../trees/traversal/morris/page.jsx | 109 ++++ .../trees/traversal/morris/quiz.jsx | 379 +++++++++++++ .../trees/traversal/post-order/page.jsx | 1 + .../trees/traversal/pre-order/page.jsx | 1 + lib/modulesMap.js | 4 + public/sitemap-0.xml | 114 ++-- 29 files changed, 5922 insertions(+), 67 deletions(-) delete mode 100755 EnvExample.txt create mode 100755 app/visualizer/trees/advanced/b-trees/animation.jsx create mode 100755 app/visualizer/trees/advanced/b-trees/code.js create mode 100755 app/visualizer/trees/advanced/b-trees/content.jsx create mode 100755 app/visualizer/trees/advanced/b-trees/page.jsx create mode 100755 app/visualizer/trees/advanced/b-trees/quiz.jsx create mode 100755 app/visualizer/trees/advanced/prefix-tree/animation.jsx create mode 100755 app/visualizer/trees/advanced/prefix-tree/code.js create mode 100755 app/visualizer/trees/advanced/prefix-tree/content.jsx create mode 100755 app/visualizer/trees/advanced/prefix-tree/page.jsx create mode 100755 app/visualizer/trees/advanced/prefix-tree/quiz.jsx create mode 100755 app/visualizer/trees/advanced/red-black/animation.jsx create mode 100755 app/visualizer/trees/advanced/red-black/code.js create mode 100755 app/visualizer/trees/advanced/red-black/content.jsx create mode 100755 app/visualizer/trees/advanced/red-black/page.jsx create mode 100755 app/visualizer/trees/advanced/red-black/quiz.jsx create mode 100755 app/visualizer/trees/traversal/morris/animation.jsx create mode 100755 app/visualizer/trees/traversal/morris/code.js create mode 100755 app/visualizer/trees/traversal/morris/content.jsx create mode 100755 app/visualizer/trees/traversal/morris/page.jsx create mode 100755 app/visualizer/trees/traversal/morris/quiz.jsx diff --git a/EnvExample.txt b/EnvExample.txt deleted file mode 100755 index 4cafc9f..0000000 --- a/EnvExample.txt +++ /dev/null @@ -1,10 +0,0 @@ -EMAIL_USER=Your App Email -EMAIL_PASSWORD=Your Google App Password -NEXT_PUBLIC_GA_ID=Your Google Analytics ID - -NEXT_PUBLIC_SUPABASE_URL=Your supabase Url -NEXT_PUBLIC_SUPABASE_ANON_KEY=Your Anon Key -NEXT_PUBLIC_TURNSTILE_SITE_KEY=Your Cloudfare Captcha Key - -TURNSTILE_SECRET_KEY=Your Cloudfare backend route api key -SUPABASE_SERVICE_KEY=Your supabase service key \ No newline at end of file diff --git a/app/visualizer/page.jsx b/app/visualizer/page.jsx index 133467a..a0d28ab 100755 --- a/app/visualizer/page.jsx +++ b/app/visualizer/page.jsx @@ -388,7 +388,7 @@ const sections = [ { name: "B-Trees", path: "/visualizer/trees/advanced/b-trees" }, { name: "Trie (Prefix Tree)", - path: "/visualizer/trees/advanced/trie", + path: "/visualizer/trees/advanced/prefix-tree", }, { name: "Segment Trees", path: "/visualizer/trees/advanced/segment" }, { name: "Fenwick Trees", path: "/visualizer/trees/advanced/fenwick" }, diff --git a/app/visualizer/trees/advanced/b-trees/animation.jsx b/app/visualizer/trees/advanced/b-trees/animation.jsx new file mode 100755 index 0000000..f862bf9 --- /dev/null +++ b/app/visualizer/trees/advanced/b-trees/animation.jsx @@ -0,0 +1,396 @@ +"use client"; +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 + +class BTreeNode { + constructor(leaf) { + this.keys = []; + this.children = []; + this.leaf = leaf; + } +} + +const splitChild = (parent, i, events) => { + const fullChild = parent.children[i]; + const newChild = new BTreeNode(fullChild.leaf); + + const midKey = fullChild.keys[T - 1]; + newChild.keys = fullChild.keys.slice(T); + const leftKeys = fullChild.keys.slice(0, T - 1); + + if (!fullChild.leaf) { + newChild.children = fullChild.children.slice(T); + fullChild.children = fullChild.children.slice(0, T); + } + fullChild.keys = leftKeys; + + parent.children.splice(i + 1, 0, newChild); + parent.keys.splice(i, 0, midKey); + + events.push({ + text: `node [${[...leftKeys, midKey, ...newChild.keys].join(", ")}] was full — split it, moving median ${midKey} up`, + nodes: [fullChild, newChild, parent], + }); +}; + +const insertNonFull = (node, key, events) => { + let i = node.keys.length - 1; + if (node.leaf) { + while (i >= 0 && key < node.keys[i]) i--; + node.keys.splice(i + 1, 0, key); + } else { + while (i >= 0 && key < node.keys[i]) i--; + i++; + if (node.children[i].keys.length === 2 * T - 1) { + splitChild(node, i, events); + if (key > node.keys[i]) i++; + } + insertNonFull(node.children[i], key, events); + } +}; + +const insertBTree = (root, key, events) => { + if (!root) { + const node = new BTreeNode(true); + node.keys = [key]; + return node; + } + if (root.keys.length === 2 * T - 1) { + const newRoot = new BTreeNode(false); + newRoot.children.push(root); + splitChild(newRoot, 0, events); + insertNonFull(newRoot, key, events); + return newRoot; + } + insertNonFull(root, key, events); + return root; +}; + +const KEY_WIDTH = 36; +const NODE_HEIGHT = 34; +const LEVEL_HEIGHT = 90; +const MIN_GAP = 18; + +const computeSubtreeWidth = (node) => { + const ownWidth = node.keys.length * KEY_WIDTH; + if (node.leaf || node.children.length === 0) { + node._width = ownWidth; + return node._width; + } + let childrenWidth = 0; + node.children.forEach((c) => { + childrenWidth += computeSubtreeWidth(c) + MIN_GAP; + }); + childrenWidth -= MIN_GAP; + node._width = Math.max(ownWidth, childrenWidth); + return node._width; +}; + +const layoutBTree = (node, depth, xLeft, y, positioned = [], edges = []) => { + if (!node) return { positioned, edges }; + const width = node._width; + + if (node.leaf || node.children.length === 0) { + const x = xLeft + width / 2; + positioned.push({ node, x, y, width: node.keys.length * KEY_WIDTH, depth, isRoot: depth === 0 }); + return { positioned, edges }; + } + + let childrenTotalWidth = 0; + node.children.forEach((c) => { + childrenTotalWidth += c._width + MIN_GAP; + }); + childrenTotalWidth -= MIN_GAP; + + let cursor = xLeft + (width - childrenTotalWidth) / 2; + const childCenters = []; + node.children.forEach((c) => { + layoutBTree(c, depth + 1, cursor, y + LEVEL_HEIGHT, positioned, edges); + childCenters.push(cursor + c._width / 2); + cursor += c._width + MIN_GAP; + }); + + const x = (childCenters[0] + childCenters[childCenters.length - 1]) / 2; + positioned.push({ node, x, y, width: node.keys.length * KEY_WIDTH, depth, isRoot: depth === 0 }); + + childCenters.forEach((cx) => { + edges.push({ x1: x, y1: y + NODE_HEIGHT / 2, x2: cx, y2: y + LEVEL_HEIGHT - NODE_HEIGHT / 2 }); + }); + + return { positioned, edges }; +}; + +const BTreeVisualizer = () => { + const [root, setRoot] = useState(null); + const [inputValue, setInputValue] = useState(""); + const [message, setMessage] = useState("Tree is empty"); + const [highlightNodes, setHighlightNodes] = useState([]); + 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.4, ease: "back.out(1.7)" } + ); + }; + + const handleInsert = () => { + const value = parseInt(inputValue, 10); + if (Number.isNaN(value)) { + setMessage("Please enter a valid number"); + return; + } + if (busy) return; + setBusy(true); + + const events = []; + const newRoot = insertBTree(root ? structuredClone(root) : null, value, events); + + setTimeout(() => { + setRoot(newRoot); + if (events.length === 0) { + setMessage(`Inserted ${value} into a leaf node that had room — no split needed`); + setHighlightNodes([]); + } else { + setMessage(`Inserted ${value} — ${events.map((e) => e.text).join("; then ")}`); + setHighlightNodes(events.flatMap((e) => e.nodes)); + setTimeout(() => setHighlightNodes([]), 1400); + } + setInputValue(""); + setBusy(false); + }, 350); + }; + + const generateRandomTree = () => { + if (busy) return; + const size = Math.floor(Math.random() * 8) + 8; + const values = Array.from({ length: size }, () => Math.floor(Math.random() * 100) + 1); + let newRoot = null; + values.forEach((v) => { + newRoot = insertBTree(newRoot, v, []); + }); + setRoot(newRoot); + setMessage(`Generated a B-tree with ${size} random inserts`); + setHighlightNodes([]); + }; + + const reset = () => { + if (busy) return; + setRoot(null); + setInputValue(""); + setMessage("Tree is empty"); + setHighlightNodes([]); + }; + + let positioned = []; + let edges = []; + if (root) { + computeSubtreeWidth(root); + const layout = layoutBTree(root, 0, 0, 40); + positioned = layout.positioned; + edges = layout.edges; + } + + const getSvgDimensions = () => { + if (positioned.length === 0) return { width: 600, height: 220 }; + const xValues = positioned.map((p) => p.x - p.width / 2).concat(positioned.map((p) => p.x + p.width / 2)); + const yValues = positioned.map((p) => p.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(); + const minX = positioned.length ? Math.min(...positioned.map((p) => p.x - p.width / 2)) - 40 : 0; + + return ( +
+

+ Insert values and watch full nodes split to keep the B-tree balanced (min degree t = 2) +

+ +
+ {/* Controls */} +
+
+ setInputValue(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && handleInsert()} + 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} +
+ +
+ {positioned.length > 0 ? ( + + + + + + + + + + + + {edges.map((edge, i) => ( + + ))} + + {positioned.map((item, i) => { + const isTouched = highlightNodes.includes(item.node); + const left = item.x - item.width / 2; + return ( + + {item.isRoot && ( + + )} + {isTouched && ( + + )} + + {item.node.keys.slice(1).map((_, k) => ( + + ))} + {item.node.keys.map((key, k) => ( + + {key} + + ))} + + ); + })} + + ) : ( +
+ No tree yet — insert a value or generate a random tree +
+ )} +
+ +
+ + + Node (holds up to 3 sorted keys) + + + + Root + + + + Split by this insert + +
+
+
+
+ ); +}; + +export default BTreeVisualizer; diff --git a/app/visualizer/trees/advanced/b-trees/code.js b/app/visualizer/trees/advanced/b-trees/code.js new file mode 100755 index 0000000..8f6af23 --- /dev/null +++ b/app/visualizer/trees/advanced/b-trees/code.js @@ -0,0 +1,317 @@ +const codeExamples = { + javascript: `// B-Tree node — t is the tree's minimum degree +// (max keys per node = 2t-1, max children = 2t) +class BTreeNode { + constructor(leaf) { + this.keys = []; + this.children = []; + this.leaf = leaf; + } +} + +const T = 3; // minimum degree, tune to taste (or to your disk block size) + +// Splits the full child at index i of parent, promoting its median key up +function splitChild(parent, i) { + const fullChild = parent.children[i]; + const newChild = new BTreeNode(fullChild.leaf); + + const midKey = fullChild.keys[T - 1]; + newChild.keys = fullChild.keys.slice(T); + fullChild.keys = fullChild.keys.slice(0, T - 1); + + if (!fullChild.leaf) { + newChild.children = fullChild.children.slice(T); + fullChild.children = fullChild.children.slice(0, T); + } + + parent.children.splice(i + 1, 0, newChild); + parent.keys.splice(i, 0, midKey); +} + +// Inserts into a node that is guaranteed not to be full +function insertNonFull(node, key) { + let i = node.keys.length - 1; + + if (node.leaf) { + while (i >= 0 && key < node.keys[i]) i--; + node.keys.splice(i + 1, 0, key); + } else { + while (i >= 0 && key < node.keys[i]) i--; + i++; + if (node.children[i].keys.length === 2 * T - 1) { + splitChild(node, i); + if (key > node.keys[i]) i++; + } + insertNonFull(node.children[i], key); + } +} + +function insert(root, key) { + if (root === null) { + const node = new BTreeNode(true); + node.keys = [key]; + return node; + } + + if (root.keys.length === 2 * T - 1) { + // Root is full — split it first; this is the only way the tree grows taller + const newRoot = new BTreeNode(false); + newRoot.children.push(root); + splitChild(newRoot, 0); + insertNonFull(newRoot, key); + return newRoot; + } + + insertNonFull(root, key); + return root; +} + +// Usage example +let root = null; +[10, 20, 5, 6, 12, 30, 7, 17].forEach((key) => { + root = insert(root, key); +});`, + + python: `# B-Tree node — t is the tree's minimum degree +# (max keys per node = 2t-1, max children = 2t) +class BTreeNode: + def __init__(self, leaf): + self.keys = [] + self.children = [] + self.leaf = leaf + +T = 3 # minimum degree, tune to taste (or to your disk block size) + +def split_child(parent, i): + full_child = parent.children[i] + new_child = BTreeNode(full_child.leaf) + + mid_key = full_child.keys[T - 1] + new_child.keys = full_child.keys[T:] + full_child.keys = full_child.keys[:T - 1] + + if not full_child.leaf: + new_child.children = full_child.children[T:] + full_child.children = full_child.children[:T] + + parent.children.insert(i + 1, new_child) + parent.keys.insert(i, mid_key) + +def insert_non_full(node, key): + i = len(node.keys) - 1 + + if node.leaf: + while i >= 0 and key < node.keys[i]: + i -= 1 + node.keys.insert(i + 1, key) + else: + while i >= 0 and key < node.keys[i]: + i -= 1 + i += 1 + if len(node.children[i].keys) == 2 * T - 1: + split_child(node, i) + if key > node.keys[i]: + i += 1 + insert_non_full(node.children[i], key) + +def insert(root, key): + if root is None: + node = BTreeNode(True) + node.keys = [key] + return node + + if len(root.keys) == 2 * T - 1: + # Root is full — split it first; this is the only way the tree grows taller + new_root = BTreeNode(False) + new_root.children.append(root) + split_child(new_root, 0) + insert_non_full(new_root, key) + return new_root + + insert_non_full(root, key) + return root + +# Usage example +root = None +for key in [10, 20, 5, 6, 12, 30, 7, 17]: + root = insert(root, key)`, + + c: `#include +#include + +#define T 3 // minimum degree, tune to taste (or to your disk block size) +#define MAX_KEYS (2 * T - 1) + +typedef struct BTreeNode { + int keys[MAX_KEYS]; + struct BTreeNode* children[MAX_KEYS + 1]; + int numKeys; + int leaf; +} BTreeNode; + +BTreeNode* newNode(int leaf) { + BTreeNode* node = (BTreeNode*)malloc(sizeof(BTreeNode)); + node->numKeys = 0; + node->leaf = leaf; + return node; +} + +void splitChild(BTreeNode* parent, int i) { + BTreeNode* fullChild = parent->children[i]; + BTreeNode* newChild = newNode(fullChild->leaf); + + int midKey = fullChild->keys[T - 1]; + newChild->numKeys = fullChild->numKeys - T; + for (int j = 0; j < newChild->numKeys; j++) { + newChild->keys[j] = fullChild->keys[j + T]; + } + if (!fullChild->leaf) { + for (int j = 0; j <= newChild->numKeys; j++) { + newChild->children[j] = fullChild->children[j + T]; + } + } + fullChild->numKeys = T - 1; + + for (int j = parent->numKeys; j > i; j--) parent->children[j + 1] = parent->children[j]; + parent->children[i + 1] = newChild; + for (int j = parent->numKeys - 1; j >= i; j--) parent->keys[j + 1] = parent->keys[j]; + parent->keys[i] = midKey; + parent->numKeys++; +} + +void insertNonFull(BTreeNode* node, int key) { + int i = node->numKeys - 1; + + if (node->leaf) { + while (i >= 0 && key < node->keys[i]) { + node->keys[i + 1] = node->keys[i]; + i--; + } + node->keys[i + 1] = key; + node->numKeys++; + } else { + while (i >= 0 && key < node->keys[i]) i--; + i++; + if (node->children[i]->numKeys == MAX_KEYS) { + splitChild(node, i); + if (key > node->keys[i]) i++; + } + insertNonFull(node->children[i], key); + } +} + +BTreeNode* insert(BTreeNode* root, int key) { + if (root == NULL) { + BTreeNode* node = newNode(1); + node->keys[0] = key; + node->numKeys = 1; + return node; + } + + if (root->numKeys == MAX_KEYS) { + // Root is full — split it first; this is the only way the tree grows taller + BTreeNode* newRoot = newNode(0); + newRoot->children[0] = root; + splitChild(newRoot, 0); + insertNonFull(newRoot, key); + return newRoot; + } + + insertNonFull(root, key); + return root; +} + +int main() { + BTreeNode* root = NULL; + int values[] = {10, 20, 5, 6, 12, 30, 7, 17}; + for (int i = 0; i < 8; i++) { + root = insert(root, values[i]); + } + printf("B-tree built, root has %d key(s)\\n", root->numKeys); + return 0; +}`, + + java: `import java.util.ArrayList; +import java.util.List; + +class BTreeNode { + List keys = new ArrayList<>(); + List children = new ArrayList<>(); + boolean leaf; + + BTreeNode(boolean leaf) { + this.leaf = leaf; + } +} + +public class BTree { + static final int T = 3; // minimum degree, tune to taste (or to your disk block size) + + static void splitChild(BTreeNode parent, int i) { + BTreeNode fullChild = parent.children.get(i); + BTreeNode newChild = new BTreeNode(fullChild.leaf); + + int midKey = fullChild.keys.get(T - 1); + newChild.keys.addAll(fullChild.keys.subList(T, fullChild.keys.size())); + List leftKeys = new ArrayList<>(fullChild.keys.subList(0, T - 1)); + + if (!fullChild.leaf) { + newChild.children.addAll(fullChild.children.subList(T, fullChild.children.size())); + fullChild.children = new ArrayList<>(fullChild.children.subList(0, T)); + } + fullChild.keys = leftKeys; + + parent.children.add(i + 1, newChild); + parent.keys.add(i, midKey); + } + + static void insertNonFull(BTreeNode node, int key) { + int i = node.keys.size() - 1; + + if (node.leaf) { + while (i >= 0 && key < node.keys.get(i)) i--; + node.keys.add(i + 1, key); + } else { + while (i >= 0 && key < node.keys.get(i)) i--; + i++; + if (node.children.get(i).keys.size() == 2 * T - 1) { + splitChild(node, i); + if (key > node.keys.get(i)) i++; + } + insertNonFull(node.children.get(i), key); + } + } + + static BTreeNode insert(BTreeNode root, int key) { + if (root == null) { + BTreeNode node = new BTreeNode(true); + node.keys.add(key); + return node; + } + + if (root.keys.size() == 2 * T - 1) { + // Root is full — split it first; this is the only way the tree grows taller + BTreeNode newRoot = new BTreeNode(false); + newRoot.children.add(root); + splitChild(newRoot, 0); + insertNonFull(newRoot, key); + return newRoot; + } + + insertNonFull(root, key); + return root; + } + + public static void main(String[] args) { + BTreeNode root = null; + int[] values = {10, 20, 5, 6, 12, 30, 7, 17}; + for (int value : values) { + root = insert(root, value); + } + System.out.println("B-tree built, root has " + root.keys.size() + " key(s)"); + } +}`, +}; + +export default codeExamples; diff --git a/app/visualizer/trees/advanced/b-trees/content.jsx b/app/visualizer/trees/advanced/b-trees/content.jsx new file mode 100755 index 0000000..1a65f6e --- /dev/null +++ b/app/visualizer/trees/advanced/b-trees/content.jsx @@ -0,0 +1,262 @@ +"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 KEY_W = 36; +const NODE_H = 30; + +const BoxNode = ({ keys, x, y, fill = "#3b82f6", stroke = "#1d4ed8", ring, delay = 0 }) => { + const width = keys.length * KEY_W; + const left = x - width / 2; + return ( + + {ring && ( + + )} + + {keys.slice(1).map((_, i) => ( + + ))} + {keys.map((k, i) => ( + + {k} + + ))} + + ); +}; + +const SplitDiagram = () => ( +
+
+
+ Node [10, 20, 30] is full — inserting 25 triggers a split +
+ + + +
+
+
+ Median 20 moves up; 25 lands in the right half +
+ + + + + + + +
+
+); + +const Content = () => { + const { theme } = useTheme(); + + const paragraphs = [ + `A B-Tree generalizes the Binary Search Tree by letting each node hold multiple sorted keys and have more than two children. Instead of "smaller left, larger right," a node with k keys has k+1 children, and each child's entire range of values falls between two adjacent keys in the parent (or before the first / after the last).`, + `This wide branching factor is the entire point: B-Trees were designed for data that lives on disk, not in memory. Reading from disk is orders of magnitude slower than reading from RAM, and each disk read typically pulls in a whole block regardless of how much of it you actually need — so it makes sense to pack as many keys as possible into a single node/block and minimize the number of levels (and therefore disk reads) needed to find anything. This is exactly why B-Trees (and their variant, B+ Trees) are the standard on-disk index structure in databases like PostgreSQL and MySQL's InnoDB, and in filesystems like NTFS and ext4.`, + `Every B-Tree has a minimum degree t that fixes its shape: every node (except the root) must hold at least t-1 keys and at most 2t-1 keys, giving it between t and 2t children. Crucially, every leaf sits at exactly the same depth — a B-Tree never becomes lopsided the way an unbalanced BST can, because it grows upward from the root instead of downward from the leaves.`, + `Insertion needs O(1) extra space per split — no recursion stack proportional to the tree's key count, just a constant amount of bookkeeping per level the insertion touches, and the visualizer here uses t = 2 (so nodes can hold up to 3 keys) purely to keep the diagram small; real-world B-Trees typically use a t sized to match a disk block, often in the hundreds.`, + ]; + + const properties = [ + { title: "Every node holds sorted keys", body: "A node with k keys has exactly k+1 children — one for each gap between (and around) its keys." }, + { title: "Keys per node are bounded", body: "Every non-root node holds between t-1 and 2t-1 keys, where t is the tree's minimum degree." }, + { title: "All leaves are at the same depth", body: "Unlike a plain BST, a B-Tree grows in height only by splitting the root — every leaf is always exactly the same distance from the root." }, + { title: "Splits happen proactively", body: "This visualizer splits a full node on the way down before inserting into it, so a single insertion never has to backtrack up the tree." }, + ]; + + const algorithm = [ + { points: "If the tree is empty, create a new leaf node holding just the new key" }, + { points: "If the root itself is full (has 2t-1 keys), split it first — this is the only way the tree grows taller" }, + { + points: "Walk down from the root looking for the leaf where the key belongs. At each internal node:", + subpoints: [ + "Find which child's range the key falls into", + "If that child is full, split it before descending into it", + "Move into that child and repeat", + ], + }, + { points: "Once a non-full leaf is reached, insert the key into its sorted position" }, + ]; + + const complexity = [ + { points: "Time Complexity: Height is O(log_t n) — search, insert, and delete all cost O(log_t n)." }, + { points: "Disk I/O: Since each node is one block, height directly bounds the number of disk reads needed." }, + ]; + + return ( +
+
+ + +
+
+ {/* What is a B-Tree */} +
+

+ + What is a B-Tree? +

+
+

+ {paragraphs[0]} +

+
+
+ + {/* Why B-Trees */} +
+

+ + Why Use a B-Tree? +

+
+

+ {paragraphs[1]} +

+
+
+ + {/* Properties */} +
+

+ + Key Properties +

+
+

+ {paragraphs[2]} +

+
+ +
+ {properties.map((p) => ( +
+
{p.title}
+
{p.body}
+
+ ))} +
+
+ + {/* How a split works */} +
+

+ + How Does a Node Split Work? +

+ +
+ + + Full node (violation) + + + + Median key, promoted up + + + + Half that received the new key + +
+
+ + {/* Algorithm Steps */} +
+

+ + Algorithm Steps (Insertion) +

+
+
    + {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) => Math.log2(n)} + maxN={25} + /> +
+ + + +
+

+ {paragraphs[3]} +

+
+
+
+ + +
+ ); +}; + +export default Content; diff --git a/app/visualizer/trees/advanced/b-trees/page.jsx b/app/visualizer/trees/advanced/b-trees/page.jsx new file mode 100755 index 0000000..71f421c --- /dev/null +++ b/app/visualizer/trees/advanced/b-trees/page.jsx @@ -0,0 +1,111 @@ +import Animation from "@/app/visualizer/trees/advanced/b-trees/animation"; +import Navbar from "@/app/components/navbarinner"; +import ModuleHeader from "@/app/components/modules/Header"; +import Footer from "@/app/components/footer"; +import BackToTop from "@/app/components/ui/backtotop"; +import ExploreOther from "@/app/components/ui/exploreOther"; +import CodeBlock from "@/app/components/modules/CodeBlock"; +import codeExamples from "./code"; +import Quiz from "@/app/visualizer/trees/advanced/b-trees/quiz"; +import Content from "@/app/visualizer/trees/advanced/b-trees/content"; +import ModuleCard from "@/app/components/ui/ModuleCard"; +import { MODULE_MAPS } from "@/lib/modulesMap"; + +export const metadata = { + title: "B-Trees | Node Split Insertion Animation and Explanation", + description: + "Learn how B-Trees stay balanced by splitting full multi-key nodes, why they're the standard on-disk index structure for databases and filesystems, with an interactive insertion visualizer, code examples in JavaScript, C, Python, and Java, and a quiz.", + keywords: [ + "B-Tree", + "B Tree Insertion", + "B Tree Node Split", + "Self-Balancing Tree", + "B Tree Properties", + "B Tree Visualization", + "Database Index Structure", + "B Tree vs BST", + "B Tree in JavaScript", + "B Tree in C", + "B Tree in Python", + "B Tree in Java", + "Advanced Trees", + "DSA Trees", + "Learn Trees", + "Tree Quiz", + ], + robots: "index, follow", + openGraph: { + images: [ + { + url: "/og.png", + width: 1200, + height: 630, + alt: "B-Tree Visualization", + }, + ], + }, +}; + +export default function Page() { + const paths = [ + { name: "Home", href: "/" }, + { name: "Visualizer", href: "/visualizer" }, + { name: "Trees : B-Tree", href: "" }, + ]; + + return ( + <> +
+ +
+ +
+
+ + +
+ +
+ +
+ +
+

+ Test Your Knowledge before moving forward! +

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