From ed03d3251ef3d9e0b3bdb20f7a6c33522b206b51 Mon Sep 17 00:00:00 2001 From: Kai Chappell Date: Mon, 1 Sep 2025 20:49:11 +0100 Subject: [PATCH] feat(viz): tree/BFS/DFS patterns --- frontend/src/app/patterns/[slug]/page.tsx | 11 +- .../visualizations-new/algorithms/bfs.tsx | 85 +++ .../visualizations-new/algorithms/dfs.tsx | 85 +++ .../algorithms/tree-traversal.tsx | 85 +++ .../data-structures/binary-tree-view.tsx | 175 ++++++ .../data-structures/queue-view.tsx | 55 ++ .../components/visualizations-new/index.ts | 7 + .../primitives/queue-element.tsx | 61 ++ .../primitives/tree-node.tsx | 83 +++ frontend/src/content/algorithms/bfs.ts | 445 ++++++++++++++ frontend/src/content/algorithms/dfs.ts | 428 ++++++++++++++ .../src/content/algorithms/tree-traversal.ts | 557 ++++++++++++++++++ frontend/src/lib/visualizations/types.ts | 43 ++ 13 files changed, 2119 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/visualizations-new/algorithms/bfs.tsx create mode 100644 frontend/src/components/visualizations-new/algorithms/dfs.tsx create mode 100644 frontend/src/components/visualizations-new/algorithms/tree-traversal.tsx create mode 100644 frontend/src/components/visualizations-new/data-structures/binary-tree-view.tsx create mode 100644 frontend/src/components/visualizations-new/data-structures/queue-view.tsx create mode 100644 frontend/src/components/visualizations-new/primitives/queue-element.tsx create mode 100644 frontend/src/components/visualizations-new/primitives/tree-node.tsx create mode 100644 frontend/src/content/algorithms/bfs.ts create mode 100644 frontend/src/content/algorithms/dfs.ts create mode 100644 frontend/src/content/algorithms/tree-traversal.ts diff --git a/frontend/src/app/patterns/[slug]/page.tsx b/frontend/src/app/patterns/[slug]/page.tsx index 57273bd..4f34d29 100644 --- a/frontend/src/app/patterns/[slug]/page.tsx +++ b/frontend/src/app/patterns/[slug]/page.tsx @@ -14,7 +14,7 @@ import { RelatedPatterns, } from "@/components/patterns"; import { PatternVisualization } from "@/components/visualization"; -import { TwoPointersVisualization, PrefixSumVisualization, LinkedListVisualization, MonotonicStackVisualization } from "@/components/visualizations-new"; +import { TwoPointersVisualization, PrefixSumVisualization, LinkedListVisualization, MonotonicStackVisualization, TreeTraversalVisualization, BFSVisualization, DFSVisualization } from "@/components/visualizations-new"; import { twoSumAlgorithm } from "@/content/algorithms/two-sum"; import { slidingWindowAlgorithm } from "@/content/algorithms/sliding-window"; import { binarySearchAlgorithm } from "@/content/algorithms/binary-search"; @@ -22,6 +22,9 @@ import { prefixSumAlgorithm } from "@/content/algorithms/prefix-sum"; import { fastSlowPointersAlgorithm } from "@/content/algorithms/fast-slow-pointers"; import { linkedListReversalAlgorithm } from "@/content/algorithms/linkedlist-reversal"; import { monotonicStackAlgorithm } from "@/content/algorithms/monotonic-stack"; +import { treeTraversalAlgorithm } from "@/content/algorithms/tree-traversal"; +import { bfsAlgorithm } from "@/content/algorithms/bfs"; +import { dfsAlgorithm } from "@/content/algorithms/dfs"; interface PageProps { params: Promise<{ slug: string }>; @@ -128,6 +131,12 @@ export default async function PatternDetailPage({ params }: PageProps) { ) : slug === "monotonic-stack" ? ( + ) : slug === "tree-traversal" ? ( + + ) : slug === "bfs" ? ( + + ) : slug === "dfs" ? ( + ) : pattern.visualization_examples && pattern.visualization_examples.length > 0 ? ( diff --git a/frontend/src/components/visualizations-new/algorithms/bfs.tsx b/frontend/src/components/visualizations-new/algorithms/bfs.tsx new file mode 100644 index 0000000..5c602ad --- /dev/null +++ b/frontend/src/components/visualizations-new/algorithms/bfs.tsx @@ -0,0 +1,85 @@ +'use client'; + +import { useVisualization } from '@/lib/visualizations/use-visualization'; +import type { AlgorithmDefinition } from '@/lib/visualizations/types'; +import { VisualizationContainer } from '../core/visualization-container'; +import { BinaryTreeView } from '../data-structures/binary-tree-view'; +import { QueueView } from '../data-structures/queue-view'; +import { ArrayView } from '../data-structures/array-view'; + +interface BFSVisualizationProps { + algorithm: AlgorithmDefinition; + className?: string; +} + +export function BFSVisualization({ + algorithm, + className, +}: BFSVisualizationProps) { + const { + currentStep, + currentStepIndex, + totalSteps, + playback, + controls, + currentPhase, + progress, + } = useVisualization(algorithm); + + const { dataState } = currentStep; + const tree = dataState.trees?.[0] ?? null; + const bfsQueue = dataState.queues?.[0] ?? null; + const outputArray = dataState.arrays?.[0] ?? null; + + // Find the current node ID based on node states + const currentNodeId = tree?.nodes.find((n) => n.state === 'current')?.id; + + return ( + +
+ {/* Top: Binary tree */} + {tree && ( + + )} + + {/* Bottom: Queue + Output */} +
+ {/* BFS Queue */} + {bfsQueue && ( + + )} + + {/* Output array */} + {outputArray && ( + + )} +
+
+
+ ); +} diff --git a/frontend/src/components/visualizations-new/algorithms/dfs.tsx b/frontend/src/components/visualizations-new/algorithms/dfs.tsx new file mode 100644 index 0000000..9dfd32c --- /dev/null +++ b/frontend/src/components/visualizations-new/algorithms/dfs.tsx @@ -0,0 +1,85 @@ +'use client'; + +import { useVisualization } from '@/lib/visualizations/use-visualization'; +import type { AlgorithmDefinition } from '@/lib/visualizations/types'; +import { VisualizationContainer } from '../core/visualization-container'; +import { BinaryTreeView } from '../data-structures/binary-tree-view'; +import { StackView } from '../data-structures/stack-view'; +import { ArrayView } from '../data-structures/array-view'; + +interface DFSVisualizationProps { + algorithm: AlgorithmDefinition; + className?: string; +} + +export function DFSVisualization({ + algorithm, + className, +}: DFSVisualizationProps) { + const { + currentStep, + currentStepIndex, + totalSteps, + playback, + controls, + currentPhase, + progress, + } = useVisualization(algorithm); + + const { dataState } = currentStep; + const tree = dataState.trees?.[0] ?? null; + const dfsStack = dataState.stacks?.[0] ?? null; + const outputArray = dataState.arrays?.[0] ?? null; + + // Find the current node ID based on node states + const currentNodeId = tree?.nodes.find((n) => n.state === 'current')?.id; + + return ( + +
+ {/* Left: Binary tree */} + {tree && ( + + )} + + {/* Right side: Stack + Output */} +
+ {/* DFS Stack */} + {dfsStack && ( + + )} + + {/* Output array */} + {outputArray && ( + + )} +
+
+
+ ); +} diff --git a/frontend/src/components/visualizations-new/algorithms/tree-traversal.tsx b/frontend/src/components/visualizations-new/algorithms/tree-traversal.tsx new file mode 100644 index 0000000..22d0cd7 --- /dev/null +++ b/frontend/src/components/visualizations-new/algorithms/tree-traversal.tsx @@ -0,0 +1,85 @@ +'use client'; + +import { useVisualization } from '@/lib/visualizations/use-visualization'; +import type { AlgorithmDefinition } from '@/lib/visualizations/types'; +import { VisualizationContainer } from '../core/visualization-container'; +import { BinaryTreeView } from '../data-structures/binary-tree-view'; +import { StackView } from '../data-structures/stack-view'; +import { ArrayView } from '../data-structures/array-view'; + +interface TreeTraversalVisualizationProps { + algorithm: AlgorithmDefinition; + className?: string; +} + +export function TreeTraversalVisualization({ + algorithm, + className, +}: TreeTraversalVisualizationProps) { + const { + currentStep, + currentStepIndex, + totalSteps, + playback, + controls, + currentPhase, + progress, + } = useVisualization(algorithm); + + const { dataState } = currentStep; + const tree = dataState.trees?.[0] ?? null; + const traversalStack = dataState.stacks?.[0] ?? null; + const outputArray = dataState.arrays?.[0] ?? null; + + // Find the current node ID based on node states + const currentNodeId = tree?.nodes.find((n) => n.state === 'current')?.id; + + return ( + +
+ {/* Left: Binary tree */} + {tree && ( + + )} + + {/* Right side: Stack + Output */} +
+ {/* Traversal stack */} + {traversalStack && ( + + )} + + {/* Output array */} + {outputArray && ( + + )} +
+
+
+ ); +} diff --git a/frontend/src/components/visualizations-new/data-structures/binary-tree-view.tsx b/frontend/src/components/visualizations-new/data-structures/binary-tree-view.tsx new file mode 100644 index 0000000..afecd6a --- /dev/null +++ b/frontend/src/components/visualizations-new/data-structures/binary-tree-view.tsx @@ -0,0 +1,175 @@ +'use client'; + +import { useMemo } from 'react'; +import { motion } from 'framer-motion'; +import { cn } from '@/lib/utils'; +import type { BinaryTreeState, BinaryTreeNodeState } from '@/lib/visualizations/types'; +import { TreeNode } from '../primitives/tree-node'; + +interface BinaryTreeViewProps { + tree: BinaryTreeState; + currentNodeId?: string; + className?: string; +} + +const NODE_RADIUS = 24; +const LEVEL_HEIGHT = 72; +const MIN_NODE_SPACING = 56; +const SVG_PADDING = 32; + +interface NodePosition { + node: BinaryTreeNodeState; + x: number; + y: number; + parentX?: number; + parentY?: number; +} + +function buildNodeMap(nodes: BinaryTreeNodeState[]): Map { + const map = new Map(); + for (const node of nodes) { + map.set(node.id, node); + } + return map; +} + +function calculateTreeDepth( + nodeId: string | null, + nodeMap: Map +): number { + if (!nodeId) return 0; + const node = nodeMap.get(nodeId); + if (!node) return 0; + const leftDepth = calculateTreeDepth(node.left, nodeMap); + const rightDepth = calculateTreeDepth(node.right, nodeMap); + return 1 + Math.max(leftDepth, rightDepth); +} + +function calculatePositions( + tree: BinaryTreeState, + totalWidth: number +): NodePosition[] { + const nodeMap = buildNodeMap(tree.nodes); + const positions: NodePosition[] = []; + + function traverse( + nodeId: string | null, + level: number, + left: number, + right: number, + parentX?: number, + parentY?: number + ) { + if (!nodeId) return; + const node = nodeMap.get(nodeId); + if (!node) return; + + const x = (left + right) / 2; + const y = SVG_PADDING + level * LEVEL_HEIGHT + NODE_RADIUS; + + positions.push({ node, x, y, parentX, parentY }); + + const childWidth = (right - left) / 2; + traverse(node.left, level + 1, left, left + childWidth, x, y); + traverse(node.right, level + 1, left + childWidth, right, x, y); + } + + traverse(tree.rootId, 0, 0, totalWidth); + return positions; +} + +export function BinaryTreeView({ + tree, + currentNodeId, + className, +}: BinaryTreeViewProps) { + const { positions, svgWidth, svgHeight } = useMemo(() => { + const nodeMap = buildNodeMap(tree.nodes); + const depth = calculateTreeDepth(tree.rootId, nodeMap); + const maxNodesAtBottom = Math.pow(2, depth - 1); + const width = Math.max( + maxNodesAtBottom * MIN_NODE_SPACING + SVG_PADDING * 2, + 200 + ); + const height = depth * LEVEL_HEIGHT + SVG_PADDING * 2; + const pos = calculatePositions(tree, width); + return { positions: pos, svgWidth: width, svgHeight: height }; + }, [tree]); + + return ( +
+ {tree.label && ( + + {tree.label} + + )} + + {/* Draw edges first (behind nodes) */} + {positions.map(({ node, x, y, parentX, parentY }) => + parentX !== undefined && parentY !== undefined ? ( + + ) : null + )} + + {/* Draw nodes */} + {positions.map(({ node, x, y }) => ( + + ))} + + {/* Current node indicator arrow */} + {currentNodeId && (() => { + const currentPos = positions.find((p) => p.node.id === currentNodeId); + if (!currentPos) return null; + const { x, y } = currentPos; + const arrowY = y + NODE_RADIUS + 12; + return ( + + + + curr + + + ); + })()} + +
+ ); +} diff --git a/frontend/src/components/visualizations-new/data-structures/queue-view.tsx b/frontend/src/components/visualizations-new/data-structures/queue-view.tsx new file mode 100644 index 0000000..ae475b3 --- /dev/null +++ b/frontend/src/components/visualizations-new/data-structures/queue-view.tsx @@ -0,0 +1,55 @@ +'use client'; + +import { AnimatePresence } from 'framer-motion'; +import { cn } from '@/lib/utils'; +import type { QueueState } from '@/lib/visualizations/types'; +import { QueueElement } from '../primitives/queue-element'; + +interface QueueViewProps { + queue: QueueState; + className?: string; +} + +export function QueueView({ queue, className }: QueueViewProps) { + const hasElements = queue.elements.length > 0; + + return ( +
+ {queue.label && ( + + {queue.label} + + )} + +
+ {/* Empty state */} + {!hasElements && ( +
+ empty +
+ )} + + {/* Queue elements - horizontal row, front on left, rear on right */} + + {queue.elements.map((element, index) => ( + 1} + /> + ))} + +
+ + {/* Direction indicator */} +
+ dequeue + + + + enqueue +
+
+ ); +} diff --git a/frontend/src/components/visualizations-new/index.ts b/frontend/src/components/visualizations-new/index.ts index 6a89d41..9e7e61e 100644 --- a/frontend/src/components/visualizations-new/index.ts +++ b/frontend/src/components/visualizations-new/index.ts @@ -12,14 +12,21 @@ export { CalculationBubble } from "./primitives/calculation-bubble"; export { LinkedListNode } from "./primitives/linked-list-node"; export { LinkedListPointer } from "./primitives/linked-list-pointer"; export { StackElement } from "./primitives/stack-element"; +export { QueueElement } from "./primitives/queue-element"; +export { TreeNode } from "./primitives/tree-node"; // Data structures export { ArrayView } from "./data-structures/array-view"; export { LinkedListView } from "./data-structures/linked-list-view"; export { StackView } from "./data-structures/stack-view"; +export { QueueView } from "./data-structures/queue-view"; +export { BinaryTreeView } from "./data-structures/binary-tree-view"; // Algorithm visualizations export { MonotonicStackVisualization } from "./algorithms/monotonic-stack"; export { PrefixSumVisualization } from "./algorithms/prefix-sum"; +export { TreeTraversalVisualization } from "./algorithms/tree-traversal"; +export { BFSVisualization } from "./algorithms/bfs"; +export { DFSVisualization } from "./algorithms/dfs"; export { TwoPointersVisualization } from "./algorithms/two-pointers"; export { LinkedListVisualization } from "./algorithms/linked-list"; diff --git a/frontend/src/components/visualizations-new/primitives/queue-element.tsx b/frontend/src/components/visualizations-new/primitives/queue-element.tsx new file mode 100644 index 0000000..721e281 --- /dev/null +++ b/frontend/src/components/visualizations-new/primitives/queue-element.tsx @@ -0,0 +1,61 @@ +'use client'; + +import { motion } from 'framer-motion'; +import { cn } from '@/lib/utils'; +import type { QueueElementState } from '@/lib/visualizations/types'; + +interface QueueElementProps { + element: QueueElementState; + isFront?: boolean; + isRear?: boolean; + className?: string; +} + +const STATE_CLASSES = { + normal: 'bg-[var(--surface-variant)] border-[var(--border)] text-[var(--foreground)]', + highlighted: 'bg-[var(--primary)]/20 border-[var(--primary)] text-[var(--primary)]', + enqueued: 'bg-[var(--success)]/20 border-[var(--success)] text-[var(--success)]', + dequeued: 'bg-[var(--error)]/20 border-[var(--error)] text-[var(--error)]', +} as const; + +export function QueueElement({ + element, + isFront = false, + isRear = false, + className, +}: QueueElementProps) { + return ( + + {element.value} + {isFront && ( + + FRONT + + )} + {isRear && ( + + REAR + + )} + + ); +} diff --git a/frontend/src/components/visualizations-new/primitives/tree-node.tsx b/frontend/src/components/visualizations-new/primitives/tree-node.tsx new file mode 100644 index 0000000..41bc15a --- /dev/null +++ b/frontend/src/components/visualizations-new/primitives/tree-node.tsx @@ -0,0 +1,83 @@ +'use client'; + +import { motion } from 'framer-motion'; +import { cn } from '@/lib/utils'; +import type { BinaryTreeNodeState } from '@/lib/visualizations/types'; + +interface TreeNodeProps { + node: BinaryTreeNodeState; + x: number; + y: number; + radius?: number; + className?: string; +} + +const STATE_CLASSES = { + normal: 'fill-[var(--surface-variant)] stroke-[var(--border)]', + current: 'fill-[var(--primary)]/20 stroke-[var(--primary)]', + visiting: 'fill-[var(--info)]/20 stroke-[var(--info)]', + visited: 'fill-[var(--success)]/20 stroke-[var(--success)] opacity-70', + highlighted: 'fill-[var(--primary)]/30 stroke-[var(--primary)]', +} as const; + +const TEXT_CLASSES = { + normal: 'fill-[var(--foreground)]', + current: 'fill-[var(--primary)]', + visiting: 'fill-[var(--info)]', + visited: 'fill-[var(--success)]', + highlighted: 'fill-[var(--primary)]', +} as const; + +export function TreeNode({ + node, + x, + y, + radius = 24, + className, +}: TreeNodeProps) { + const isActive = node.state === 'current' || node.state === 'highlighted'; + + return ( + + + + {node.value} + + + ); +} diff --git a/frontend/src/content/algorithms/bfs.ts b/frontend/src/content/algorithms/bfs.ts new file mode 100644 index 0000000..9af4de6 --- /dev/null +++ b/frontend/src/content/algorithms/bfs.ts @@ -0,0 +1,445 @@ +import type { AlgorithmDefinition } from '@/lib/visualizations/types'; + +/** + * Tree structure (level order: [4, 2, 6, 1, 3, 5, 7]): + * + * 4 (root) + * / \ + * 2 6 + * / \ / \ + * 1 3 5 7 + * + * BFS (Level-order) output: [4, 2, 6, 1, 3, 5, 7] + */ + +// Node IDs for our tree +const NODE_4 = 'n4'; +const NODE_2 = 'n2'; +const NODE_6 = 'n6'; +const NODE_1 = 'n1'; +const NODE_3 = 'n3'; +const NODE_5 = 'n5'; +const NODE_7 = 'n7'; + +// Helper to create tree state with specific node states +function createTreeState(nodeStates: Record) { + return { + id: 'tree', + label: 'Binary Search Tree', + rootId: NODE_4, + nodes: [ + { id: NODE_4, value: 4, state: nodeStates[NODE_4] ?? 'normal', left: NODE_2, right: NODE_6 }, + { id: NODE_2, value: 2, state: nodeStates[NODE_2] ?? 'normal', left: NODE_1, right: NODE_3 }, + { id: NODE_6, value: 6, state: nodeStates[NODE_6] ?? 'normal', left: NODE_5, right: NODE_7 }, + { id: NODE_1, value: 1, state: nodeStates[NODE_1] ?? 'normal', left: null, right: null }, + { id: NODE_3, value: 3, state: nodeStates[NODE_3] ?? 'normal', left: null, right: null }, + { id: NODE_5, value: 5, state: nodeStates[NODE_5] ?? 'normal', left: null, right: null }, + { id: NODE_7, value: 7, state: nodeStates[NODE_7] ?? 'normal', left: null, right: null }, + ], + }; +} + +// Helper to create queue state +function createQueueState(values: number[], highlightFront = false, newRear = false) { + return { + id: 'bfs-queue', + label: 'Queue', + elements: values.map((v, i) => ({ + id: `queue-${v}-${i}`, + value: v, + state: (highlightFront && i === 0 ? 'highlighted' : newRear && i === values.length - 1 ? 'enqueued' : 'normal') as 'normal' | 'highlighted' | 'enqueued' | 'dequeued', + })), + }; +} + +// Helper to create output array state +function createOutputState(values: (number | null)[], highlightIndex?: number) { + const maxLen = 7; + const elements = []; + for (let i = 0; i < maxLen; i++) { + const val = values[i]; + elements.push({ + value: val ?? 0, + index: i, + state: (val === null ? 'dimmed' : i === highlightIndex ? 'highlighted' : 'success') as 'normal' | 'highlighted' | 'dimmed' | 'success' | 'comparing', + }); + } + return { + id: 'output', + label: 'Output Array', + elements, + }; +} + +export const bfsAlgorithm: AlgorithmDefinition = { + id: 'bfs', + title: 'Breadth-First Search (BFS)', + slug: 'bfs', + pattern: { + name: 'BFS', + description: + 'Use a queue to explore nodes level by level, visiting all nodes at the current depth before moving deeper.', + }, + problemStatement: + 'Given a binary tree, traverse it level by level (breadth-first) and return the values in the order visited.', + intuition: + 'BFS explores nodes in waves outward from the root. Using a queue ensures we process nodes in the order they were discovered - first in, first out (FIFO). This naturally produces level-order traversal.', + code: { + language: 'python', + code: `def bfs(root): + if not root: + return [] + + result = [] + queue = [root] + + while queue: + # Dequeue front node + node = queue.pop(0) + result.append(node.val) + + # Enqueue children (left first, then right) + if node.left: + queue.append(node.left) + if node.right: + queue.append(node.right) + + return result`, + }, + initialExample: { + input: { tree: [4, 2, 6, 1, 3, 5, 7] }, + expected: [4, 2, 6, 1, 3, 5, 7], + }, + steps: [ + // ========================================== + // Phase 1: Problem (2 steps) + // ========================================== + { + id: 'problem-1', + phase: 'problem', + explanation: + 'We have a binary tree. Our goal is to visit all nodes level by level, from left to right at each level.', + dataState: { + arrays: [], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({})], + queues: [createQueueState([])], + }, + }, + { + id: 'problem-2', + phase: 'problem', + explanation: + 'For this tree: Level 0 has [4], Level 1 has [2, 6], Level 2 has [1, 3, 5, 7]. Expected output: [4, 2, 6, 1, 3, 5, 7].', + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'highlighted', [NODE_2]: 'visiting', [NODE_6]: 'visiting' })], + queues: [createQueueState([])], + }, + }, + + // ========================================== + // Phase 2: Intuition (3 steps) + // ========================================== + { + id: 'intuition-1', + phase: 'intuition', + explanation: + 'BFS uses a queue (FIFO). We start by adding the root, then repeatedly: dequeue one node, add its value to result, and enqueue its children.', + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'current' })], + queues: [createQueueState([4])], + }, + }, + { + id: 'intuition-2', + phase: 'intuition', + explanation: + 'The queue acts as a "to-visit" list. When we dequeue a node, its children join the back of the queue. This ensures we finish each level before starting the next.', + dataState: { + arrays: [createOutputState([4, null, null, null, null, null, null], 0)], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'current', [NODE_6]: 'visiting' })], + queues: [createQueueState([2, 6], true)], + }, + }, + { + id: 'intuition-3', + phase: 'intuition', + explanation: + 'Key insight: nodes discovered first are processed first (FIFO). A node\'s children are always behind other nodes at the same level in the queue.', + dataState: { + arrays: [createOutputState([4, 2, null, null, null, null, null])], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_6]: 'current', [NODE_1]: 'visiting', [NODE_3]: 'visiting' })], + queues: [createQueueState([6, 1, 3], true)], + }, + }, + + // ========================================== + // Phase 3: Pattern (2 steps) + // ========================================== + { + id: 'pattern-1', + phase: 'pattern', + explanation: + 'BFS pattern: (1) Initialize queue with root, (2) While queue not empty: dequeue, process, enqueue children.', + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({})], + queues: [createQueueState([])], + }, + }, + { + id: 'pattern-2', + phase: 'pattern', + explanation: + 'The queue ensures level-order: all Level N nodes are processed before any Level N+1 nodes, because children are always added to the rear.', + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [ + { id: 'queue', name: 'queue', value: 'FIFO' }, + { id: 'result', name: 'result', value: '[]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'current' })], + queues: [createQueueState([4])], + }, + }, + + // ========================================== + // Phase 4: Code (3 steps) + // ========================================== + { + id: 'code-1', + phase: 'code', + explanation: + 'Initialize: empty result list, queue containing just the root node.', + codeLine: 5, + codeHighlightLines: [5, 6, 7], + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [ + { id: 'result', name: 'result', value: '[]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'current' })], + queues: [createQueueState([4])], + }, + }, + { + id: 'code-2', + phase: 'code', + explanation: + 'Main loop: While queue is not empty, dequeue the front node and add its value to result.', + codeLine: 10, + codeHighlightLines: [9, 10, 11, 12], + dataState: { + arrays: [createOutputState([4, null, null, null, null, null, null], 0)], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited' })], + queues: [createQueueState([])], + }, + }, + { + id: 'code-3', + phase: 'code', + explanation: + 'After processing a node, enqueue its left child (if exists), then right child (if exists). This maintains left-to-right order within each level.', + codeLine: 15, + codeHighlightLines: [14, 15, 16, 17, 18], + dataState: { + arrays: [createOutputState([4, null, null, null, null, null, null])], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visiting', [NODE_6]: 'visiting' })], + queues: [createQueueState([2, 6], false, true)], + }, + }, + + // ========================================== + // Phase 5: Execution (12 steps) + // ========================================== + { + id: 'exec-1', + phase: 'execution', + explanation: + 'Start: queue = [4], result = []. The root node is ready to be processed.', + codeLine: 7, + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [ + { id: 'result', name: 'result', value: '[]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'current' })], + queues: [createQueueState([4], true)], + }, + }, + { + id: 'exec-2', + phase: 'execution', + explanation: + 'Dequeue 4 (the root). Add to result. Result: [4]. Now enqueue its children: 2 (left), 6 (right).', + codeLine: 12, + dataState: { + arrays: [createOutputState([4, null, null, null, null, null, null], 0)], + pointers: [], + variables: [ + { id: 'node', name: 'node', value: 4 }, + { id: 'result', name: 'result', value: '[4]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visiting', [NODE_6]: 'visiting' })], + queues: [createQueueState([2, 6], false, true)], + }, + }, + { + id: 'exec-3', + phase: 'execution', + explanation: + 'Queue: [2, 6]. Dequeue 2 (front). Add to result. Result: [4, 2]. Enqueue children: 1, 3.', + codeLine: 12, + dataState: { + arrays: [createOutputState([4, 2, null, null, null, null, null], 1)], + pointers: [], + variables: [ + { id: 'node', name: 'node', value: 2, previousValue: 4 }, + { id: 'result', name: 'result', value: '[4, 2]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_6]: 'visiting', [NODE_1]: 'visiting', [NODE_3]: 'visiting' })], + queues: [createQueueState([6, 1, 3], false, true)], + }, + }, + { + id: 'exec-4', + phase: 'execution', + explanation: + 'Queue: [6, 1, 3]. Dequeue 6 (front). Add to result. Result: [4, 2, 6]. Enqueue children: 5, 7.', + codeLine: 12, + dataState: { + arrays: [createOutputState([4, 2, 6, null, null, null, null], 2)], + pointers: [], + variables: [ + { id: 'node', name: 'node', value: 6, previousValue: 2 }, + { id: 'result', name: 'result', value: '[4, 2, 6]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_6]: 'visited', [NODE_1]: 'visiting', [NODE_3]: 'visiting', [NODE_5]: 'visiting', [NODE_7]: 'visiting' })], + queues: [createQueueState([1, 3, 5, 7], false, true)], + }, + }, + { + id: 'exec-5', + phase: 'execution', + explanation: + 'Level 1 complete! Queue: [1, 3, 5, 7] - all Level 2 nodes. Dequeue 1. No children to enqueue.', + codeLine: 12, + dataState: { + arrays: [createOutputState([4, 2, 6, 1, null, null, null], 3)], + pointers: [], + variables: [ + { id: 'node', name: 'node', value: 1, previousValue: 6 }, + { id: 'result', name: 'result', value: '[4, 2, 6, 1]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_6]: 'visited', [NODE_1]: 'visited', [NODE_3]: 'visiting', [NODE_5]: 'visiting', [NODE_7]: 'visiting' })], + queues: [createQueueState([3, 5, 7], true)], + }, + }, + { + id: 'exec-6', + phase: 'execution', + explanation: + 'Queue: [3, 5, 7]. Dequeue 3. Result: [4, 2, 6, 1, 3]. Node 3 is a leaf (no children).', + codeLine: 12, + dataState: { + arrays: [createOutputState([4, 2, 6, 1, 3, null, null], 4)], + pointers: [], + variables: [ + { id: 'node', name: 'node', value: 3, previousValue: 1 }, + { id: 'result', name: 'result', value: '[4, 2, 6, 1, 3]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_6]: 'visited', [NODE_1]: 'visited', [NODE_3]: 'visited', [NODE_5]: 'visiting', [NODE_7]: 'visiting' })], + queues: [createQueueState([5, 7], true)], + }, + }, + { + id: 'exec-7', + phase: 'execution', + explanation: + 'Queue: [5, 7]. Dequeue 5. Result: [4, 2, 6, 1, 3, 5]. Node 5 is a leaf (no children).', + codeLine: 12, + dataState: { + arrays: [createOutputState([4, 2, 6, 1, 3, 5, null], 5)], + pointers: [], + variables: [ + { id: 'node', name: 'node', value: 5, previousValue: 3 }, + { id: 'result', name: 'result', value: '[4, 2, 6, 1, 3, 5]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_6]: 'visited', [NODE_1]: 'visited', [NODE_3]: 'visited', [NODE_5]: 'visited', [NODE_7]: 'visiting' })], + queues: [createQueueState([7], true)], + }, + }, + { + id: 'exec-8', + phase: 'execution', + explanation: + 'Queue: [7]. Dequeue 7. Result: [4, 2, 6, 1, 3, 5, 7]. Node 7 is a leaf (no children).', + codeLine: 12, + dataState: { + arrays: [createOutputState([4, 2, 6, 1, 3, 5, 7], 6)], + pointers: [], + variables: [ + { id: 'node', name: 'node', value: 7, previousValue: 5 }, + { id: 'result', name: 'result', value: '[4, 2, 6, 1, 3, 5, 7]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_6]: 'visited', [NODE_1]: 'visited', [NODE_3]: 'visited', [NODE_5]: 'visited', [NODE_7]: 'visited' })], + queues: [createQueueState([])], + }, + }, + { + id: 'exec-9', + phase: 'execution', + explanation: + 'Queue is empty! All nodes have been visited. Final result: [4, 2, 6, 1, 3, 5, 7] - perfect level-order traversal.', + codeLine: 20, + dataState: { + arrays: [createOutputState([4, 2, 6, 1, 3, 5, 7])], + pointers: [], + variables: [ + { id: 'result', name: 'result', value: '[4, 2, 6, 1, 3, 5, 7]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_6]: 'visited', [NODE_1]: 'visited', [NODE_3]: 'visited', [NODE_5]: 'visited', [NODE_7]: 'visited' })], + queues: [createQueueState([])], + }, + }, + ], +}; diff --git a/frontend/src/content/algorithms/dfs.ts b/frontend/src/content/algorithms/dfs.ts new file mode 100644 index 0000000..f80d5be --- /dev/null +++ b/frontend/src/content/algorithms/dfs.ts @@ -0,0 +1,428 @@ +import type { AlgorithmDefinition } from '@/lib/visualizations/types'; + +/** + * Tree structure (level order: [4, 2, 6, 1, 3, 5, 7]): + * + * 4 (root) + * / \ + * 2 6 + * / \ / \ + * 1 3 5 7 + * + * DFS (Preorder) output: [4, 2, 1, 3, 6, 5, 7] + */ + +// Node IDs for our tree +const NODE_4 = 'n4'; +const NODE_2 = 'n2'; +const NODE_6 = 'n6'; +const NODE_1 = 'n1'; +const NODE_3 = 'n3'; +const NODE_5 = 'n5'; +const NODE_7 = 'n7'; + +// Helper to create tree state with specific node states +function createTreeState(nodeStates: Record) { + return { + id: 'tree', + label: 'Binary Search Tree', + rootId: NODE_4, + nodes: [ + { id: NODE_4, value: 4, state: nodeStates[NODE_4] ?? 'normal', left: NODE_2, right: NODE_6 }, + { id: NODE_2, value: 2, state: nodeStates[NODE_2] ?? 'normal', left: NODE_1, right: NODE_3 }, + { id: NODE_6, value: 6, state: nodeStates[NODE_6] ?? 'normal', left: NODE_5, right: NODE_7 }, + { id: NODE_1, value: 1, state: nodeStates[NODE_1] ?? 'normal', left: null, right: null }, + { id: NODE_3, value: 3, state: nodeStates[NODE_3] ?? 'normal', left: null, right: null }, + { id: NODE_5, value: 5, state: nodeStates[NODE_5] ?? 'normal', left: null, right: null }, + { id: NODE_7, value: 7, state: nodeStates[NODE_7] ?? 'normal', left: null, right: null }, + ], + }; +} + +// Helper to create stack state +function createStackState(values: number[], highlightTop = false) { + return { + id: 'dfs-stack', + label: 'Stack', + elements: values.map((v, i) => ({ + id: `stack-${v}-${i}`, + value: v, + state: (highlightTop && i === values.length - 1 ? 'highlighted' : 'normal') as 'normal' | 'highlighted', + })), + }; +} + +// Helper to create output array state +function createOutputState(values: (number | null)[], highlightIndex?: number) { + const maxLen = 7; + const elements = []; + for (let i = 0; i < maxLen; i++) { + const val = values[i]; + elements.push({ + value: val ?? 0, + index: i, + state: (val === null ? 'dimmed' : i === highlightIndex ? 'highlighted' : 'success') as 'normal' | 'highlighted' | 'dimmed' | 'success' | 'comparing', + }); + } + return { + id: 'output', + label: 'Output Array', + elements, + }; +} + +export const dfsAlgorithm: AlgorithmDefinition = { + id: 'dfs', + title: 'Depth-First Search (DFS)', + slug: 'dfs', + pattern: { + name: 'DFS', + description: + 'Use a stack to explore as deep as possible along each branch before backtracking.', + }, + problemStatement: + 'Given a binary tree, traverse it depth-first (preorder: Root-Left-Right) and return the values in the order visited.', + intuition: + 'DFS explores one branch completely before backtracking. Using a stack (LIFO) naturally achieves this: we pop a node, process it, then push its children. By pushing right before left, we ensure left children are processed first.', + code: { + language: 'python', + code: `def dfs(root): + if not root: + return [] + + result = [] + stack = [root] + + while stack: + # Pop top node + node = stack.pop() + result.append(node.val) + + # Push right first, then left (so left is popped first) + if node.right: + stack.append(node.right) + if node.left: + stack.append(node.left) + + return result`, + }, + initialExample: { + input: { tree: [4, 2, 6, 1, 3, 5, 7] }, + expected: [4, 2, 1, 3, 6, 5, 7], + }, + steps: [ + // ========================================== + // Phase 1: Problem (2 steps) + // ========================================== + { + id: 'problem-1', + phase: 'problem', + explanation: + 'We have a binary tree. Our goal is to visit all nodes depth-first: go as deep as possible down each branch before backtracking.', + dataState: { + arrays: [], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({})], + stacks: [createStackState([])], + }, + }, + { + id: 'problem-2', + phase: 'problem', + explanation: + 'Preorder DFS visits Root, then Left subtree, then Right subtree. Expected output: [4, 2, 1, 3, 6, 5, 7].', + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'highlighted' })], + stacks: [createStackState([])], + }, + }, + + // ========================================== + // Phase 2: Intuition (3 steps) + // ========================================== + { + id: 'intuition-1', + phase: 'intuition', + explanation: + 'DFS uses a stack (LIFO). We start by pushing the root, then repeatedly: pop a node, process it, and push its children.', + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'current' })], + stacks: [createStackState([4], true)], + }, + }, + { + id: 'intuition-2', + phase: 'intuition', + explanation: + 'Key trick: Push right child BEFORE left child. Since stack is LIFO, left child will be popped (and processed) first!', + dataState: { + arrays: [createOutputState([4, null, null, null, null, null, null], 0)], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visiting', [NODE_6]: 'visiting' })], + stacks: [createStackState([6, 2], true)], + }, + }, + { + id: 'intuition-3', + phase: 'intuition', + explanation: + 'The stack remembers where to backtrack. When we finish the left subtree, the right subtree is waiting on the stack.', + dataState: { + arrays: [createOutputState([4, 2, null, null, null, null, null])], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_1]: 'visiting', [NODE_3]: 'visiting', [NODE_6]: 'visiting' })], + stacks: [createStackState([6, 3, 1], true)], + }, + }, + + // ========================================== + // Phase 3: Pattern (2 steps) + // ========================================== + { + id: 'pattern-1', + phase: 'pattern', + explanation: + 'DFS pattern: (1) Initialize stack with root, (2) While stack not empty: pop, process, push children (right then left).', + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({})], + stacks: [createStackState([])], + }, + }, + { + id: 'pattern-2', + phase: 'pattern', + explanation: + 'The stack ensures depth-first order: we completely explore one subtree before moving to its sibling.', + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [ + { id: 'stack', name: 'stack', value: 'LIFO' }, + { id: 'result', name: 'result', value: '[]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'current' })], + stacks: [createStackState([4], true)], + }, + }, + + // ========================================== + // Phase 4: Code (3 steps) + // ========================================== + { + id: 'code-1', + phase: 'code', + explanation: + 'Initialize: empty result list, stack containing just the root node.', + codeLine: 5, + codeHighlightLines: [5, 6, 7], + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [ + { id: 'result', name: 'result', value: '[]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'current' })], + stacks: [createStackState([4], true)], + }, + }, + { + id: 'code-2', + phase: 'code', + explanation: + 'Main loop: While stack is not empty, pop the top node and add its value to result.', + codeLine: 10, + codeHighlightLines: [9, 10, 11, 12], + dataState: { + arrays: [createOutputState([4, null, null, null, null, null, null], 0)], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited' })], + stacks: [createStackState([])], + }, + }, + { + id: 'code-3', + phase: 'code', + explanation: + 'Push right child first, then left child. LIFO means left will be popped first, ensuring we go left before right.', + codeLine: 15, + codeHighlightLines: [14, 15, 16, 17, 18], + dataState: { + arrays: [createOutputState([4, null, null, null, null, null, null])], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visiting', [NODE_6]: 'visiting' })], + stacks: [createStackState([6, 2], true)], + }, + }, + + // ========================================== + // Phase 5: Execution (10 steps) + // ========================================== + { + id: 'exec-1', + phase: 'execution', + explanation: + 'Start: stack = [4], result = []. Pop 4, add to result. Push right (6) then left (2).', + codeLine: 12, + dataState: { + arrays: [createOutputState([4, null, null, null, null, null, null], 0)], + pointers: [], + variables: [ + { id: 'node', name: 'node', value: 4 }, + { id: 'result', name: 'result', value: '[4]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visiting', [NODE_6]: 'visiting' })], + stacks: [createStackState([6, 2], true)], + }, + }, + { + id: 'exec-2', + phase: 'execution', + explanation: + 'Stack: [6, 2]. Pop 2 (top). Result: [4, 2]. Push right (3) then left (1). Stack: [6, 3, 1].', + codeLine: 12, + dataState: { + arrays: [createOutputState([4, 2, null, null, null, null, null], 1)], + pointers: [], + variables: [ + { id: 'node', name: 'node', value: 2, previousValue: 4 }, + { id: 'result', name: 'result', value: '[4, 2]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_1]: 'visiting', [NODE_3]: 'visiting', [NODE_6]: 'visiting' })], + stacks: [createStackState([6, 3, 1], true)], + }, + }, + { + id: 'exec-3', + phase: 'execution', + explanation: + 'Stack: [6, 3, 1]. Pop 1 (top). Result: [4, 2, 1]. Node 1 has no children. Stack: [6, 3].', + codeLine: 12, + dataState: { + arrays: [createOutputState([4, 2, 1, null, null, null, null], 2)], + pointers: [], + variables: [ + { id: 'node', name: 'node', value: 1, previousValue: 2 }, + { id: 'result', name: 'result', value: '[4, 2, 1]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_1]: 'visited', [NODE_3]: 'visiting', [NODE_6]: 'visiting' })], + stacks: [createStackState([6, 3], true)], + }, + }, + { + id: 'exec-4', + phase: 'execution', + explanation: + 'Stack: [6, 3]. Pop 3 (top). Result: [4, 2, 1, 3]. Node 3 has no children. Stack: [6]. Left subtree done!', + codeLine: 12, + dataState: { + arrays: [createOutputState([4, 2, 1, 3, null, null, null], 3)], + pointers: [], + variables: [ + { id: 'node', name: 'node', value: 3, previousValue: 1 }, + { id: 'result', name: 'result', value: '[4, 2, 1, 3]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_1]: 'visited', [NODE_3]: 'visited', [NODE_6]: 'visiting' })], + stacks: [createStackState([6], true)], + }, + }, + { + id: 'exec-5', + phase: 'execution', + explanation: + 'Stack: [6]. Pop 6 (top). Result: [4, 2, 1, 3, 6]. Push right (7) then left (5). Now exploring right subtree!', + codeLine: 12, + dataState: { + arrays: [createOutputState([4, 2, 1, 3, 6, null, null], 4)], + pointers: [], + variables: [ + { id: 'node', name: 'node', value: 6, previousValue: 3 }, + { id: 'result', name: 'result', value: '[4, 2, 1, 3, 6]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_1]: 'visited', [NODE_3]: 'visited', [NODE_6]: 'visited', [NODE_5]: 'visiting', [NODE_7]: 'visiting' })], + stacks: [createStackState([7, 5], true)], + }, + }, + { + id: 'exec-6', + phase: 'execution', + explanation: + 'Stack: [7, 5]. Pop 5 (top). Result: [4, 2, 1, 3, 6, 5]. Node 5 has no children. Stack: [7].', + codeLine: 12, + dataState: { + arrays: [createOutputState([4, 2, 1, 3, 6, 5, null], 5)], + pointers: [], + variables: [ + { id: 'node', name: 'node', value: 5, previousValue: 6 }, + { id: 'result', name: 'result', value: '[4, 2, 1, 3, 6, 5]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_1]: 'visited', [NODE_3]: 'visited', [NODE_6]: 'visited', [NODE_5]: 'visited', [NODE_7]: 'visiting' })], + stacks: [createStackState([7], true)], + }, + }, + { + id: 'exec-7', + phase: 'execution', + explanation: + 'Stack: [7]. Pop 7 (top). Result: [4, 2, 1, 3, 6, 5, 7]. Node 7 has no children. Stack: [].', + codeLine: 12, + dataState: { + arrays: [createOutputState([4, 2, 1, 3, 6, 5, 7], 6)], + pointers: [], + variables: [ + { id: 'node', name: 'node', value: 7, previousValue: 5 }, + { id: 'result', name: 'result', value: '[4, 2, 1, 3, 6, 5, 7]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_1]: 'visited', [NODE_3]: 'visited', [NODE_6]: 'visited', [NODE_5]: 'visited', [NODE_7]: 'visited' })], + stacks: [createStackState([])], + }, + }, + { + id: 'exec-8', + phase: 'execution', + explanation: + 'Stack is empty! All nodes have been visited. Final result: [4, 2, 1, 3, 6, 5, 7] - preorder DFS traversal.', + codeLine: 20, + dataState: { + arrays: [createOutputState([4, 2, 1, 3, 6, 5, 7])], + pointers: [], + variables: [ + { id: 'result', name: 'result', value: '[4, 2, 1, 3, 6, 5, 7]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_1]: 'visited', [NODE_3]: 'visited', [NODE_6]: 'visited', [NODE_5]: 'visited', [NODE_7]: 'visited' })], + stacks: [createStackState([])], + }, + }, + ], +}; diff --git a/frontend/src/content/algorithms/tree-traversal.ts b/frontend/src/content/algorithms/tree-traversal.ts new file mode 100644 index 0000000..8193a96 --- /dev/null +++ b/frontend/src/content/algorithms/tree-traversal.ts @@ -0,0 +1,557 @@ +import type { AlgorithmDefinition } from '@/lib/visualizations/types'; + +/** + * Tree structure (level order: [4, 2, 6, 1, 3, 5, 7]): + * + * 4 (root) + * / \ + * 2 6 + * / \ / \ + * 1 3 5 7 + * + * Inorder traversal output: [1, 2, 3, 4, 5, 6, 7] + */ + +// Node IDs for our tree +const NODE_4 = 'n4'; +const NODE_2 = 'n2'; +const NODE_6 = 'n6'; +const NODE_1 = 'n1'; +const NODE_3 = 'n3'; +const NODE_5 = 'n5'; +const NODE_7 = 'n7'; + +// Helper to create tree state with specific node states +function createTreeState(nodeStates: Record) { + return { + id: 'tree', + label: 'Binary Search Tree', + rootId: NODE_4, + nodes: [ + { id: NODE_4, value: 4, state: nodeStates[NODE_4] ?? 'normal', left: NODE_2, right: NODE_6 }, + { id: NODE_2, value: 2, state: nodeStates[NODE_2] ?? 'normal', left: NODE_1, right: NODE_3 }, + { id: NODE_6, value: 6, state: nodeStates[NODE_6] ?? 'normal', left: NODE_5, right: NODE_7 }, + { id: NODE_1, value: 1, state: nodeStates[NODE_1] ?? 'normal', left: null, right: null }, + { id: NODE_3, value: 3, state: nodeStates[NODE_3] ?? 'normal', left: null, right: null }, + { id: NODE_5, value: 5, state: nodeStates[NODE_5] ?? 'normal', left: null, right: null }, + { id: NODE_7, value: 7, state: nodeStates[NODE_7] ?? 'normal', left: null, right: null }, + ], + }; +} + +// Helper to create stack state +function createStackState(values: number[], highlightTop = false) { + return { + id: 'traversal-stack', + label: 'Traversal Stack', + elements: values.map((v, i) => ({ + id: `stack-${i}`, + value: v, + state: (highlightTop && i === values.length - 1 ? 'highlighted' : 'normal') as 'normal' | 'highlighted', + })), + }; +} + +// Helper to create output array state +function createOutputState(values: (number | null)[], highlightIndex?: number) { + const maxLen = 7; + const elements = []; + for (let i = 0; i < maxLen; i++) { + const val = values[i]; + elements.push({ + value: val ?? 0, + index: i, + state: (val === null ? 'dimmed' : i === highlightIndex ? 'highlighted' : 'success') as 'normal' | 'highlighted' | 'dimmed' | 'success' | 'comparing', + }); + } + return { + id: 'output', + label: 'Output Array', + elements, + }; +} + +export const treeTraversalAlgorithm: AlgorithmDefinition = { + id: 'tree-traversal', + title: 'Inorder Tree Traversal (Iterative)', + slug: 'tree-traversal', + pattern: { + name: 'Tree Traversal', + description: + 'Use a stack to simulate recursion and traverse a binary tree in inorder (Left-Root-Right) order.', + }, + problemStatement: + 'Given a binary search tree, traverse it in inorder (Left-Root-Right) to produce a sorted output. Use an iterative approach with an explicit stack.', + intuition: + 'Recursion implicitly uses a call stack. We can make this explicit: go left as far as possible while pushing nodes, then pop to process and move right. For a BST, this produces sorted order.', + code: { + language: 'python', + code: `def inorder_traversal(root): + result = [] + stack = [] + curr = root + + while curr or stack: + # Go left as far as possible + while curr: + stack.append(curr) + curr = curr.left + + # Pop and process + curr = stack.pop() + result.append(curr.val) + + # Move to right subtree + curr = curr.right + + return result`, + }, + initialExample: { + input: { tree: [4, 2, 6, 1, 3, 5, 7] }, + expected: [1, 2, 3, 4, 5, 6, 7], + }, + steps: [ + // ========================================== + // Phase 1: Problem (2 steps) + // ========================================== + { + id: 'problem-1', + phase: 'problem', + explanation: + 'We have a binary search tree. Our goal is to visit all nodes in inorder (Left-Root-Right), which for a BST gives us sorted output.', + dataState: { + arrays: [], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({})], + stacks: [createStackState([])], + }, + }, + { + id: 'problem-2', + phase: 'problem', + explanation: + 'For this BST: visit left subtree (1, 2, 3), then root (4), then right subtree (5, 6, 7). Expected output: [1, 2, 3, 4, 5, 6, 7].', + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'highlighted' })], + stacks: [createStackState([])], + }, + }, + + // ========================================== + // Phase 2: Intuition (3 steps) + // ========================================== + { + id: 'intuition-1', + phase: 'intuition', + explanation: + 'Recursion uses an implicit call stack. We can make this explicit: maintain our own stack to track nodes waiting to be processed.', + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({})], + stacks: [createStackState([])], + }, + }, + { + id: 'intuition-2', + phase: 'intuition', + explanation: + 'Pattern: Starting from any node, go left as far as possible, pushing each node to the stack. When you can\'t go left, pop and process, then move right.', + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'current', [NODE_2]: 'visiting', [NODE_1]: 'visiting' })], + stacks: [createStackState([])], + }, + }, + { + id: 'intuition-3', + phase: 'intuition', + explanation: + 'The stack remembers ancestors waiting for their right subtrees to be processed. This mimics how recursion unwinds.', + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visiting', [NODE_2]: 'visiting', [NODE_1]: 'current' })], + stacks: [createStackState([4, 2], true)], + }, + }, + + // ========================================== + // Phase 3: Pattern (2 steps) + // ========================================== + { + id: 'pattern-1', + phase: 'pattern', + explanation: + 'The iterative pattern has three repeating phases: (1) Go left and push, (2) Pop and process, (3) Move right.', + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({})], + stacks: [createStackState([])], + }, + }, + { + id: 'pattern-2', + phase: 'pattern', + explanation: + 'We continue while either curr points to a node OR the stack is non-empty. Both conditions must be false to terminate.', + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [ + { id: 'curr', name: 'curr', value: 'node' }, + { id: 'stack', name: 'stack', value: '[]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'current' })], + stacks: [createStackState([])], + }, + }, + + // ========================================== + // Phase 4: Code (3 steps) + // ========================================== + { + id: 'code-1', + phase: 'code', + explanation: + 'Initialize: empty result array, empty stack, and curr pointing to root.', + codeLine: 2, + codeHighlightLines: [2, 3, 4], + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [ + { id: 'curr', name: 'curr', value: 'root (4)' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'current' })], + stacks: [createStackState([])], + }, + }, + { + id: 'code-2', + phase: 'code', + explanation: + 'Inner while loop: Go left as far as possible, pushing each node. This processes the left subtree first.', + codeLine: 8, + codeHighlightLines: [8, 9, 10], + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visiting', [NODE_2]: 'visiting', [NODE_1]: 'current' })], + stacks: [createStackState([4, 2])], + }, + }, + { + id: 'code-3', + phase: 'code', + explanation: + 'After going left, pop from stack, add value to result, then move to right child.', + codeLine: 13, + codeHighlightLines: [13, 14, 17], + dataState: { + arrays: [createOutputState([1, null, null, null, null, null], 0)], + pointers: [], + variables: [], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visiting', [NODE_2]: 'current', [NODE_1]: 'visited' })], + stacks: [createStackState([4])], + }, + }, + + // ========================================== + // Phase 5: Execution (14 steps) + // ========================================== + { + id: 'exec-1', + phase: 'execution', + explanation: + 'Start: curr = 4 (root), stack = []. The outer loop condition "curr or stack" is true.', + codeLine: 6, + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [ + { id: 'curr', name: 'curr', value: 4 }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'current' })], + stacks: [createStackState([])], + }, + }, + { + id: 'exec-2', + phase: 'execution', + explanation: + 'Go left: Push 4 to stack, move curr to left child (2). Stack: [4]', + codeLine: 9, + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [ + { id: 'curr', name: 'curr', value: 2, previousValue: 4 }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visiting', [NODE_2]: 'current' })], + stacks: [createStackState([4], true)], + }, + }, + { + id: 'exec-3', + phase: 'execution', + explanation: + 'Continue left: Push 2 to stack, move curr to left child (1). Stack: [4, 2]', + codeLine: 9, + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [ + { id: 'curr', name: 'curr', value: 1, previousValue: 2 }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visiting', [NODE_2]: 'visiting', [NODE_1]: 'current' })], + stacks: [createStackState([4, 2], true)], + }, + }, + { + id: 'exec-4', + phase: 'execution', + explanation: + 'Push 1 to stack, move curr to left child (null). Stack: [4, 2, 1]. Can\'t go left anymore.', + codeLine: 9, + dataState: { + arrays: [createOutputState([null, null, null, null, null, null, null])], + pointers: [], + variables: [ + { id: 'curr', name: 'curr', value: 'null', previousValue: 1 }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visiting', [NODE_2]: 'visiting', [NODE_1]: 'visiting' })], + stacks: [createStackState([4, 2, 1], true)], + }, + }, + { + id: 'exec-5', + phase: 'execution', + explanation: + 'Pop 1 from stack, add to result. Output: [1]. Move to right child of 1 (null).', + codeLine: 14, + dataState: { + arrays: [createOutputState([1, null, null, null, null, null, null], 0)], + pointers: [], + variables: [ + { id: 'curr', name: 'curr', value: 'null' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visiting', [NODE_2]: 'visiting', [NODE_1]: 'visited' })], + stacks: [createStackState([4, 2])], + }, + }, + { + id: 'exec-6', + phase: 'execution', + explanation: + 'curr is null but stack is non-empty. Pop 2 from stack, add to result. Output: [1, 2]. Move to right child (3).', + codeLine: 14, + dataState: { + arrays: [createOutputState([1, 2, null, null, null, null, null], 1)], + pointers: [], + variables: [ + { id: 'curr', name: 'curr', value: 3, previousValue: 'null' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visiting', [NODE_2]: 'visited', [NODE_1]: 'visited', [NODE_3]: 'current' })], + stacks: [createStackState([4])], + }, + }, + { + id: 'exec-7', + phase: 'execution', + explanation: + 'At node 3. Go left: Push 3 to stack, move to left child (null). Stack: [4, 3]', + codeLine: 9, + dataState: { + arrays: [createOutputState([1, 2, null, null, null, null, null])], + pointers: [], + variables: [ + { id: 'curr', name: 'curr', value: 'null', previousValue: 3 }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visiting', [NODE_2]: 'visited', [NODE_1]: 'visited', [NODE_3]: 'visiting' })], + stacks: [createStackState([4, 3], true)], + }, + }, + { + id: 'exec-8', + phase: 'execution', + explanation: + 'Pop 3 from stack, add to result. Output: [1, 2, 3]. Move to right child (null).', + codeLine: 14, + dataState: { + arrays: [createOutputState([1, 2, 3, null, null, null, null], 2)], + pointers: [], + variables: [ + { id: 'curr', name: 'curr', value: 'null' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visiting', [NODE_2]: 'visited', [NODE_1]: 'visited', [NODE_3]: 'visited' })], + stacks: [createStackState([4])], + }, + }, + { + id: 'exec-9', + phase: 'execution', + explanation: + 'Pop 4 from stack (the root!), add to result. Output: [1, 2, 3, 4]. Move to right child (6).', + codeLine: 14, + dataState: { + arrays: [createOutputState([1, 2, 3, 4, null, null, null], 3)], + pointers: [], + variables: [ + { id: 'curr', name: 'curr', value: 6, previousValue: 'null' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_1]: 'visited', [NODE_3]: 'visited', [NODE_6]: 'current' })], + stacks: [createStackState([])], + }, + }, + { + id: 'exec-10', + phase: 'execution', + explanation: + 'At node 6. Go left: Push 6 to stack, move to left child (5). Stack: [6]', + codeLine: 9, + dataState: { + arrays: [createOutputState([1, 2, 3, 4, null, null, null])], + pointers: [], + variables: [ + { id: 'curr', name: 'curr', value: 5, previousValue: 6 }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_1]: 'visited', [NODE_3]: 'visited', [NODE_6]: 'visiting', [NODE_5]: 'current' })], + stacks: [createStackState([6], true)], + }, + }, + { + id: 'exec-11', + phase: 'execution', + explanation: + 'At node 5. Go left: Push 5 to stack, move to left child (null). Stack: [6, 5]', + codeLine: 9, + dataState: { + arrays: [createOutputState([1, 2, 3, 4, null, null, null])], + pointers: [], + variables: [ + { id: 'curr', name: 'curr', value: 'null', previousValue: 5 }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_1]: 'visited', [NODE_3]: 'visited', [NODE_6]: 'visiting', [NODE_5]: 'visiting' })], + stacks: [createStackState([6, 5], true)], + }, + }, + { + id: 'exec-12', + phase: 'execution', + explanation: + 'Pop 5 from stack, add to result. Output: [1, 2, 3, 4, 5]. Move to right child (null).', + codeLine: 14, + dataState: { + arrays: [createOutputState([1, 2, 3, 4, 5, null, null], 4)], + pointers: [], + variables: [ + { id: 'curr', name: 'curr', value: 'null' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_1]: 'visited', [NODE_3]: 'visited', [NODE_6]: 'visiting', [NODE_5]: 'visited' })], + stacks: [createStackState([6])], + }, + }, + { + id: 'exec-13', + phase: 'execution', + explanation: + 'Pop 6 from stack, add to result. Output: [1, 2, 3, 4, 5, 6]. Move to right child (7).', + codeLine: 14, + dataState: { + arrays: [createOutputState([1, 2, 3, 4, 5, 6, null], 5)], + pointers: [], + variables: [ + { id: 'curr', name: 'curr', value: 7, previousValue: 'null' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_1]: 'visited', [NODE_3]: 'visited', [NODE_6]: 'visited', [NODE_5]: 'visited', [NODE_7]: 'current' })], + stacks: [createStackState([])], + }, + }, + { + id: 'exec-14', + phase: 'execution', + explanation: + 'At node 7. Go left: Push 7 to stack, move to left child (null). Stack: [7]', + codeLine: 9, + dataState: { + arrays: [createOutputState([1, 2, 3, 4, 5, 6, null])], + pointers: [], + variables: [ + { id: 'curr', name: 'curr', value: 'null', previousValue: 7 }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_1]: 'visited', [NODE_3]: 'visited', [NODE_6]: 'visited', [NODE_5]: 'visited', [NODE_7]: 'visiting' })], + stacks: [createStackState([7], true)], + }, + }, + { + id: 'exec-15', + phase: 'execution', + explanation: + 'Pop 7 from stack, add to result. Output: [1, 2, 3, 4, 5, 6, 7]. Move to right child (null).', + codeLine: 14, + dataState: { + arrays: [createOutputState([1, 2, 3, 4, 5, 6, 7], 6)], + pointers: [], + variables: [ + { id: 'curr', name: 'curr', value: 'null' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_1]: 'visited', [NODE_3]: 'visited', [NODE_6]: 'visited', [NODE_5]: 'visited', [NODE_7]: 'visited' })], + stacks: [createStackState([])], + }, + }, + { + id: 'exec-16', + phase: 'execution', + explanation: + 'Done! curr is null AND stack is empty. Final output: [1, 2, 3, 4, 5, 6, 7] - the BST values in sorted order.', + codeLine: 19, + dataState: { + arrays: [createOutputState([1, 2, 3, 4, 5, 6, 7])], + pointers: [], + variables: [ + { id: 'result', name: 'result', value: '[1, 2, 3, 4, 5, 6, 7]' }, + ], + calculations: [], + trees: [createTreeState({ [NODE_4]: 'visited', [NODE_2]: 'visited', [NODE_1]: 'visited', [NODE_3]: 'visited', [NODE_6]: 'visited', [NODE_5]: 'visited', [NODE_7]: 'visited' })], + stacks: [createStackState([])], + }, + }, + ], +}; diff --git a/frontend/src/lib/visualizations/types.ts b/frontend/src/lib/visualizations/types.ts index 5639aae..2d114a5 100644 --- a/frontend/src/lib/visualizations/types.ts +++ b/frontend/src/lib/visualizations/types.ts @@ -82,6 +82,10 @@ export interface DataState { linkedListPointers?: LinkedListPointerState[]; // Stack support stacks?: StackState[]; + // Tree support + trees?: BinaryTreeState[]; + // Queue support + queues?: QueueState[]; } /** Single step in the visualization */ @@ -203,3 +207,42 @@ export interface StackState { elements: StackElementState[]; label?: string; } + +// ============================================ +// Binary Tree Types +// ============================================ + +/** State of a binary tree node */ +export interface BinaryTreeNodeState { + id: string; + value: number; + state: 'normal' | 'current' | 'visiting' | 'visited' | 'highlighted'; + left: string | null; + right: string | null; +} + +/** Complete binary tree state */ +export interface BinaryTreeState { + id: string; + nodes: BinaryTreeNodeState[]; + rootId: string; + label?: string; +} + +// ============================================ +// Queue Types +// ============================================ + +/** State of a queue element */ +export interface QueueElementState { + id: string; + value: number | string; + state: 'normal' | 'highlighted' | 'enqueued' | 'dequeued'; +} + +/** Complete queue state */ +export interface QueueState { + id: string; + elements: QueueElementState[]; + label?: string; +}