updating post order animation

This commit is contained in:
Jidong Xiao
2025-04-28 02:16:21 -04:00
committed by JamesFlare1212
parent 50bbf8c2a7
commit fea90f2b4d
2 changed files with 557 additions and 494 deletions

View File

@@ -1,11 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<!-- Author: Evan Lee -->
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Morris PostOrder Traversal Animation & Code Display</title> <title>Morris Post Order Traversal Animation & Code Display</title>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<!-- Load Konva.js --> <!-- Load Konva.js -->
<script src="https://cdn.jsdelivr.net/npm/konva@8.3.13/konva.min.js"></script> <script src="../../konva.js"></script>
<script src="script.js" defer></script> <script src="script.js" defer></script>
</head> </head>
<body> <body>

View File

@@ -46,10 +46,10 @@ const codeContent = [
'<span class="line-number">43.</span> }', '<span class="line-number">43.</span> }',
'<span class="line-number">44.</span> reverse(tail); <span style="color:green">// restore structure</span>', '<span class="line-number">44.</span> reverse(tail); <span style="color:green">// restore structure</span>',
'<span class="line-number">45.</span> }' '<span class="line-number">45.</span> }'
]; ];
// Initialize code display with proper structure for highlighting // Initialize code display with proper structure for highlighting
function initCodeDisplay() { function initCodeDisplay() {
const codeDisplay = document.getElementById('codeDisplay'); const codeDisplay = document.getElementById('codeDisplay');
codeContent.forEach((line, index) => { codeContent.forEach((line, index) => {
const lineDiv = document.createElement('div'); const lineDiv = document.createElement('div');
@@ -58,10 +58,10 @@ const codeContent = [
lineDiv.innerHTML = line; lineDiv.innerHTML = line;
codeDisplay.appendChild(lineDiv); codeDisplay.appendChild(lineDiv);
}); });
} }
// Function to highlight a specific line of code // Function to highlight a specific line of code
function highlightLine(lineNum) { function highlightLine(lineNum) {
// First, remove all highlights // First, remove all highlights
const codeLines = document.querySelectorAll('.code-line'); const codeLines = document.querySelectorAll('.code-line');
codeLines.forEach(line => { codeLines.forEach(line => {
@@ -78,10 +78,10 @@ const codeContent = [
targetLine.scrollIntoView({ behavior: 'smooth', block: 'center' }); targetLine.scrollIntoView({ behavior: 'smooth', block: 'center' });
} }
} }
} }
// Wait for the "Next Step" button click. // Wait for the "Next Step" button click.
function waitForNext() { function waitForNext() {
return new Promise(resolve => { return new Promise(resolve => {
const btn = document.getElementById('nextStep'); const btn = document.getElementById('nextStep');
btn.disabled = false; btn.disabled = false;
@@ -90,23 +90,23 @@ const codeContent = [
resolve(); resolve();
}; };
}); });
} }
// Append text to the output. // Append text to the output.
function appendOutput(text) { function appendOutput(text) {
const out = document.getElementById('output'); const out = document.getElementById('output');
out.innerText += text; out.innerText += text;
} }
// Update the explanation text. // Update the explanation text.
function updateExplanation(text) { function updateExplanation(text) {
document.getElementById('treeExplanation').innerHTML = document.getElementById('treeExplanation').innerHTML =
'Post-order traversal output: <span id="output">' + 'Post-order traversal output: <span id="output">' +
document.getElementById('output').innerText + '</span><br>' + text; document.getElementById('output').innerText + '</span><br>' + text;
} }
// Tree node class using Konva for visualization. // Tree node class using Konva for visualization.
class TreeNode { class TreeNode {
constructor(val, x, y) { constructor(val, x, y) {
this.val = val; this.val = val;
this.left = null; this.left = null;
@@ -136,19 +136,19 @@ const codeContent = [
this.threadEdge = null; this.threadEdge = null;
this.reversedEdges = []; this.reversedEdges = [];
} }
} }
// Initialize Konva stage and layer. // Initialize Konva stage and layer.
const stage = new Konva.Stage({ const stage = new Konva.Stage({
container: 'container', container: 'container',
width: document.querySelector('.tree-container').clientWidth - 30, width: document.querySelector('.tree-container').clientWidth - 30,
height: document.querySelector('.tree-container').clientHeight - 140 height: document.querySelector('.tree-container').clientHeight - 140
}); });
const layer = new Konva.Layer(); const layer = new Konva.Layer();
stage.add(layer); stage.add(layer);
// Calculate connection points for arrow drawing. // Calculate connection points for arrow drawing.
function calculateConnectionPoints(fromNode, toNode) { function calculateConnectionPoints(fromNode, toNode) {
const nodeRadius = 20; const nodeRadius = 20;
const dx = toNode.x - fromNode.x; const dx = toNode.x - fromNode.x;
const dy = toNode.y - fromNode.y; const dy = toNode.y - fromNode.y;
@@ -159,10 +159,10 @@ const codeContent = [
toX: toNode.x - nodeRadius * Math.cos(angle), toX: toNode.x - nodeRadius * Math.cos(angle),
toY: toNode.y - nodeRadius * Math.sin(angle) toY: toNode.y - nodeRadius * Math.sin(angle)
}; };
} }
// Create an arrow between two nodes. // Create an arrow between two nodes.
function createArrow(fromNode, toNode, color = '#888', dashed = false) { function createArrow(fromNode, toNode, color = '#888', dashed = false) {
const points = calculateConnectionPoints(fromNode, toNode); const points = calculateConnectionPoints(fromNode, toNode);
const arrow = new Konva.Arrow({ const arrow = new Konva.Arrow({
points: [points.fromX, points.fromY, points.toX, points.toY], points: [points.fromX, points.fromY, points.toX, points.toY],
@@ -176,95 +176,95 @@ const codeContent = [
}); });
layer.add(arrow); layer.add(arrow);
return arrow; return arrow;
} }
// Draw an edge between two nodes. // Draw an edge between two nodes.
function drawEdge(parent, child, isLeft = true) { function drawEdge(parent, child, isLeft = true) {
const arrow = createArrow(parent, child); const arrow = createArrow(parent, child);
layer.add(arrow); layer.add(arrow);
if (isLeft) { parent.leftEdge = arrow; } if (isLeft) { parent.leftEdge = arrow; }
else { parent.rightEdge = arrow; } else { parent.rightEdge = arrow; }
arrow.moveToBottom(); arrow.moveToBottom();
return arrow; return arrow;
} }
// Create and remove thread edges. // Create and remove thread edges.
function createThreadEdge(fromNode, toNode) { function createThreadEdge(fromNode, toNode) {
if (fromNode.threadEdge) { fromNode.threadEdge.destroy(); } if (fromNode.threadEdge) { fromNode.threadEdge.destroy(); }
const threadArrow = createArrow(fromNode, toNode, '#ff5722', true); const threadArrow = createArrow(fromNode, toNode, '#ff5722', true);
fromNode.threadEdge = threadArrow; fromNode.threadEdge = threadArrow;
threadArrow.moveToTop(); threadArrow.moveToTop();
layer.draw(); layer.draw();
return threadArrow; return threadArrow;
} }
function removeThreadEdge(node) { function removeThreadEdge(node) {
if (node && node.threadEdge) { if (node && node.threadEdge) {
node.threadEdge.destroy(); node.threadEdge.destroy();
node.threadEdge = null; node.threadEdge = null;
layer.draw(); layer.draw();
} }
} }
// Highlight and unhighlight a node. // Highlight and unhighlight a node.
function highlightNode(node, color = '#8bc34a') { function highlightNode(node, color = '#8bc34a') {
if (node && node.shape) { node.shape.to({ fill: color, duration: 0.25 }); } if (node && node.shape) { node.shape.to({ fill: color, duration: 0.25 }); }
} }
function unhighlightNode(node, color = 'white') { 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. // Calculate tree layout.
const stageWidth = stage.width(); const stageWidth = stage.width();
const stageHeight = stage.height(); const stageHeight = stage.height();
const centerX = stageWidth / 2; const centerX = stageWidth / 2;
const topY = 40; const topY = 40;
const levelHeight = stageHeight / 4; const levelHeight = stageHeight / 4;
// Build the 9-node binary tree. // Build the 9-node binary tree.
const node1 = new TreeNode(1, centerX, topY); const node1 = new TreeNode(1, centerX, topY);
const node2 = new TreeNode(2, centerX - stageWidth/4, topY + levelHeight); const node2 = new TreeNode(2, centerX - stageWidth/4, topY + levelHeight);
const node3 = new TreeNode(3, 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 node4 = new TreeNode(4, centerX - stageWidth/3, topY + 2*levelHeight);
const node5 = new TreeNode(5, centerX - stageWidth/6, 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 node6 = new TreeNode(6, centerX - stageWidth/4, topY + 3*levelHeight);
const node7 = new TreeNode(7, centerX - stageWidth/12, 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 node8 = new TreeNode(8, centerX + stageWidth/3, topY + 2*levelHeight);
const node9 = new TreeNode(9, centerX + stageWidth/4, topY + 3*levelHeight); const node9 = new TreeNode(9, centerX + stageWidth/4, topY + 3*levelHeight);
node1.left = node2; node1.left = node2;
node1.right = node3; node1.right = node3;
node2.left = node4; node2.left = node4;
node2.right = node5; node2.right = node5;
node5.left = node6; node5.left = node6;
node5.right = node7; node5.right = node7;
node3.right = node8; node3.right = node8;
node8.left = node9; node8.left = node9;
const nodes = [node1, node2, node3, node4, node5, node6, node7, node8, node9]; const nodes = [node1, node2, node3, node4, node5, node6, node7, node8, node9];
nodes.forEach(n => { nodes.forEach(n => {
layer.add(n.shape); layer.add(n.shape);
layer.add(n.label); layer.add(n.label);
}); });
drawEdge(node1, node2, true); drawEdge(node1, node2, true);
drawEdge(node1, node3, false); drawEdge(node1, node3, false);
drawEdge(node2, node4, true); drawEdge(node2, node4, true);
drawEdge(node2, node5, false); drawEdge(node2, node5, false);
drawEdge(node5, node6, true); drawEdge(node5, node6, true);
drawEdge(node5, node7, false); drawEdge(node5, node7, false);
drawEdge(node3, node8, false); drawEdge(node3, node8, false);
drawEdge(node8, node9, true); drawEdge(node8, node9, true);
nodes.forEach(n => { nodes.forEach(n => {
n.shape.moveToTop(); n.shape.moveToTop();
n.label.moveToTop(); n.label.moveToTop();
}); });
layer.draw(); layer.draw();
// Visualize the reversal process. // Visualize the reversal process.
async function visualizeReversal(head) { async function visualizeReversal(head) {
const connections = []; const connections = [];
let current = head; let current = head;
while (current !== null && current.right !== null) { while (current !== null && current.right !== null) {
@@ -292,33 +292,56 @@ const codeContent = [
await waitForNext(); await waitForNext();
} }
return prev; return prev;
} }
// Restore the original tree structure. // Restore the original tree structure.
async function visualizeRestore(head) { async function visualizeRestore(head) {
// Clear all reversed edges
nodes.forEach(node => { nodes.forEach(node => {
if (node.reversedEdges) { if (node.reversedEdges) {
node.reversedEdges.forEach(edge => edge.destroy()); node.reversedEdges.forEach(edge => edge.destroy());
node.reversedEdges = []; node.reversedEdges = [];
} }
}); });
let current = head;
while (current !== null && current.right !== null) { // Restore all original edges in the tree
if (!current.rightEdge) { // First ensure the correct node relationships
current.rightEdge = createArrow(current, current.right, '#888'); node1.left = node2;
current.rightEdge.moveToBottom(); node1.right = node3;
} node2.left = node4;
current = current.right; node2.right = node5;
} node5.left = node6;
node5.right = node7;
node3.right = node8;
node8.left = node9;
// 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();
});
// Move all nodes to the top
nodes.forEach(n => { nodes.forEach(n => {
n.shape.moveToTop(); n.shape.moveToTop();
n.label.moveToTop(); n.label.moveToTop();
}); });
layer.draw();
}
// Reverse a chain of right pointers. layer.draw();
async function reverseChain(head) { }
// Reverse a chain of right pointers.
async function reverseChain(head) {
updateExplanation("Visualizing the reversal of pointers"); updateExplanation("Visualizing the reversal of pointers");
await waitForNext(); await waitForNext();
const reversedHead = await visualizeReversal(head); const reversedHead = await visualizeReversal(head);
@@ -330,13 +353,27 @@ const codeContent = [
curr = next; curr = next;
} }
return prev; return prev;
} }
// Restore the chain. // Restore the chain.
async function restoreChain(head) { async function restoreChain(head) {
updateExplanation("Visualizing the restoration of original pointers"); updateExplanation("Visualizing the restoration of original pointers");
await waitForNext(); await waitForNext();
await visualizeRestore(head);
// 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; let prev = null, curr = head, next;
while (curr !== null) { while (curr !== null) {
next = curr.right; next = curr.right;
@@ -344,11 +381,34 @@ const codeContent = [
prev = curr; prev = curr;
curr = next; curr = next;
} }
return prev;
}
// Reverse traverse the right edge of a subtree. // Restore the original tree structure
async function reverseTraverseRightEdge(head) { 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; if (!head) return;
highlightLine(-1); // Clear code highlighting during reverse traversal highlightLine(-1); // Clear code highlighting during reverse traversal
updateExplanation("Executing reverseTraverseRightEdge on node " + head.val); updateExplanation("Executing reverseTraverseRightEdge on node " + head.val);
@@ -371,10 +431,10 @@ const codeContent = [
await waitForNext(); await waitForNext();
await restoreChain(tail); await restoreChain(tail);
layer.draw(); layer.draw();
} }
// Morris PostOrder Traversal Animation. // Morris PostOrder Traversal Animation.
async function postorderTraversal(root) { async function postorderTraversal(root) {
// Ensure all nodes are white at start // Ensure all nodes are white at start
nodes.forEach(n => { unhighlightNode(n, 'white'); }); nodes.forEach(n => { unhighlightNode(n, 'white'); });
@@ -385,14 +445,16 @@ const codeContent = [
highlightLine(1); highlightLine(1);
updateExplanation("Initializing current = root (node 1)"); updateExplanation("Initializing current = root (node 1)");
let current = root;
// Highlight current node immediately after initialization
highlightNode(current, '#ffeb3b');
layer.draw();
await waitForNext(); await waitForNext();
highlightLine(2); highlightLine(2);
updateExplanation("Declaring rightmost pointer"); updateExplanation("Declaring rightmost pointer");
await waitForNext(); await waitForNext();
let current = root;
// Main loop // Main loop
while (current !== null) { while (current !== null) {
// Clear previous highlighting // Clear previous highlighting
@@ -546,16 +608,16 @@ const codeContent = [
n.label.moveToTop(); n.label.moveToTop();
}); });
layer.draw(); layer.draw();
} }
window.addEventListener('resize', function() { window.addEventListener('resize', function() {
stage.width(document.querySelector('.tree-container').clientWidth - 30); stage.width(document.querySelector('.tree-container').clientWidth - 30);
stage.height(document.querySelector('.tree-container').clientHeight - 140); stage.height(document.querySelector('.tree-container').clientHeight - 140);
layer.draw(); layer.draw();
}); });
window.onload = function() { window.onload = function() {
initCodeDisplay(); initCodeDisplay();
document.getElementById('nextStep').disabled = false; document.getElementById('nextStep').disabled = false;
postorderTraversal(node1); postorderTraversal(node1);
}; };