diff --git a/animations/trees/morris/morrisPostOrder.html b/animations/trees/morris/morrisPostOrder.html index a6d5986..bfae653 100644 --- a/animations/trees/morris/morrisPostOrder.html +++ b/animations/trees/morris/morrisPostOrder.html @@ -5,33 +5,25 @@ Morris Post‑Order Traversal Animation & Code Display - + @@ -126,7 +159,7 @@ 18. current = current->right; 19. } 20. } -21. reverseTraverseRightEdge(root); // traverse the final right edge +21. reverseTraverseRightEdge(root); // final right edge 22. return; 23. } 24. @@ -149,11 +182,43 @@ 41. std::cout << current->val << " "; 42. current = current->right; 43. } -44. reverse(tail); // restore the original tree structure +44. reverse(tail); // restore structure 45. }
+
+
+
+ Current Node +
+
+
+ Rightmost Node +
+
+
+ Visited Node +
+
+
+ Thread Created +
+
+
+ Tree Edge +
+
+
+ Thread Edge +
+
+
+ Reversed Edge +
+
+ +
Post-order traversal output: @@ -182,8 +247,8 @@ // Update the explanation text. function updateExplanation(text) { - document.getElementById('treeExplanation').innerHTML = - 'Post-order traversal output: ' + + document.getElementById('treeExplanation').innerHTML = + 'Post-order traversal output: ' + document.getElementById('output').innerText + '
' + text; } @@ -213,46 +278,93 @@ 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 - 40, - height: document.querySelector('.tree-container').clientHeight - 140 + width: document.querySelector('.tree-container').clientWidth - 30, + height: document.querySelector('.tree-container').clientHeight - 140 }); const layer = new Konva.Layer(); stage.add(layer); - // Draw an edge between two nodes. - function drawEdge(parent, child, color = '#888') { - const line = new Konva.Line({ - points: [parent.x, parent.y, child.x, child.y], - stroke: color, - strokeWidth: 2 - }); - layer.add(line); - return line; + // 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) + }; } - // Highlight or unhighlight a node. - function highlightNode(node, color = '#8bc34a') { - if (node && node.shape) { - node.shape.to({ fill: color, duration: 0.25 }); + // 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 }); - } + 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 = 50; + const topY = 40; const levelHeight = stageHeight / 4; // Build the 9-node binary tree. @@ -281,15 +393,14 @@ layer.add(n.label); }); - const edges = []; - edges.push(drawEdge(node1, node2)); - edges.push(drawEdge(node1, node3)); - edges.push(drawEdge(node2, node4)); - edges.push(drawEdge(node2, node5)); - edges.push(drawEdge(node5, node6)); - edges.push(drawEdge(node5, node7)); - edges.push(drawEdge(node3, node8)); - edges.push(drawEdge(node8, node9)); + 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(); @@ -297,11 +408,81 @@ }); layer.draw(); - // Reverse a chain of right pointers. - function reverseChain(head) { + // 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; - let curr = head; - let next; + 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; @@ -314,19 +495,15 @@ // Reverse traverse the right edge of a subtree. async function reverseTraverseRightEdge(head) { if (!head) return; - updateExplanation("Reversing the right edge starting from node " + head.val); await waitForNext(); - - let tail = reverseChain(head); + 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(); @@ -334,58 +511,50 @@ layer.draw(); cur = cur.right; } - updateExplanation("Restoring the original right edge"); await waitForNext(); - - reverseChain(tail); + await restoreChain(tail); layer.draw(); } // Morris Post‑Order Traversal Animation. async function postorderTraversal(root) { let current = root; - while (current !== null) { highlightNode(current, '#ffeb3b'); layer.draw(); - if (current.left !== null) { - updateExplanation("Current: " + current.val + " - Has left child, finding rightmost node in left subtree"); + updateExplanation("Current: " + current.val + " - Has left child, finding rightmost in left subtree"); await waitForNext(); - let rightmost = current.left; highlightNode(rightmost, '#2196f3'); layer.draw(); - while (rightmost.right !== null && rightmost.right !== current) { - updateExplanation("Moving to the right to find rightmost: " + rightmost.val); + updateExplanation("Moving to right: " + rightmost.val); await waitForNext(); - unhighlightNode(rightmost, 'white'); rightmost = rightmost.right; highlightNode(rightmost, '#2196f3'); layer.draw(); } - if (rightmost.right === null) { updateExplanation("Rightmost: " + rightmost.val + " - Creating thread to current: " + current.val); await waitForNext(); - + createThreadEdge(rightmost, current); rightmost.right = current; highlightNode(rightmost, '#03a9f4'); layer.draw(); - + await waitForNext(); + unhighlightNode(rightmost, 'white'); unhighlightNode(current, 'white'); current = current.left; } else { - updateExplanation("Thread detected at rightmost: " + rightmost.val + " - Processing subtree"); + updateExplanation("Thread detected at rightmost: " + rightmost.val + " - Removing thread and processing subtree"); await waitForNext(); - + removeThreadEdge(rightmost); rightmost.right = null; unhighlightNode(rightmost, 'white'); layer.draw(); - await reverseTraverseRightEdge(current.left); unhighlightNode(current, 'white'); current = current.right; @@ -393,25 +562,25 @@ } else { updateExplanation("Current: " + current.val + " - No left child, moving right"); await waitForNext(); - unhighlightNode(current, 'white'); current = current.right; } } - updateExplanation("Processing final right edge"); await waitForNext(); - await reverseTraverseRightEdge(root); updateExplanation("Morris Post-Order Traversal complete"); - await waitForNext(); updateExplanation("Final post-order traversal result: " + document.getElementById('output').innerText); + nodes.forEach(n => { + n.shape.moveToTop(); + n.label.moveToTop(); + }); + layer.draw(); } - // Adjust stage on window resize. window.addEventListener('resize', function() { - stage.width(document.querySelector('.tree-container').clientWidth - 40); + stage.width(document.querySelector('.tree-container').clientWidth - 30); stage.height(document.querySelector('.tree-container').clientHeight - 140); layer.draw(); });