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 @@
+
+
+
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();
});