Skip to content

Commit c238b0b

Browse files
TamimEhsanTamimEhsan
authored andcommitted
feat: Add a Red-Black tree to the Binary Search Tree page
New src/lib/algorithms/redBlack.js: a Red-Black planner with full insert, delete (transplant + two-child successor copy + the four double-black fix-up cases), and search. It runs the standard CLRS algorithm on a mutable working copy (parent pointers + nil sentinel, ids preserved) and emits a setTree snapshot after each rotation / recolor, so every fix-up step animates. The /bst page gains a tree-type dropdown (BST / Red-Black); switching reseeds the tree. tree-node renders red/black fill with the operation highlight as an outer ring and a fill transition for smooth recoloring; tree-canvas passes the node color; useTreeEditor gains reset(); TreeMenu gains an optional mode dropdown. Home card renamed to "Binary Search Trees".
1 parent 11d893b commit c238b0b

7 files changed

Lines changed: 351 additions & 15 deletions

File tree

src/app/bst/page.jsx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,41 @@
22

33
import { useState } from 'react';
44
import Navbar from '@/components/navbar';
5-
import { fromValues, insertActions, deleteActions, searchActions } from '@/lib/algorithms/bst';
5+
import * as bst from '@/lib/algorithms/bst';
6+
import * as redBlack from '@/lib/algorithms/redBlack';
67
import { binaryLayout } from '@/components/tree/layout';
78
import { useTreeEditor } from '@/components/tree/use-tree-editor';
89
import TreeCanvas from '@/components/tree/tree-canvas';
910
import TreeMenu from '@/components/tree/tree-menu';
1011

