diff --git a/animations/trees/morris/morrisPostOrder.html b/animations/trees/morris/morrisPostOrder.html index 5bc5b37..8a23404 100644 --- a/animations/trees/morris/morrisPostOrder.html +++ b/animations/trees/morris/morrisPostOrder.html @@ -3,143 +3,10 @@ Morris Post‑Order Traversal Animation & Code Display - + +

Morris Post-Order Traversal Visualization

@@ -189,531 +56,5 @@ - - \ No newline at end of file diff --git a/animations/trees/morris/script.js b/animations/trees/morris/script.js new file mode 100644 index 0000000..6feb08d --- /dev/null +++ b/animations/trees/morris/script.js @@ -0,0 +1,561 @@ +// Define code content +const codeContent = [ + '0. void postorderTraversal(TreeNode* root) {', + '1. TreeNode* current = root;', + '2. TreeNode* rightmost;', + '3. while (current != nullptr) {', + '4. if (current->left != nullptr) {', + '5. rightmost = current->left;', + '6. while (rightmost->right != nullptr && rightmost->right != current) {', + '7. rightmost = rightmost->right;', + '8. }', + '9. if (rightmost->right == nullptr) {', + '10. rightmost->right = current;', + '11. current = current->left;', + '12. } else {', + '13. rightmost->right = nullptr;', + '14. reverseTraverseRightEdge(current->left);', + '15. current = current->right;', + '16. }', + '17. } else {', + '18. current = current->right;', + '19. }', + '20. }', + '21. reverseTraverseRightEdge(root); // final right edge', + '22. return;', + '23. }', + '24. ', + '25. TreeNode* reverse(TreeNode* head) {', + '26. TreeNode* prev = nullptr;', + '27. TreeNode* next = nullptr;', + '28. while (head != nullptr) {', + '29. next = head->right;', + '30. head->right = prev;', + '31. prev = head;', + '32. head = next;', + '33. }', + '34. return prev;', + '35. }', + '36. ', + '37. void reverseTraverseRightEdge(TreeNode* head) {', + '38. TreeNode* tail = reverse(head);', + '39. TreeNode* current = tail;', + '40. while (current != nullptr) {', + '41. std::cout << current->val << " ";', + '42. current = current->right;', + '43. }', + '44. reverse(tail); // restore structure', + '45. }' + ]; + + // Initialize code display with proper structure for highlighting + function initCodeDisplay() { + const codeDisplay = document.getElementById('codeDisplay'); + codeContent.forEach((line, index) => { + const lineDiv = document.createElement('div'); + lineDiv.className = 'code-line'; + lineDiv.setAttribute('data-line', index); + lineDiv.innerHTML = line; + codeDisplay.appendChild(lineDiv); + }); + } + + // Function to highlight a specific line of code + function highlightLine(lineNum) { + // First, remove all highlights + const codeLines = document.querySelectorAll('.code-line'); + codeLines.forEach(line => { + line.classList.remove('highlighted'); + }); + + // Add highlight to the specified line + if (lineNum >= 0 && lineNum < codeContent.length) { + const targetLine = document.querySelector(`.code-line[data-line="${lineNum}"]`); + if (targetLine) { + targetLine.classList.add('highlighted'); + + // Scroll to make the highlighted line visible + targetLine.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + } + } + + // Wait for the "Next Step" button click. + function waitForNext() { + return new Promise(resolve => { + const btn = document.getElementById('nextStep'); + btn.disabled = false; + btn.onclick = () => { + btn.disabled = true; + resolve(); + }; + }); + } + + // Append text to the output. + function appendOutput(text) { + const out = document.getElementById('output'); + out.innerText += text; + } + + // Update the explanation text. + function updateExplanation(text) { + document.getElementById('treeExplanation').innerHTML = + 'Post-order traversal output: ' + + document.getElementById('output').innerText + '
' + text; + } + + // Tree node class using Konva for visualization. + class TreeNode { + constructor(val, x, y) { + this.val = val; + this.left = null; + this.right = null; + this.x = x; + this.y = y; + this.shape = new Konva.Rect({ + x: x - 20, + y: y - 20, + width: 40, + height: 40, + fill: 'white', + stroke: '#888', + strokeWidth: 2, + cornerRadius: 4 + }); + this.label = new Konva.Text({ + x: x - 7, + y: y - 10, + text: String(val), + fontSize: 20, + fontFamily: 'Arial', + fill: 'black' + }); + this.leftEdge = null; + this.rightEdge = null; + this.threadEdge = null; + this.reversedEdges = []; + } + } + + // Initialize Konva stage and layer. + const stage = new Konva.Stage({ + container: 'container', + width: document.querySelector('.tree-container').clientWidth - 30, + height: document.querySelector('.tree-container').clientHeight - 140 + }); + const layer = new Konva.Layer(); + stage.add(layer); + + // Calculate connection points for arrow drawing. + function calculateConnectionPoints(fromNode, toNode) { + const nodeRadius = 20; + const dx = toNode.x - fromNode.x; + const dy = toNode.y - fromNode.y; + const angle = Math.atan2(dy, dx); + return { + fromX: fromNode.x + nodeRadius * Math.cos(angle), + fromY: fromNode.y + nodeRadius * Math.sin(angle), + toX: toNode.x - nodeRadius * Math.cos(angle), + toY: toNode.y - nodeRadius * Math.sin(angle) + }; + } + + // Create an arrow between two nodes. + function createArrow(fromNode, toNode, color = '#888', dashed = false) { + const points = calculateConnectionPoints(fromNode, toNode); + const arrow = new Konva.Arrow({ + points: [points.fromX, points.fromY, points.toX, points.toY], + pointerLength: 10, + pointerWidth: 8, + fill: color, + stroke: color, + strokeWidth: dashed ? 1 : 2, + dashEnabled: dashed, + dash: dashed ? [5, 5] : null + }); + layer.add(arrow); + return arrow; + } + + // Draw an edge between two nodes. + function drawEdge(parent, child, isLeft = true) { + const arrow = createArrow(parent, child); + layer.add(arrow); + if (isLeft) { parent.leftEdge = arrow; } + else { parent.rightEdge = arrow; } + arrow.moveToBottom(); + return arrow; + } + + // Create and remove thread edges. + function createThreadEdge(fromNode, toNode) { + if (fromNode.threadEdge) { fromNode.threadEdge.destroy(); } + const threadArrow = createArrow(fromNode, toNode, '#ff5722', true); + fromNode.threadEdge = threadArrow; + threadArrow.moveToTop(); + layer.draw(); + return threadArrow; + } + + function removeThreadEdge(node) { + if (node && node.threadEdge) { + node.threadEdge.destroy(); + node.threadEdge = null; + layer.draw(); + } + } + + // Highlight and unhighlight a node. + function highlightNode(node, color = '#8bc34a') { + if (node && node.shape) { node.shape.to({ fill: color, duration: 0.25 }); } + } + + function unhighlightNode(node, color = 'white') { + if (node && node.shape) { node.shape.to({ fill: color, duration: 0.25 }); } + } + + // Calculate tree layout. + const stageWidth = stage.width(); + const stageHeight = stage.height(); + const centerX = stageWidth / 2; + const topY = 40; + const levelHeight = stageHeight / 4; + + // Build the 9-node binary tree. + const node1 = new TreeNode(1, centerX, topY); + const node2 = new TreeNode(2, centerX - stageWidth/4, topY + levelHeight); + const node3 = new TreeNode(3, centerX + stageWidth/4, topY + levelHeight); + const node4 = new TreeNode(4, centerX - stageWidth/3, topY + 2*levelHeight); + const node5 = new TreeNode(5, centerX - stageWidth/6, topY + 2*levelHeight); + const node6 = new TreeNode(6, centerX - stageWidth/4, topY + 3*levelHeight); + const node7 = new TreeNode(7, centerX - stageWidth/12, topY + 3*levelHeight); + const node8 = new TreeNode(8, centerX + stageWidth/3, topY + 2*levelHeight); + const node9 = new TreeNode(9, centerX + stageWidth/4, topY + 3*levelHeight); + + node1.left = node2; + node1.right = node3; + node2.left = node4; + node2.right = node5; + node5.left = node6; + node5.right = node7; + node3.right = node8; + node8.left = node9; + + const nodes = [node1, node2, node3, node4, node5, node6, node7, node8, node9]; + nodes.forEach(n => { + layer.add(n.shape); + layer.add(n.label); + }); + + drawEdge(node1, node2, true); + drawEdge(node1, node3, false); + drawEdge(node2, node4, true); + drawEdge(node2, node5, false); + drawEdge(node5, node6, true); + drawEdge(node5, node7, false); + drawEdge(node3, node8, false); + drawEdge(node8, node9, true); + + nodes.forEach(n => { + n.shape.moveToTop(); + n.label.moveToTop(); + }); + layer.draw(); + + // Visualize the reversal process. + async function visualizeReversal(head) { + const connections = []; + let current = head; + while (current !== null && current.right !== null) { + connections.push({ from: current, to: current.right }); + current = current.right; + } + for (const conn of connections) { + if (conn.from.rightEdge) { + conn.from.rightEdge.destroy(); + conn.from.rightEdge = null; + } + } + let prev = null; + current = head; + while (current !== null) { + const next = current.right; + if (prev !== null) { + const reversedEdge = createArrow(current, prev, '#e91e63'); + current.reversedEdges.push(reversedEdge); + reversedEdge.moveToTop(); + } + prev = current; + current = next; + layer.draw(); + await waitForNext(); + } + return prev; + } + + // Restore the original tree structure. + async function visualizeRestore(head) { + nodes.forEach(node => { + if (node.reversedEdges) { + node.reversedEdges.forEach(edge => edge.destroy()); + node.reversedEdges = []; + } + }); + let current = head; + while (current !== null && current.right !== null) { + if (!current.rightEdge) { + current.rightEdge = createArrow(current, current.right, '#888'); + current.rightEdge.moveToBottom(); + } + current = current.right; + } + nodes.forEach(n => { + n.shape.moveToTop(); + n.label.moveToTop(); + }); + layer.draw(); + } + + // Reverse a chain of right pointers. + async function reverseChain(head) { + updateExplanation("Visualizing the reversal of pointers"); + await waitForNext(); + const reversedHead = await visualizeReversal(head); + let prev = null, curr = head, next; + while (curr !== null) { + next = curr.right; + curr.right = prev; + prev = curr; + curr = next; + } + return prev; + } + + // Restore the chain. + async function restoreChain(head) { + updateExplanation("Visualizing the restoration of original pointers"); + await waitForNext(); + await visualizeRestore(head); + let prev = null, curr = head, next; + while (curr !== null) { + next = curr.right; + curr.right = prev; + prev = curr; + curr = next; + } + return prev; + } + + // Reverse traverse the right edge of a subtree. + async function reverseTraverseRightEdge(head) { + if (!head) return; + highlightLine(-1); // Clear code highlighting during reverse traversal + updateExplanation("Executing reverseTraverseRightEdge on node " + head.val); + await waitForNext(); + let tail = await reverseChain(head); + layer.draw(); + updateExplanation("Visiting nodes along the reversed right edge"); + let cur = tail; + while (cur !== null) { + highlightNode(cur, '#4caf50'); + layer.draw(); + appendOutput(cur.val + " "); + updateExplanation("Visited node: " + cur.val); + await waitForNext(); + unhighlightNode(cur, 'white'); + layer.draw(); + cur = cur.right; + } + updateExplanation("Restoring the original right edge"); + await waitForNext(); + await restoreChain(tail); + layer.draw(); + } + + // Morris Post‑Order Traversal Animation. + async function postorderTraversal(root) { + // Ensure all nodes are white at start + nodes.forEach(n => { unhighlightNode(n, 'white'); }); + + // Highlight the initialization steps + highlightLine(0); + updateExplanation("Starting the Morris Post-Order Traversal"); + await waitForNext(); + + highlightLine(1); + updateExplanation("Initializing current = root (node 1)"); + await waitForNext(); + + highlightLine(2); + updateExplanation("Declaring rightmost pointer"); + await waitForNext(); + + let current = root; + + // Main loop + while (current !== null) { + // Clear previous highlighting + nodes.forEach(n => { + if (n !== current) { + unhighlightNode(n, 'white'); + } + }); + + highlightLine(3); + updateExplanation("Checking if current is null"); + await waitForNext(); + + // Always ensure current node is highlighted in yellow + highlightNode(current, '#ffeb3b'); + layer.draw(); + + highlightLine(4); + updateExplanation("Checking if current node " + current.val + " has a left child"); + await waitForNext(); + + if (current.left !== null) { + highlightLine(5); + updateExplanation("Current: " + current.val + " has left child, initializing rightmost = current.left (node " + current.left.val + ")"); + await waitForNext(); + + let rightmost = current.left; + highlightNode(rightmost, '#2196f3'); + layer.draw(); + + highlightLine(6); + updateExplanation("Finding the rightmost node in left subtree that doesn't have a thread"); + await waitForNext(); + + while (rightmost.right !== null && rightmost.right !== current) { + highlightLine(7); + updateExplanation("Moving rightmost from " + rightmost.val + " to its right child"); + await waitForNext(); + + unhighlightNode(rightmost, 'white'); + rightmost = rightmost.right; + highlightNode(rightmost, '#2196f3'); + // Ensure current is still highlighted + highlightNode(current, '#ffeb3b'); + layer.draw(); + + highlightLine(6); + updateExplanation("Checking if we've reached the rightmost node or a thread"); + await waitForNext(); + } + + highlightLine(9); + updateExplanation("Checking if rightmost (" + rightmost.val + ") has no thread"); + await waitForNext(); + + if (rightmost.right === null) { + highlightLine(10); + updateExplanation("Creating thread from rightmost (" + rightmost.val + ") to current (" + current.val + ")"); + await waitForNext(); + + createThreadEdge(rightmost, current); + rightmost.right = current; + highlightNode(rightmost, '#03a9f4'); + // Keep current highlighted + highlightNode(current, '#ffeb3b'); + layer.draw(); + await waitForNext(); + + highlightLine(11); + updateExplanation("Moving current to its left child (" + current.left.val + ")"); + await waitForNext(); + + unhighlightNode(rightmost, 'white'); + unhighlightNode(current, 'white'); + current = current.left; + // Highlight new current + highlightNode(current, '#ffeb3b'); + layer.draw(); + } else { + highlightLine(13); + updateExplanation("Thread detected from rightmost (" + rightmost.val + ") to current (" + current.val + ") - removing thread"); + await waitForNext(); + + removeThreadEdge(rightmost); + rightmost.right = null; + unhighlightNode(rightmost, 'white'); + // Keep current highlighted + highlightNode(current, '#ffeb3b'); + layer.draw(); + + highlightLine(14); + updateExplanation("Processing left subtree of current (" + current.val + ")"); + await waitForNext(); + + // Save current node to restore highlight after recursion + const savedCurrent = current; + await reverseTraverseRightEdge(current.left); + + // Restore current node highlight + highlightNode(savedCurrent, '#ffeb3b'); + layer.draw(); + + highlightLine(15); + updateExplanation("Moving current to its right child"); + await waitForNext(); + + unhighlightNode(current, 'white'); + current = current.right; + if (current) { + highlightNode(current, '#ffeb3b'); + layer.draw(); + } + } + } else { + highlightLine(17); + updateExplanation("Current: " + current.val + " has no left child"); + await waitForNext(); + + highlightLine(18); + updateExplanation("Moving current to its right child"); + await waitForNext(); + + unhighlightNode(current, 'white'); + current = current.right; + if (current) { + highlightNode(current, '#ffeb3b'); + layer.draw(); + } + } + + highlightLine(3); + updateExplanation("Checking if current is null"); + await waitForNext(); + } + + highlightLine(21); + updateExplanation("Processing the final right edge starting from root"); + await waitForNext(); + + await reverseTraverseRightEdge(root); + + highlightLine(22); + updateExplanation("Morris Post-Order Traversal complete"); + await waitForNext(); + + highlightLine(-1); // Clear highlighting + updateExplanation("Final post-order traversal result: " + document.getElementById('output').innerText); + nodes.forEach(n => { + unhighlightNode(n, 'white'); + n.shape.moveToTop(); + n.label.moveToTop(); + }); + layer.draw(); + } + + window.addEventListener('resize', function() { + stage.width(document.querySelector('.tree-container').clientWidth - 30); + stage.height(document.querySelector('.tree-container').clientHeight - 140); + layer.draw(); + }); + + window.onload = function() { + initCodeDisplay(); + document.getElementById('nextStep').disabled = false; + postorderTraversal(node1); + }; \ No newline at end of file diff --git a/animations/trees/morris/style.css b/animations/trees/morris/style.css new file mode 100644 index 0000000..baab928 --- /dev/null +++ b/animations/trees/morris/style.css @@ -0,0 +1,166 @@ +html, body { + margin: 0; + padding: 0; + font-family: Arial, sans-serif; + } + + h1 { + text-align: center; + margin-top: 10px; + margin-bottom: 5px; + } + + p { + text-align: center; + margin: 5px 0 15px; + } + + .visualization-container { + display: flex; + justify-content: space-between; + max-width: 1600px; /* Increased from 1200px */ + margin: 0 auto; + height: 88vh; + } + + .code-container { + background-color: #f0f0f0; + border-radius: 10px; + padding: 15px; + width: 55%; /* Increased from 48% */ + overflow-y: auto; + height: 87vh; + font-size: 15px; + } + + pre { + margin: 0; + background-color: #f0f0f0; + width: 100%; + } + + code { + font-family: Consolas, "Courier New", monospace; + white-space: pre; + display: inline-block; + min-width: 750px; /* Ensure code is wide enough for longest line */ + } + + #codeDisplay { + width: 100%; + } + + .line-number { + color: #666; + display: inline-block; + min-width: 30px; + } + + .code-line { + display: block; + width: 100%; + position: relative; + box-sizing: border-box; + } + + /* Fix for line highlighting - make sure the entire line is highlighted */ + .code-line.highlighted { + background-color: #ffeb3b; + width: 100%; + display: block; + } + + .tree-container { + background-color: #f0f0f0; + border-radius: 10px; + padding: 15px; + width: 42%; /* Reduced from 48% to compensate for code container increase */ + height: 80vh; + position: relative; + display: flex; + flex-direction: column; + } + + #nextStep { + margin: 10px auto; + padding: 8px 15px; + font-size: 16px; + width: fit-content; + cursor: pointer; + background-color: #4CAF50; + color: white; + border: none; + border-radius: 4px; + display: block; + } + + #nextStep:hover { + background-color: #45a049; + } + + #nextStep:disabled { + background-color: #cccccc; + cursor: not-allowed; + } + + #container { + flex-grow: 1; + width: 100%; + } + + .explanation { + margin-top: 10px; + font-size: 14px; + padding: 10px; + background-color: #e8e8e8; + border-radius: 5px; + } + + .color-key { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 5px; + padding: 8px; + background-color: #e8e8e8; + border-radius: 5px; + } + + .color-item { + display: flex; + align-items: center; + margin-right: 10px; + font-size: 13px; + } + + .color-box { + width: 16px; + height: 16px; + border-radius: 3px; + margin-right: 4px; + border: 1px solid #888; + } + + .color-arrow { + position: relative; + width: 24px; + height: 3px; + margin-right: 4px; + } + + .color-arrow:after { + content: ""; + position: absolute; + right: -2px; + top: -4px; + border-left: 6px solid; + border-top: 4px solid transparent; + border-bottom: 4px solid transparent; + } + + .dotted-line { + width: 24px; + height: 0; + border-top: 2px dashed #ff5722; + margin-right: 4px; + } \ No newline at end of file