// 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);
};