12+
const MODES = ['Binary Search Tree', 'Red-Black Tree'];
13+
const SEED = [50, 30, 70, 20, 40, 60, 80];
14+
const seedFor = (mode) => (mode === 1 ? redBlack.fromValues(SEED) : bst.fromValues(SEED));
15+
1116
export default function Bst() {
12-
const [initialTree] = useState(() => fromValues([50, 30, 70, 20, 40, 60, 80]));
17+
const [mode, setMode] = useState(0);
18+
const [initialTree] = useState(() => seedFor(0));
1319
const g = useTreeEditor({ initialTree });
20+
const algo = mode === 1 ? redBlack : bst;
21+
22+
const onModeChange = (m) => {
23+
if (g.isRunning) return;
24+
setMode(m);
25+
g.reset(seedFor(m));
26+
};
1427

1528
return (
1629
<div className="flex flex-col h-screen">
1730
<Navbar />
1831
<div className="flex flex-1 overflow-hidden">
1932
<TreeMenu
20-
title="Binary Search Tree"
33+
title={MODES[mode]}
34+
modes={MODES}
35+
onModeChange={onModeChange}
2136
disabled={g.isRunning}
22-
onInsert={(v) => g.run(insertActions(g.getContext().tree, v))}
23-
onDelete={(v) => g.run(deleteActions(g.getContext().tree, v))}
24-
onSearch={(v) => g.run(searchActions(g.getContext().tree, v))}
37+
onInsert={(v) => g.run(algo.insertActions(g.getContext().tree, v))}
38+
onDelete={(v) => g.run(algo.deleteActions(g.getContext().tree, v))}
39+
onSearch={(v) => g.run(algo.searchActions(g.getContext().tree, v))}
2540
onClear={g.clear}
2641
onSpeedChange={g.setSpeed}
2742
/>

src/app/components/algorithm-cards.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ const algorithms = [
3737
image: '/AlgorithmVisualizer/images/network-flow.png?height=200&width=300'
3838
},{
3939
id: 'bst',
40-
title: 'Binary Search Tree',
41-
description: "Insert, delete, and search on a BST with animated tree restructuring",
40+
title: 'Binary Search Trees',
41+
description: "Insert, delete, and search on a plain BST or a Red-Black tree, with animated rotations and recoloring",
4242
image: '/AlgorithmVisualizer/images/bst.png?height=200&width=300'
4343
},
4444
{

src/components/tree/tree-canvas.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ export default function TreeCanvas({ tree, layout, nodeState = {}, edgeState = {
109109
value={n.value}
110110
secondary={labels[n.id]}
111111
state={nodeState[n.id]}
112+
color={n.color}
112113
/>
113114
);
114115
})}

src/components/tree/tree-menu.jsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { useState } from 'react';
22
import { CustomSlider } from '@/components/custom-slider';
3+
import { CustomSelect } from '@/components/custom-select';
34
import { Button } from '@/components/ui/button';
45
import { Plus, Minus, Search, RotateCcw } from 'lucide-react';
56

6-
// Sidebar for tree visualizers: a value field + insert/delete/search actions,
7-
// clear, and a speed slider. Callers pass the op handlers (they get the number).
7+
// Sidebar for tree visualizers: an optional tree-type dropdown, a value field +
8+
// insert/delete/search actions, clear, and a speed slider. Callers pass the op
9+
// handlers (they get the number).
810

9-
export default function TreeMenu({ title, disabled, onInsert, onDelete, onSearch, onClear, onSpeedChange }) {
11+
export default function TreeMenu({ title, disabled, modes, onModeChange, onInsert, onDelete, onSearch, onClear, onSpeedChange }) {
1012
const [value, setValue] = useState('');
1113
const num = () => Number(value);
1214
const valid = value.trim() !== '' && Number.isFinite(num());
@@ -15,6 +17,17 @@ export default function TreeMenu({ title, disabled, onInsert, onDelete, onSearch
1517
<div className="w-64 bg-gray-100 p-4 space-y-6 overflow-auto">
1618
<h2 className="text-lg font-semibold">{title}</h2>
1719

20+
{modes && (
21+
<div className="space-y-3">
22+
<div className="flex items-center gap-2">
23+
<div className="h-px flex-1 bg-gray-300" />
24+
<span className="text-xs font-medium text-gray-500 uppercase tracking-wider">Tree type</span>
25+
<div className="h-px flex-1 bg-gray-300" />
26+
</div>
27+
<CustomSelect title="Type" options={modes} onChange={onModeChange} disabled={disabled} />
28+
</div>
29+
)}
30+
1831
<div className="space-y-3">
1932
<div className="flex items-center gap-2">
2033
<div className="h-px flex-1 bg-gray-300" />

src/components/tree/tree-node.jsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,21 @@ const FILL = {
1111
remove: ['#f43f5e', '#be123c'],
1212
};
1313

14+
// red/black node fills (used when a `color` is given, e.g. Red-Black trees)
15+
const RB_FILL = {
16+
red: ['#dc2626', '#b91c1c'],
17+
black: ['#1f2937', '#0f172a'],
18+
};
19+
// when a node has a fixed red/black fill, the operation highlight is drawn as
20+
// an outer ring instead of recoloring the fill
21+
const RING = { current: '#f59e0b', found: '#10b981', visited: '#94a3b8', remove: '#f43f5e' };
22+
1423
const R = 16;
1524

16-
export default function TreeNode({ x, y, value, secondary, state }) {
25+
export default function TreeNode({ x, y, value, secondary, state, color }) {
1726
if (state === 'hidden') return null;
18-
const [bg, border] = FILL[state] || FILL.normal;
27+
const [bg, border] = color ? RB_FILL[color] || RB_FILL.black : FILL[state] || FILL.normal;
28+
const ring = color ? RING[state] : null; // operation highlight for colored nodes
1929
// shrink the primary label so longer values (e.g. recursion call labels
2030
// like "(4,3)") still fit inside the circle
2131
const len = String(value ?? '').length;
@@ -24,7 +34,8 @@ export default function TreeNode({ x, y, value, secondary, state }) {
2434
return (
2535
<g style={{ transform: `translate(${x}px, ${y}px)` }}>
2636
<animate attributeName="opacity" from="0" to="1" dur="0.4s" begin="0s" />
27-
<circle r={R} fill={bg} stroke={border} strokeWidth="1.5" filter="url(#treeShadow)" />
37+
{ring && <circle r={R + 3} fill="none" stroke={ring} strokeWidth="2" />}
38+
<circle r={R} fill={bg} stroke={border} strokeWidth="1.5" filter="url(#treeShadow)" style={{ transition: 'fill 0.3s' }} />
2839
<text
2940
textAnchor="middle"
3041
dominantBaseline="central"

src/components/tree/use-tree-editor.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@ export function useTreeEditor({ initialTree = null } = {}) {
5757

5858
const getContext = () => ({ tree: treeRef.current });
5959
const clear = () => { if (isRunningRef.current) return; applyTree(null); clearMarks(); };
60+
const reset = (t) => { if (isRunningRef.current) return; applyTree(t); clearMarks(); };
6061
const setSpeed = (s) => { speedRef.current = toDelay(s); };
6162

62-
return { tree, nodeState, edgeState, labels, status, isRunning, run, getContext, clear, setSpeed };
63+
return { tree, nodeState, edgeState, labels, status, isRunning, run, getContext, clear, reset, setSpeed };
6364
}
6465

6566
function sleep(ms) {

0 commit comments

Comments
 (0)