diff --git a/animations/trees/morris/morrisPostOrder.html b/animations/trees/morris/morrisPostOrder.html index 8a23404..78dd9ab 100644 --- a/animations/trees/morris/morrisPostOrder.html +++ b/animations/trees/morris/morrisPostOrder.html @@ -1,11 +1,12 @@ + - Morris Post‑Order Traversal Animation & Code Display + Morris Post Order Traversal Animation & Code Display - + @@ -57,4 +58,4 @@ - \ No newline at end of file + diff --git a/animations/trees/morris/script.js b/animations/trees/morris/script.js index 6feb08d..eaaa2e2 100644 --- a/animations/trees/morris/script.js +++ b/animations/trees/morris/script.js @@ -1,238 +1,311 @@ // 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 + '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'); }); - 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(); + // 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' }); } } - - // Highlight and unhighlight a node. - function highlightNode(node, color = '#8bc34a') { - if (node && node.shape) { node.shape.to({ fill: color, duration: 0.25 }); } +} + +// 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 = []; } - - function unhighlightNode(node, color = 'white') { - if (node && node.shape) { node.shape.to({ fill: color, duration: 0.25 }); } +} + +// 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) { + // Clear all reversed edges + nodes.forEach(node => { + if (node.reversedEdges) { + node.reversedEdges.forEach(edge => edge.destroy()); + node.reversedEdges = []; + } + }); - // 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); - + // Restore all original edges in the tree + // First ensure the correct node relationships node1.left = node2; node1.right = node3; node2.left = node4; @@ -242,276 +315,249 @@ const codeContent = [ 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); + // Then recreate any missing visual edges + if (!node1.leftEdge) node1.leftEdge = createArrow(node1, node2, '#888'); + if (!node1.rightEdge) node1.rightEdge = createArrow(node1, node3, '#888'); + if (!node2.leftEdge) node2.leftEdge = createArrow(node2, node4, '#888'); + if (!node2.rightEdge) node2.rightEdge = createArrow(node2, node5, '#888'); + if (!node5.leftEdge) node5.leftEdge = createArrow(node5, node6, '#888'); + if (!node5.rightEdge) node5.rightEdge = createArrow(node5, node7, '#888'); + if (!node3.rightEdge) node3.rightEdge = createArrow(node3, node8, '#888'); + if (!node8.leftEdge) node8.leftEdge = createArrow(node8, node9, '#888'); + + // Move all arrows to the bottom + nodes.forEach(node => { + if (node.leftEdge) node.leftEdge.moveToBottom(); + if (node.rightEdge) node.rightEdge.moveToBottom(); }); - 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); - + // Move all nodes to the top 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(); - // 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; + // Store the original tree structure before restoring + const originalTree = { + node1: { left: node1.left, right: node1.right }, + node2: { left: node2.left, right: node2.right }, + node3: { left: node3.left, right: node3.right }, + node4: { left: node4.left, right: node4.right }, + node5: { left: node5.left, right: node5.right }, + node6: { left: node6.left, right: node6.right }, + node7: { left: node7.left, right: node7.right }, + node8: { left: node8.left, right: node8.right }, + node9: { left: node9.left, right: node9.right } + }; + + // Reverse the linked list back + let prev = null, curr = head, next; + while (curr !== null) { + next = curr.right; + curr.right = prev; + prev = curr; + curr = next; } - // Restore the original tree structure. - async function visualizeRestore(head) { - nodes.forEach(node => { - if (node.reversedEdges) { - node.reversedEdges.forEach(edge => edge.destroy()); - node.reversedEdges = []; + // Restore the original tree structure + node1.left = originalTree.node1.left; + node1.right = originalTree.node1.right; + node2.left = originalTree.node2.left; + node2.right = originalTree.node2.right; + node3.left = originalTree.node3.left; + node3.right = originalTree.node3.right; + node4.left = originalTree.node4.left; + node4.right = originalTree.node4.right; + node5.left = originalTree.node5.left; + node5.right = originalTree.node5.right; + node6.left = originalTree.node6.left; + node6.right = originalTree.node6.right; + node7.left = originalTree.node7.left; + node7.right = originalTree.node7.right; + node8.left = originalTree.node8.left; + node8.right = originalTree.node8.right; + node9.left = originalTree.node9.left; + node9.right = originalTree.node9.right; + + await visualizeRestore(prev); + + 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)"); + let current = root; + // Highlight current node immediately after initialization + highlightNode(current, '#ffeb3b'); + layer.draw(); + await waitForNext(); + + highlightLine(2); + updateExplanation("Declaring rightmost pointer"); + await waitForNext(); + + // Main loop + while (current !== null) { + // Clear previous highlighting + nodes.forEach(n => { + if (n !== current) { + unhighlightNode(n, 'white'); } }); - 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(); - }); + + highlightLine(3); + updateExplanation("Checking if current is null"); + await waitForNext(); + + // Always ensure current node is highlighted in yellow + highlightNode(current, '#ffeb3b'); 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"); + highlightLine(4); + updateExplanation("Checking if current node " + current.val + " has a left child"); 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"); + if (current.left !== null) { + highlightLine(5); + updateExplanation("Current: " + current.val + " has left child, initializing rightmost = current.left (node " + current.left.val + ")"); await waitForNext(); - // Always ensure current node is highlighted in yellow - highlightNode(current, '#ffeb3b'); + let rightmost = current.left; + highlightNode(rightmost, '#2196f3'); layer.draw(); - highlightLine(4); - updateExplanation("Checking if current node " + current.val + " has a left child"); + highlightLine(6); + updateExplanation("Finding the rightmost node in left subtree that doesn't have a thread"); await waitForNext(); - if (current.left !== null) { - highlightLine(5); - updateExplanation("Current: " + current.val + " has left child, initializing rightmost = current.left (node " + current.left.val + ")"); + while (rightmost.right !== null && rightmost.right !== current) { + highlightLine(7); + updateExplanation("Moving rightmost from " + rightmost.val + " to its right child"); await waitForNext(); - let rightmost = current.left; + unhighlightNode(rightmost, 'white'); + rightmost = rightmost.right; highlightNode(rightmost, '#2196f3'); + // Ensure current is still highlighted + highlightNode(current, '#ffeb3b'); layer.draw(); highlightLine(6); - updateExplanation("Finding the rightmost node in left subtree that doesn't have a thread"); + 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(); - 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"); + createThreadEdge(rightmost, current); + rightmost.right = current; + highlightNode(rightmost, '#03a9f4'); + // Keep current highlighted + highlightNode(current, '#ffeb3b'); + layer.draw(); 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(); - } - } + 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(17); - updateExplanation("Current: " + current.val + " has no left child"); + highlightLine(13); + updateExplanation("Thread detected from rightmost (" + rightmost.val + ") to current (" + current.val + ") - removing thread"); await waitForNext(); - highlightLine(18); + 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(); @@ -522,40 +568,56 @@ const codeContent = [ layer.draw(); } } - - highlightLine(3); - updateExplanation("Checking if current is null"); + } 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(21); - updateExplanation("Processing the final right edge starting from root"); + highlightLine(3); + updateExplanation("Checking if current is null"); 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(); - }); + highlightLine(21); + updateExplanation("Processing the final right edge starting from root"); + await waitForNext(); - window.onload = function() { - initCodeDisplay(); - document.getElementById('nextStep').disabled = false; - postorderTraversal(node1); - }; \ No newline at end of file + 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