Skip to content

Commit 8c0baef

Browse files
Merge pull request #107 from indrasuthar07/build-tree
feat: build tree from preorder and inorder
2 parents 8e1964e + c9a0c12 commit 8c0baef

File tree

4 files changed

+645
-0
lines changed

4 files changed

+645
-0
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
export function* buildTreeFromPreorderInorder(preorder, inorder) {
2+
const steps = [];
3+
let stepIndex = 0;
4+
5+
function* buildTree(preStart, preEnd, inStart, inEnd, parent = null) {
6+
if (preStart > preEnd || inStart > inEnd) {
7+
yield {
8+
type: "base_case",
9+
message: "Base case: Empty subtree",
10+
preorder: preorder ,inorder: inorder ,preStart, inEnd, inStart, preEnd, root: null, tree: null, parent
11+
};
12+
return null;
13+
}
14+
15+
const rootValue = preorder[preStart];
16+
17+
yield {
18+
type: "select_root",
19+
message: `Selecting root: ${rootValue} (first element in preorder)`,
20+
preorder: preorder ,inorder: inorder ,preStart, inEnd, inStart, preEnd, rootValue, tree: null, parent
21+
};
22+
const rootIndexInInorder = inorder.indexOf(rootValue);
23+
24+
yield {
25+
type: "find_in_inorder",
26+
message: `Finding ${rootValue} in inorder at index ${rootIndexInInorder}`,
27+
preorder: preorder ,inorder: inorder ,preStart, inEnd, inStart, preEnd, rootValue, rootIndexInInorder, tree: null, parent
28+
};
29+
30+
const leftSize = rootIndexInInorder - inStart;
31+
const rightSize = inEnd - rootIndexInInorder;
32+
33+
yield {
34+
type: "calculate_sizes",
35+
message: `Left subtree size: ${leftSize}, Right subtree size: ${rightSize}`,
36+
preorder: preorder,inorder: inorder,
37+
preStart, inEnd, inStart, preEnd, rootValue, rootIndexInInorder, leftSize, rightSize, tree: null, parent
38+
};
39+
40+
const root = {
41+
value: rootValue,
42+
left: null, right: null,
43+
id: `node_${rootValue}_${Date.now()}_${Math.random()}`
44+
};
45+
46+
yield {
47+
type: "create_node",
48+
message: `Creating node with value ${rootValue}`,
49+
preorder: preorder ,inorder: inorder ,preStart, inEnd, inStart, preEnd, rootValue, rootIndexInInorder, leftSize, rightSize, tree: root, parent
50+
};
51+
52+
if (leftSize > 0) {
53+
yield {
54+
type: "build_left",
55+
message: `Building left subtree: preorder[${preStart + 1}..${preStart + leftSize}], inorder[${inStart}..${rootIndexInInorder - 1}]`,
56+
preorder: preorder, inorder: inorder,
57+
preStart, preEnd, inStart, inEnd, root: rootValue,
58+
rootIndexInInorder, leftSize, rightSize, tree: root, parent
59+
};
60+
61+
const leftSubtree = yield* buildTree(
62+
preStart + 1,
63+
preStart + leftSize, inStart,
64+
rootIndexInInorder - 1, root
65+
);
66+
root.left = leftSubtree;
67+
68+
yield {
69+
type: "attach_left",
70+
message: `Attached left subtree to ${rootValue}`,
71+
preorder: preorder, inorder: inorder,
72+
preStart, preEnd, inStart, inEnd,
73+
root: rootValue, tree: root, parent
74+
};
75+
}
76+
if (rightSize > 0) {
77+
yield {
78+
type: "build_right",
79+
message: `Building right subtree: preorder[${preStart + leftSize + 1}..${preEnd}], inorder[${rootIndexInInorder + 1}..${inEnd}]`,
80+
preorder: preorder, inorder: inorder,
81+
preStart, preEnd, inStart, inEnd, root: rootValue,
82+
rootIndexInInorder, leftSize, rightSize, tree: root, parent
83+
};
84+
85+
const rightSubtree = yield* buildTree(
86+
preStart + leftSize + 1, preEnd,
87+
rootIndexInInorder + 1, inEnd, root
88+
);
89+
root.right = rightSubtree;
90+
91+
yield {
92+
type: "attach_right",
93+
message: `Attached right subtree to ${rootValue}`,
94+
preorder: preorder,
95+
inorder: inorder,
96+
preStart, preEnd, inStart, inEnd, root: rootValue, tree: root, parent
97+
};
98+
}
99+
100+
yield {
101+
type: "complete_subtree",
102+
message: `Completed subtree rooted at ${rootValue}`,
103+
preorder: preorder,
104+
inorder: inorder, preStart, preEnd, inStart, inEnd, root: rootValue, tree: root, parent
105+
};
106+
return root;
107+
}
108+
109+
yield {
110+
type: "start",
111+
message: `Starting to build tree from preorder: [${preorder.join(", ")}] and inorder: [${inorder.join(", ")}]`,
112+
preorder: preorder, inorder: inorder, tree: null
113+
};
114+
const root = yield* buildTree(0, preorder.length - 1, 0, inorder.length - 1);
115+
116+
yield {
117+
type: "complete",
118+
message: "Tree construction complete!",
119+
preorder: preorder, inorder: inorder, tree: root
120+
};
121+
return root;
122+
}
123+
export function cloneTree(node) {
124+
if (!node) return null;
125+
return {
126+
value: node.value,
127+
left: cloneTree(node.left),
128+
right: cloneTree(node.right),
129+
id: node.id
130+
};
131+
}
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
import React, { useEffect, useRef, useState, useMemo } from "react";
2+
3+
export default function BuildTreeVisualizer({
4+
tree = null, currentStep = null, preorder = [], inorder = [],
5+
nodeSize = 60, gapY = 100, gapX = 80
6+
}) {
7+
const containerRef = useRef(null);
8+
const [layoutNodes, setLayoutNodes] = useState([]);
9+
const [containerWidth, setContainerWidth] = useState(1000);
10+
const [containerHeight, setContainerHeight] = useState(600);
11+
12+
useEffect(() => {
13+
if (!tree) {
14+
setLayoutNodes([]);
15+
return;
16+
}
17+
const rect = containerRef.current?.getBoundingClientRect();
18+
if (rect) {
19+
setContainerWidth(rect.width || 1000);
20+
setContainerHeight(rect.height || 600);
21+
}
22+
function getTreeHeight(node) {
23+
if (!node) return 0;
24+
return 1 + Math.max(
25+
getTreeHeight(node.left),
26+
getTreeHeight(node.right)
27+
);
28+
}
29+
const treeHeight = getTreeHeight(tree);
30+
setContainerHeight(Math.max(treeHeight * gapY + 200, 400));
31+
const layoutMap = new Map();
32+
const usableWidth = Math.max(containerWidth - 100, 800);
33+
const inorderPositions = new Map();
34+
let inorderIndex = 0;
35+
36+
function assignInorderPositions(node) {
37+
if (!node) return;
38+
assignInorderPositions(node.left);
39+
inorderPositions.set(node, inorderIndex++);
40+
assignInorderPositions(node.right);
41+
}
42+
assignInorderPositions(tree);
43+
44+
const totalNodes = inorderPositions.size;
45+
function assignPositions(node, depth) {
46+
if (!node) return;
47+
48+
const inorderPos = inorderPositions.get(node) ?? 0;
49+
const spacing = usableWidth / (totalNodes + 1);
50+
const x = 50 + spacing * (inorderPos + 1);
51+
const y = depth * gapY + 80;
52+
53+
const nodeId = node.id || `node_${node.value}_${depth}_${inorderPos}`;
54+
layoutMap.set(nodeId, { value: node.value, node, x, y, depth, id: nodeId });
55+
assignPositions(node.left, depth + 1);
56+
assignPositions(node.right, depth + 1);
57+
}
58+
59+
assignPositions(tree, 0);
60+
61+
const layoutArray = Array.from(layoutMap.values());
62+
setLayoutNodes(layoutArray);
63+
}, [tree, containerWidth, gapY, gapX]);
64+
65+
useEffect(() => {
66+
const onResize = () => {
67+
const rect = containerRef.current?.getBoundingClientRect();
68+
if (rect) {
69+
setContainerWidth(rect.width || 1000);
70+
}
71+
};
72+
window.addEventListener("resize", onResize);
73+
onResize();
74+
return () => window.removeEventListener("resize", onResize);
75+
}, []);
76+
77+
const edges = useMemo(() => {
78+
if (!tree || !layoutNodes.length) return [];
79+
const edgesArray = [];
80+
const nodeMap = new Map(layoutNodes.map(n => [n.node, n]));
81+
82+
function buildEdges(node) {
83+
if (!node) return;
84+
const fromNode = nodeMap.get(node);
85+
86+
if (!fromNode) return;
87+
if (node.left) {
88+
const toNode = nodeMap.get(node.left);
89+
if (toNode) {
90+
edgesArray.push({ from: fromNode, to: toNode, side: "left" });
91+
}
92+
buildEdges(node.left);
93+
}
94+
if (node.right) {
95+
const toNode = nodeMap.get(node.right);
96+
if (toNode) {
97+
edgesArray.push({ from: fromNode, to: toNode, side: "right" });
98+
}
99+
buildEdges(node.right);
100+
}
101+
}
102+
buildEdges(tree);
103+
return edgesArray;
104+
}, [tree, layoutNodes]);
105+
106+
const getNodeClass = (nodeLayout) => {
107+
if (!currentStep || !currentStep.tree) {
108+
return "bg-gray-700 text-white border-2 border-gray-600";
109+
}
110+
111+
const step = currentStep;
112+
const stepType = step.type;
113+
const rootValue = step.root;
114+
const nodeValue = nodeLayout.value;
115+
116+
if (rootValue === nodeValue) {
117+
if (stepType === "select_root" || stepType === "create_node") {
118+
return "bg-blue-500 text-white border-4 border-blue-300 ring-4 ring-blue-400 ring-opacity-70 shadow-xl transform scale-110 z-20";
119+
} else if (stepType === "find_in_inorder" || stepType === "calculate_sizes") {
120+
return "bg-yellow-500 text-white border-4 border-yellow-300 ring-2 ring-yellow-400 ring-opacity-60 z-10";
121+
} else if (stepType === "attach_left" || stepType === "attach_right") {
122+
return "bg-green-500 text-white border-4 border-green-300 ring-2 ring-green-400 ring-opacity-60 z-10";
123+
} else if (stepType === "complete_subtree") {
124+
return "bg-emerald-500 text-white border-4 border-emerald-300 ring-2 ring-emerald-400 ring-opacity-60 z-10";
125+
}
126+
}
127+
if (step.tree) {
128+
const isInTree = (node, targetTree) => {
129+
if (!targetTree) return false;
130+
if (targetTree.value === node.value) return true;
131+
return isInTree(node, targetTree.left) || isInTree(node, targetTree.right);
132+
};
133+
134+
if (isInTree(nodeLayout.node, step.tree) && rootValue !== nodeValue) {
135+
return "bg-indigo-600 text-white border-2 border-indigo-400 opacity-90";
136+
}
137+
}
138+
return "bg-gray-700 text-white border-2 border-gray-600";
139+
};
140+
141+
const Node = ({ n }) => {
142+
const cls = getNodeClass(n);
143+
const radius = nodeSize / 2;
144+
145+
return (
146+
<div
147+
key={`${n.value}-${n.x}-${n.y}`}
148+
className={`${cls} flex items-center justify-center rounded-full shadow-lg font-bold transition-all duration-500 ease-in-out cursor-default`}
149+
style={{
150+
position: "absolute",
151+
left: `${n.x - radius}px`,
152+
top: `${n.y - radius}px`,
153+
width: `${nodeSize}px`,
154+
height: `${nodeSize}px`,
155+
zIndex: 10,
156+
fontWeight: 700
157+
}}
158+
>
159+
<span className="text-base select-none drop-shadow-md">{String(n.value)}</span>
160+
</div>
161+
);
162+
};
163+
164+
const SvgEdges = () => {
165+
if (!layoutNodes.length) return null;
166+
return (
167+
<svg
168+
className="absolute inset-0 pointer-events-none"
169+
width={containerWidth}
170+
height={containerHeight}
171+
style={{ zIndex: 1 }}
172+
>
173+
<defs>
174+
<linearGradient id="edge-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
175+
<stop offset="0%" stopColor="#60a5fa" stopOpacity="0.6" />
176+
<stop offset="100%" stopColor="#8b5cf6" stopOpacity="0.6" />
177+
</linearGradient>
178+
</defs>
179+
{edges.map((e, i) => {
180+
const x1 = e.from.x;
181+
const y1 = e.from.y + nodeSize * 0.45;
182+
const x2 = e.to.x;
183+
const y2 = e.to.y - nodeSize * 0.45;
184+
const midX = (x1 + x2) / 2;
185+
const controlY = Math.min(y1, y2) - 30;
186+
const d = `M ${x1} ${y1} Q ${midX} ${controlY} ${x2} ${y2}`;
187+
188+
return (
189+
<path
190+
key={`${e.from.value}-${e.to.value}-${i}`}
191+
d={d}
192+
fill="none"
193+
stroke="url(#edge-gradient)"
194+
strokeWidth={2.5}
195+
strokeDasharray="5,5"
196+
style={{ transition: "all 0.6s ease" }}
197+
/>
198+
);
199+
})}
200+
</svg>
201+
);
202+
};
203+
if (!tree) {
204+
return (
205+
<div className="flex items-center justify-center h-64 bg-gray-900/30 rounded-xl border-2 border-gray-700">
206+
<div className="text-center">
207+
<div className="text-4xl mb-3">🌳</div>
208+
<div className="text-gray-400 font-medium">No tree to visualize</div>
209+
</div>
210+
</div>
211+
);
212+
}
213+
return (
214+
<div className="w-full py-6">
215+
<div
216+
ref={containerRef}
217+
className="relative w-full bg-gradient-to-br from-gray-900/80 to-gray-800/60 rounded-xl border-2 border-gray-700/80 overflow-hidden shadow-2xl backdrop-blur-sm mx-auto"
218+
style={{ minHeight: `${containerHeight}px` }}
219+
>
220+
<SvgEdges />
221+
{layoutNodes.map((n) => (
222+
<Node key={n.id || `node-${n.value}-${n.x}-${n.y}`} n={n} />
223+
))}
224+
</div>
225+
</div>
226+
);
227+
}
228+

0 commit comments

Comments
 (0)