feat(viz): tree/BFS/DFS patterns
This commit is contained in:
@@ -14,7 +14,7 @@ import {
|
|||||||
RelatedPatterns,
|
RelatedPatterns,
|
||||||
} from "@/components/patterns";
|
} from "@/components/patterns";
|
||||||
import { PatternVisualization } from "@/components/visualization";
|
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 { twoSumAlgorithm } from "@/content/algorithms/two-sum";
|
||||||
import { slidingWindowAlgorithm } from "@/content/algorithms/sliding-window";
|
import { slidingWindowAlgorithm } from "@/content/algorithms/sliding-window";
|
||||||
import { binarySearchAlgorithm } from "@/content/algorithms/binary-search";
|
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 { fastSlowPointersAlgorithm } from "@/content/algorithms/fast-slow-pointers";
|
||||||
import { linkedListReversalAlgorithm } from "@/content/algorithms/linkedlist-reversal";
|
import { linkedListReversalAlgorithm } from "@/content/algorithms/linkedlist-reversal";
|
||||||
import { monotonicStackAlgorithm } from "@/content/algorithms/monotonic-stack";
|
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 {
|
interface PageProps {
|
||||||
params: Promise<{ slug: string }>;
|
params: Promise<{ slug: string }>;
|
||||||
@@ -128,6 +131,12 @@ export default async function PatternDetailPage({ params }: PageProps) {
|
|||||||
<LinkedListVisualization algorithm={linkedListReversalAlgorithm} />
|
<LinkedListVisualization algorithm={linkedListReversalAlgorithm} />
|
||||||
) : slug === "monotonic-stack" ? (
|
) : slug === "monotonic-stack" ? (
|
||||||
<MonotonicStackVisualization algorithm={monotonicStackAlgorithm} />
|
<MonotonicStackVisualization algorithm={monotonicStackAlgorithm} />
|
||||||
|
) : slug === "tree-traversal" ? (
|
||||||
|
<TreeTraversalVisualization algorithm={treeTraversalAlgorithm} />
|
||||||
|
) : slug === "bfs" ? (
|
||||||
|
<BFSVisualization algorithm={bfsAlgorithm} />
|
||||||
|
) : slug === "dfs" ? (
|
||||||
|
<DFSVisualization algorithm={dfsAlgorithm} />
|
||||||
) : pattern.visualization_examples && pattern.visualization_examples.length > 0 ? (
|
) : pattern.visualization_examples && pattern.visualization_examples.length > 0 ? (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<VisualizationContainer
|
||||||
|
title={algorithm.title}
|
||||||
|
pattern={algorithm.pattern}
|
||||||
|
code={algorithm.code}
|
||||||
|
currentLine={currentStep.codeLine}
|
||||||
|
highlightLines={currentStep.codeHighlightLines}
|
||||||
|
explanation={currentStep.explanation}
|
||||||
|
decision={currentStep.decision}
|
||||||
|
phase={currentPhase}
|
||||||
|
variables={dataState.variables}
|
||||||
|
currentStepIndex={currentStepIndex}
|
||||||
|
totalSteps={totalSteps}
|
||||||
|
isPlaying={playback.isPlaying}
|
||||||
|
speed={playback.speed}
|
||||||
|
controls={controls}
|
||||||
|
progress={progress}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
{/* Top: Binary tree */}
|
||||||
|
{tree && (
|
||||||
|
<BinaryTreeView
|
||||||
|
tree={tree}
|
||||||
|
currentNodeId={currentNodeId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Bottom: Queue + Output */}
|
||||||
|
<div className="flex flex-col items-center gap-4">
|
||||||
|
{/* BFS Queue */}
|
||||||
|
{bfsQueue && (
|
||||||
|
<QueueView queue={bfsQueue} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Output array */}
|
||||||
|
{outputArray && (
|
||||||
|
<ArrayView
|
||||||
|
array={outputArray}
|
||||||
|
pointers={dataState.pointers}
|
||||||
|
elementSize="sm"
|
||||||
|
showIndices={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</VisualizationContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<VisualizationContainer
|
||||||
|
title={algorithm.title}
|
||||||
|
pattern={algorithm.pattern}
|
||||||
|
code={algorithm.code}
|
||||||
|
currentLine={currentStep.codeLine}
|
||||||
|
highlightLines={currentStep.codeHighlightLines}
|
||||||
|
explanation={currentStep.explanation}
|
||||||
|
decision={currentStep.decision}
|
||||||
|
phase={currentPhase}
|
||||||
|
variables={dataState.variables}
|
||||||
|
currentStepIndex={currentStepIndex}
|
||||||
|
totalSteps={totalSteps}
|
||||||
|
isPlaying={playback.isPlaying}
|
||||||
|
speed={playback.speed}
|
||||||
|
controls={controls}
|
||||||
|
progress={progress}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-8">
|
||||||
|
{/* Left: Binary tree */}
|
||||||
|
{tree && (
|
||||||
|
<BinaryTreeView
|
||||||
|
tree={tree}
|
||||||
|
currentNodeId={currentNodeId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Right side: Stack + Output */}
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
{/* DFS Stack */}
|
||||||
|
{dfsStack && (
|
||||||
|
<StackView stack={dfsStack} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Output array */}
|
||||||
|
{outputArray && (
|
||||||
|
<ArrayView
|
||||||
|
array={outputArray}
|
||||||
|
pointers={dataState.pointers}
|
||||||
|
elementSize="sm"
|
||||||
|
showIndices={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</VisualizationContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<VisualizationContainer
|
||||||
|
title={algorithm.title}
|
||||||
|
pattern={algorithm.pattern}
|
||||||
|
code={algorithm.code}
|
||||||
|
currentLine={currentStep.codeLine}
|
||||||
|
highlightLines={currentStep.codeHighlightLines}
|
||||||
|
explanation={currentStep.explanation}
|
||||||
|
decision={currentStep.decision}
|
||||||
|
phase={currentPhase}
|
||||||
|
variables={dataState.variables}
|
||||||
|
currentStepIndex={currentStepIndex}
|
||||||
|
totalSteps={totalSteps}
|
||||||
|
isPlaying={playback.isPlaying}
|
||||||
|
speed={playback.speed}
|
||||||
|
controls={controls}
|
||||||
|
progress={progress}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-8">
|
||||||
|
{/* Left: Binary tree */}
|
||||||
|
{tree && (
|
||||||
|
<BinaryTreeView
|
||||||
|
tree={tree}
|
||||||
|
currentNodeId={currentNodeId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Right side: Stack + Output */}
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
{/* Traversal stack */}
|
||||||
|
{traversalStack && (
|
||||||
|
<StackView stack={traversalStack} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Output array */}
|
||||||
|
{outputArray && (
|
||||||
|
<ArrayView
|
||||||
|
array={outputArray}
|
||||||
|
pointers={dataState.pointers}
|
||||||
|
elementSize="sm"
|
||||||
|
showIndices={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</VisualizationContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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<string, BinaryTreeNodeState> {
|
||||||
|
const map = new Map<string, BinaryTreeNodeState>();
|
||||||
|
for (const node of nodes) {
|
||||||
|
map.set(node.id, node);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateTreeDepth(
|
||||||
|
nodeId: string | null,
|
||||||
|
nodeMap: Map<string, BinaryTreeNodeState>
|
||||||
|
): 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 (
|
||||||
|
<div className={cn('flex flex-col items-center', className)}>
|
||||||
|
{tree.label && (
|
||||||
|
<span className="mb-2 text-sm font-medium text-[var(--muted-foreground)]">
|
||||||
|
{tree.label}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<svg
|
||||||
|
width={svgWidth}
|
||||||
|
height={svgHeight}
|
||||||
|
viewBox={`0 0 ${svgWidth} ${svgHeight}`}
|
||||||
|
className="overflow-visible"
|
||||||
|
>
|
||||||
|
{/* Draw edges first (behind nodes) */}
|
||||||
|
{positions.map(({ node, x, y, parentX, parentY }) =>
|
||||||
|
parentX !== undefined && parentY !== undefined ? (
|
||||||
|
<motion.line
|
||||||
|
key={`edge-${node.id}`}
|
||||||
|
x1={parentX}
|
||||||
|
y1={parentY + NODE_RADIUS}
|
||||||
|
x2={x}
|
||||||
|
y2={y - NODE_RADIUS}
|
||||||
|
className="stroke-[var(--border)]"
|
||||||
|
strokeWidth={2}
|
||||||
|
initial={false}
|
||||||
|
animate={{
|
||||||
|
opacity: node.state === 'visited' ? 0.5 : 1,
|
||||||
|
}}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Draw nodes */}
|
||||||
|
{positions.map(({ node, x, y }) => (
|
||||||
|
<TreeNode
|
||||||
|
key={node.id}
|
||||||
|
node={node}
|
||||||
|
x={x}
|
||||||
|
y={y}
|
||||||
|
radius={NODE_RADIUS}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* 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 (
|
||||||
|
<g key={`current-indicator-${currentNodeId}`}>
|
||||||
|
<motion.polygon
|
||||||
|
points={`${x},${arrowY} ${x - 6},${arrowY + 10} ${x + 6},${arrowY + 10}`}
|
||||||
|
className="fill-[var(--primary)]"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
|
/>
|
||||||
|
<motion.text
|
||||||
|
x={x}
|
||||||
|
y={arrowY + 24}
|
||||||
|
textAnchor="middle"
|
||||||
|
className="fill-[var(--primary)] text-xs font-medium"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
|
>
|
||||||
|
curr
|
||||||
|
</motion.text>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<div className={cn('flex flex-col items-center', className)}>
|
||||||
|
{queue.label && (
|
||||||
|
<span className="mb-2 whitespace-nowrap text-sm font-medium text-[var(--muted-foreground)]">
|
||||||
|
{queue.label}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="relative flex min-w-[88px] flex-row items-center gap-1">
|
||||||
|
{/* Empty state */}
|
||||||
|
{!hasElements && (
|
||||||
|
<div className="flex h-10 w-10 items-center justify-center rounded-lg border-2 border-dashed border-[var(--border)] text-xs text-[var(--muted-foreground)]">
|
||||||
|
empty
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Queue elements - horizontal row, front on left, rear on right */}
|
||||||
|
<AnimatePresence mode="popLayout">
|
||||||
|
{queue.elements.map((element, index) => (
|
||||||
|
<QueueElement
|
||||||
|
key={element.id}
|
||||||
|
element={element}
|
||||||
|
isFront={index === 0}
|
||||||
|
isRear={index === queue.elements.length - 1 && queue.elements.length > 1}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Direction indicator */}
|
||||||
|
<div className="mt-4 flex items-center gap-2 text-[10px] text-[var(--muted-foreground)]">
|
||||||
|
<span>dequeue</span>
|
||||||
|
<span>←</span>
|
||||||
|
<span className="w-8 border-t border-dashed border-[var(--border)]" />
|
||||||
|
<span>→</span>
|
||||||
|
<span>enqueue</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -12,14 +12,21 @@ export { CalculationBubble } from "./primitives/calculation-bubble";
|
|||||||
export { LinkedListNode } from "./primitives/linked-list-node";
|
export { LinkedListNode } from "./primitives/linked-list-node";
|
||||||
export { LinkedListPointer } from "./primitives/linked-list-pointer";
|
export { LinkedListPointer } from "./primitives/linked-list-pointer";
|
||||||
export { StackElement } from "./primitives/stack-element";
|
export { StackElement } from "./primitives/stack-element";
|
||||||
|
export { QueueElement } from "./primitives/queue-element";
|
||||||
|
export { TreeNode } from "./primitives/tree-node";
|
||||||
|
|
||||||
// Data structures
|
// Data structures
|
||||||
export { ArrayView } from "./data-structures/array-view";
|
export { ArrayView } from "./data-structures/array-view";
|
||||||
export { LinkedListView } from "./data-structures/linked-list-view";
|
export { LinkedListView } from "./data-structures/linked-list-view";
|
||||||
export { StackView } from "./data-structures/stack-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
|
// Algorithm visualizations
|
||||||
export { MonotonicStackVisualization } from "./algorithms/monotonic-stack";
|
export { MonotonicStackVisualization } from "./algorithms/monotonic-stack";
|
||||||
export { PrefixSumVisualization } from "./algorithms/prefix-sum";
|
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 { TwoPointersVisualization } from "./algorithms/two-pointers";
|
||||||
export { LinkedListVisualization } from "./algorithms/linked-list";
|
export { LinkedListVisualization } from "./algorithms/linked-list";
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<motion.div
|
||||||
|
layout
|
||||||
|
initial={{ opacity: 0, x: 20, scale: 0.8 }}
|
||||||
|
animate={{
|
||||||
|
opacity: element.state === 'dequeued' ? 0 : 1,
|
||||||
|
x: 0,
|
||||||
|
scale: element.state === 'highlighted' ? 1.05 : 1,
|
||||||
|
}}
|
||||||
|
exit={{ opacity: 0, x: -20, scale: 0.8 }}
|
||||||
|
transition={{
|
||||||
|
type: 'spring',
|
||||||
|
stiffness: 300,
|
||||||
|
damping: 30,
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
'relative flex h-10 w-10 items-center justify-center rounded-lg border-2 font-mono font-medium transition-colors duration-200',
|
||||||
|
STATE_CLASSES[element.state],
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{element.value}
|
||||||
|
{isFront && (
|
||||||
|
<span className="absolute -bottom-5 text-[10px] font-medium text-[var(--muted-foreground)]">
|
||||||
|
FRONT
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{isRear && (
|
||||||
|
<span className="absolute -bottom-5 text-[10px] font-medium text-[var(--muted-foreground)]">
|
||||||
|
REAR
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<motion.g
|
||||||
|
initial={false}
|
||||||
|
animate={{
|
||||||
|
scale: isActive ? 1.1 : 1,
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
type: 'spring',
|
||||||
|
stiffness: 300,
|
||||||
|
damping: 30,
|
||||||
|
}}
|
||||||
|
style={{ transformOrigin: `${x}px ${y}px` }}
|
||||||
|
className={cn(className)}
|
||||||
|
>
|
||||||
|
<motion.circle
|
||||||
|
cx={x}
|
||||||
|
cy={y}
|
||||||
|
r={radius}
|
||||||
|
strokeWidth={2}
|
||||||
|
className={cn(
|
||||||
|
'transition-colors duration-200',
|
||||||
|
STATE_CLASSES[node.state]
|
||||||
|
)}
|
||||||
|
initial={false}
|
||||||
|
animate={{
|
||||||
|
filter: isActive ? 'drop-shadow(0 0 8px var(--primary))' : 'none',
|
||||||
|
}}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
|
/>
|
||||||
|
<text
|
||||||
|
x={x}
|
||||||
|
y={y}
|
||||||
|
textAnchor="middle"
|
||||||
|
dominantBaseline="central"
|
||||||
|
className={cn(
|
||||||
|
'pointer-events-none select-none font-mono text-sm font-medium',
|
||||||
|
TEXT_CLASSES[node.state]
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{node.value}
|
||||||
|
</text>
|
||||||
|
</motion.g>
|
||||||
|
);
|
||||||
|
}
|
||||||
445
frontend/src/content/algorithms/bfs.ts
Normal file
445
frontend/src/content/algorithms/bfs.ts
Normal file
@@ -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<string, 'normal' | 'current' | 'visiting' | 'visited' | 'highlighted'>) {
|
||||||
|
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([])],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
428
frontend/src/content/algorithms/dfs.ts
Normal file
428
frontend/src/content/algorithms/dfs.ts
Normal file
@@ -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<string, 'normal' | 'current' | 'visiting' | 'visited' | 'highlighted'>) {
|
||||||
|
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([])],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
557
frontend/src/content/algorithms/tree-traversal.ts
Normal file
557
frontend/src/content/algorithms/tree-traversal.ts
Normal file
@@ -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<string, 'normal' | 'current' | 'visiting' | 'visited' | 'highlighted'>) {
|
||||||
|
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([])],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -82,6 +82,10 @@ export interface DataState {
|
|||||||
linkedListPointers?: LinkedListPointerState[];
|
linkedListPointers?: LinkedListPointerState[];
|
||||||
// Stack support
|
// Stack support
|
||||||
stacks?: StackState[];
|
stacks?: StackState[];
|
||||||
|
// Tree support
|
||||||
|
trees?: BinaryTreeState[];
|
||||||
|
// Queue support
|
||||||
|
queues?: QueueState[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Single step in the visualization */
|
/** Single step in the visualization */
|
||||||
@@ -203,3 +207,42 @@ export interface StackState {
|
|||||||
elements: StackElementState[];
|
elements: StackElementState[];
|
||||||
label?: string;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user