diff --git a/src/algorithms/Tree/buildTreeFromPreorderInorder.js b/src/algorithms/Tree/buildTreeFromPreorderInorder.js new file mode 100644 index 0000000..1b90e66 --- /dev/null +++ b/src/algorithms/Tree/buildTreeFromPreorderInorder.js @@ -0,0 +1,131 @@ +export function* buildTreeFromPreorderInorder(preorder, inorder) { + const steps = []; + let stepIndex = 0; + + function* buildTree(preStart, preEnd, inStart, inEnd, parent = null) { + if (preStart > preEnd || inStart > inEnd) { + yield { + type: "base_case", + message: "Base case: Empty subtree", + preorder: preorder ,inorder: inorder ,preStart, inEnd, inStart, preEnd, root: null, tree: null, parent + }; + return null; + } + + const rootValue = preorder[preStart]; + + yield { + type: "select_root", + message: `Selecting root: ${rootValue} (first element in preorder)`, + preorder: preorder ,inorder: inorder ,preStart, inEnd, inStart, preEnd, rootValue, tree: null, parent + }; + const rootIndexInInorder = inorder.indexOf(rootValue); + + yield { + type: "find_in_inorder", + message: `Finding ${rootValue} in inorder at index ${rootIndexInInorder}`, + preorder: preorder ,inorder: inorder ,preStart, inEnd, inStart, preEnd, rootValue, rootIndexInInorder, tree: null, parent + }; + + const leftSize = rootIndexInInorder - inStart; + const rightSize = inEnd - rootIndexInInorder; + + yield { + type: "calculate_sizes", + message: `Left subtree size: ${leftSize}, Right subtree size: ${rightSize}`, + preorder: preorder,inorder: inorder, + preStart, inEnd, inStart, preEnd, rootValue, rootIndexInInorder, leftSize, rightSize, tree: null, parent + }; + + const root = { + value: rootValue, + left: null, right: null, + id: `node_${rootValue}_${Date.now()}_${Math.random()}` + }; + + yield { + type: "create_node", + message: `Creating node with value ${rootValue}`, + preorder: preorder ,inorder: inorder ,preStart, inEnd, inStart, preEnd, rootValue, rootIndexInInorder, leftSize, rightSize, tree: root, parent + }; + + if (leftSize > 0) { + yield { + type: "build_left", + message: `Building left subtree: preorder[${preStart + 1}..${preStart + leftSize}], inorder[${inStart}..${rootIndexInInorder - 1}]`, + preorder: preorder, inorder: inorder, + preStart, preEnd, inStart, inEnd, root: rootValue, + rootIndexInInorder, leftSize, rightSize, tree: root, parent + }; + + const leftSubtree = yield* buildTree( + preStart + 1, + preStart + leftSize, inStart, + rootIndexInInorder - 1, root + ); + root.left = leftSubtree; + + yield { + type: "attach_left", + message: `Attached left subtree to ${rootValue}`, + preorder: preorder, inorder: inorder, + preStart, preEnd, inStart, inEnd, + root: rootValue, tree: root, parent + }; + } + if (rightSize > 0) { + yield { + type: "build_right", + message: `Building right subtree: preorder[${preStart + leftSize + 1}..${preEnd}], inorder[${rootIndexInInorder + 1}..${inEnd}]`, + preorder: preorder, inorder: inorder, + preStart, preEnd, inStart, inEnd, root: rootValue, + rootIndexInInorder, leftSize, rightSize, tree: root, parent + }; + + const rightSubtree = yield* buildTree( + preStart + leftSize + 1, preEnd, + rootIndexInInorder + 1, inEnd, root + ); + root.right = rightSubtree; + + yield { + type: "attach_right", + message: `Attached right subtree to ${rootValue}`, + preorder: preorder, + inorder: inorder, + preStart, preEnd, inStart, inEnd, root: rootValue, tree: root, parent + }; + } + + yield { + type: "complete_subtree", + message: `Completed subtree rooted at ${rootValue}`, + preorder: preorder, + inorder: inorder, preStart, preEnd, inStart, inEnd, root: rootValue, tree: root, parent + }; + return root; + } + + yield { + type: "start", + message: `Starting to build tree from preorder: [${preorder.join(", ")}] and inorder: [${inorder.join(", ")}]`, + preorder: preorder, inorder: inorder, tree: null + }; + const root = yield* buildTree(0, preorder.length - 1, 0, inorder.length - 1); + + yield { + type: "complete", + message: "Tree construction complete!", + preorder: preorder, inorder: inorder, tree: root + }; + return root; +} +export function cloneTree(node) { + if (!node) return null; + return { + value: node.value, + left: cloneTree(node.left), + right: cloneTree(node.right), + id: node.id + }; +} diff --git a/src/components/Tree/BuildTreeVisualizer.jsx b/src/components/Tree/BuildTreeVisualizer.jsx new file mode 100644 index 0000000..2147d44 --- /dev/null +++ b/src/components/Tree/BuildTreeVisualizer.jsx @@ -0,0 +1,228 @@ +import React, { useEffect, useRef, useState, useMemo } from "react"; + +export default function BuildTreeVisualizer({ + tree = null, currentStep = null, preorder = [], inorder = [], + nodeSize = 60, gapY = 100, gapX = 80 +}) { + const containerRef = useRef(null); + const [layoutNodes, setLayoutNodes] = useState([]); + const [containerWidth, setContainerWidth] = useState(1000); + const [containerHeight, setContainerHeight] = useState(600); + + useEffect(() => { + if (!tree) { + setLayoutNodes([]); + return; + } + const rect = containerRef.current?.getBoundingClientRect(); + if (rect) { + setContainerWidth(rect.width || 1000); + setContainerHeight(rect.height || 600); + } + function getTreeHeight(node) { + if (!node) return 0; + return 1 + Math.max( + getTreeHeight(node.left), + getTreeHeight(node.right) + ); + } + const treeHeight = getTreeHeight(tree); + setContainerHeight(Math.max(treeHeight * gapY + 200, 400)); + const layoutMap = new Map(); + const usableWidth = Math.max(containerWidth - 100, 800); + const inorderPositions = new Map(); + let inorderIndex = 0; + + function assignInorderPositions(node) { + if (!node) return; + assignInorderPositions(node.left); + inorderPositions.set(node, inorderIndex++); + assignInorderPositions(node.right); + } + assignInorderPositions(tree); + + const totalNodes = inorderPositions.size; + function assignPositions(node, depth) { + if (!node) return; + + const inorderPos = inorderPositions.get(node) ?? 0; + const spacing = usableWidth / (totalNodes + 1); + const x = 50 + spacing * (inorderPos + 1); + const y = depth * gapY + 80; + + const nodeId = node.id || `node_${node.value}_${depth}_${inorderPos}`; + layoutMap.set(nodeId, { value: node.value, node, x, y, depth, id: nodeId }); + assignPositions(node.left, depth + 1); + assignPositions(node.right, depth + 1); + } + + assignPositions(tree, 0); + + const layoutArray = Array.from(layoutMap.values()); + setLayoutNodes(layoutArray); + }, [tree, containerWidth, gapY, gapX]); + + useEffect(() => { + const onResize = () => { + const rect = containerRef.current?.getBoundingClientRect(); + if (rect) { + setContainerWidth(rect.width || 1000); + } + }; + window.addEventListener("resize", onResize); + onResize(); + return () => window.removeEventListener("resize", onResize); + }, []); + + const edges = useMemo(() => { + if (!tree || !layoutNodes.length) return []; + const edgesArray = []; + const nodeMap = new Map(layoutNodes.map(n => [n.node, n])); + + function buildEdges(node) { + if (!node) return; + const fromNode = nodeMap.get(node); + + if (!fromNode) return; + if (node.left) { + const toNode = nodeMap.get(node.left); + if (toNode) { + edgesArray.push({ from: fromNode, to: toNode, side: "left" }); + } + buildEdges(node.left); + } + if (node.right) { + const toNode = nodeMap.get(node.right); + if (toNode) { + edgesArray.push({ from: fromNode, to: toNode, side: "right" }); + } + buildEdges(node.right); + } + } + buildEdges(tree); + return edgesArray; + }, [tree, layoutNodes]); + + const getNodeClass = (nodeLayout) => { + if (!currentStep || !currentStep.tree) { + return "bg-gray-700 text-white border-2 border-gray-600"; + } + + const step = currentStep; + const stepType = step.type; + const rootValue = step.root; + const nodeValue = nodeLayout.value; + + if (rootValue === nodeValue) { + if (stepType === "select_root" || stepType === "create_node") { + 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"; + } else if (stepType === "find_in_inorder" || stepType === "calculate_sizes") { + return "bg-yellow-500 text-white border-4 border-yellow-300 ring-2 ring-yellow-400 ring-opacity-60 z-10"; + } else if (stepType === "attach_left" || stepType === "attach_right") { + return "bg-green-500 text-white border-4 border-green-300 ring-2 ring-green-400 ring-opacity-60 z-10"; + } else if (stepType === "complete_subtree") { + return "bg-emerald-500 text-white border-4 border-emerald-300 ring-2 ring-emerald-400 ring-opacity-60 z-10"; + } + } + if (step.tree) { + const isInTree = (node, targetTree) => { + if (!targetTree) return false; + if (targetTree.value === node.value) return true; + return isInTree(node, targetTree.left) || isInTree(node, targetTree.right); + }; + + if (isInTree(nodeLayout.node, step.tree) && rootValue !== nodeValue) { + return "bg-indigo-600 text-white border-2 border-indigo-400 opacity-90"; + } + } + return "bg-gray-700 text-white border-2 border-gray-600"; + }; + + const Node = ({ n }) => { + const cls = getNodeClass(n); + const radius = nodeSize / 2; + + return ( +