Files
CSCI-1200/animations/trees/morris/morrisPostOrder.html
2025-04-15 22:10:48 -04:00

595 lines
21 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Morris PostOrder Traversal Animation & Code Display</title>
<style>
html, body { margin: 0; padding: 0; font-family: Arial, sans-serif; }
h1 { text-align: center; margin-top: 10px; margin-bottom: 5px; }
p { text-align: center; margin: 5px 0 15px; }
.visualization-container {
display: flex;
justify-content: space-between;
max-width: 1200px;
margin: 0 auto;
height: 88vh;
}
.code-container {
background-color: #f0f0f0;
border-radius: 10px;
padding: 15px;
width: 48%;
overflow-y: auto;
height: 88vh;
font-size: 15px;
}
pre {
margin: 0;
background-color: #f0f0f0;
overflow-x: auto;
}
code {
font-family: Consolas, "Courier New", monospace;
white-space: pre;
}
.line-number {
color: #666;
display: inline-block;
min-width: 30px;
}
.tree-container {
background-color: #f0f0f0;
border-radius: 10px;
padding: 15px;
width: 48%;
height: 88vh;
position: relative;
display: flex;
flex-direction: column;
}
#nextStep {
margin: 10px auto;
padding: 8px 15px;
font-size: 16px;
width: fit-content;
cursor: pointer;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
display: block;
}
#nextStep:hover { background-color: #45a049; }
#nextStep:disabled { background-color: #cccccc; cursor: not-allowed; }
#container { flex-grow: 1; width: 100%; }
.explanation {
margin-top: 10px;
font-size: 14px;
padding: 10px;
background-color: #e8e8e8;
border-radius: 5px;
}
.color-key {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 5px;
padding: 8px;
background-color: #e8e8e8;
border-radius: 5px;
}
.color-item {
display: flex;
align-items: center;
margin-right: 10px;
font-size: 13px;
}
.color-box {
width: 16px;
height: 16px;
border-radius: 3px;
margin-right: 4px;
border: 1px solid #888;
}
.color-arrow {
position: relative;
width: 24px;
height: 3px;
margin-right: 4px;
}
.color-arrow:after {
content: "";
position: absolute;
right: -2px;
top: -4px;
border-left: 6px solid;
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
}
.dotted-line {
width: 24px;
height: 0;
border-top: 2px dashed #ff5722;
margin-right: 4px;
}
</style>
<!-- Load Konva.js -->
<script src="https://cdn.jsdelivr.net/npm/konva@8.3.13/konva.min.js"></script>
</head>
<body>
<h1>Morris Post-Order Traversal Visualization</h1>
<p>This animation shows how the Morris post-order traversal algorithm works without using a stack or recursion. Click the "Next Step" button to run the animation.</p>
<div class="visualization-container">
<div class="code-container">
<pre><code><span class="line-number">0.</span> <span style="color:blue">void</span> postorderTraversal(<span style="color:blue">TreeNode</span>* root) {
<span class="line-number">1.</span> <span style="color:blue">TreeNode</span>* current = root;
<span class="line-number">2.</span> <span style="color:blue">TreeNode</span>* rightmost;
<span class="line-number">3.</span> <span style="color:blue">while</span> (current != <span style="color:blue">nullptr</span>) {
<span class="line-number">4.</span> <span style="color:blue">if</span> (current->left != <span style="color:blue">nullptr</span>) {
<span class="line-number">5.</span> rightmost = current->left;
<span class="line-number">6.</span> <span style="color:blue">while</span> (rightmost->right != <span style="color:blue">nullptr</span> && rightmost->right != current) {
<span class="line-number">7.</span> rightmost = rightmost->right;
<span class="line-number">8.</span> }
<span class="line-number">9.</span> <span style="color:blue">if</span> (rightmost->right == <span style="color:blue">nullptr</span>) {
<span class="line-number">10.</span> rightmost->right = current;
<span class="line-number">11.</span> current = current->left;
<span class="line-number">12.</span> } <span style="color:blue">else</span> {
<span class="line-number">13.</span> rightmost->right = <span style="color:blue">nullptr</span>;
<span class="line-number">14.</span> reverseTraverseRightEdge(current->left);
<span class="line-number">15.</span> current = current->right;
<span class="line-number">16.</span> }
<span class="line-number">17.</span> } <span style="color:blue">else</span> {
<span class="line-number">18.</span> current = current->right;
<span class="line-number">19.</span> }
<span class="line-number">20.</span> }
<span class="line-number">21.</span> reverseTraverseRightEdge(root); <span style="color:green">// final right edge</span>
<span class="line-number">22.</span> <span style="color:blue">return</span>;
<span class="line-number">23.</span> }
<span class="line-number">24.</span>
<span class="line-number">25.</span> <span style="color:blue">TreeNode</span>* reverse(<span style="color:blue">TreeNode</span>* head) {
<span class="line-number">26.</span> <span style="color:blue">TreeNode</span>* prev = <span style="color:blue">nullptr</span>;
<span class="line-number">27.</span> <span style="color:blue">TreeNode</span>* next = <span style="color:blue">nullptr</span>;
<span class="line-number">28.</span> <span style="color:blue">while</span> (head != <span style="color:blue">nullptr</span>) {
<span class="line-number">29.</span> next = head->right;
<span class="line-number">30.</span> head->right = prev;
<span class="line-number">31.</span> prev = head;
<span class="line-number">32.</span> head = next;
<span class="line-number">33.</span> }
<span class="line-number">34.</span> <span style="color:blue">return</span> prev;
<span class="line-number">35.</span> }
<span class="line-number">36.</span>
<span class="line-number">37.</span> <span style="color:blue">void</span> reverseTraverseRightEdge(<span style="color:blue">TreeNode</span>* head) {
<span class="line-number">38.</span> <span style="color:blue">TreeNode</span>* tail = reverse(head);
<span class="line-number">39.</span> <span style="color:blue">TreeNode</span>* current = tail;
<span class="line-number">40.</span> <span style="color:blue">while</span> (current != <span style="color:blue">nullptr</span>) {
<span class="line-number">41.</span> std::cout << current->val << " ";
<span class="line-number">42.</span> current = current->right;
<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">45.</span> }</code></pre>
</div>
<div class="tree-container">
<div class="color-key">
<div class="color-item">
<div class="color-box" style="background-color: #ffeb3b;"></div>
<span>Current Node</span>
</div>
<div class="color-item">
<div class="color-box" style="background-color: #2196f3;"></div>
<span>Rightmost Node</span>
</div>
<div class="color-item">
<div class="color-box" style="background-color: #4caf50;"></div>
<span>Visited Node</span>
</div>
<div class="color-item">
<div class="color-box" style="background-color: #03a9f4;"></div>
<span>Thread Created</span>
</div>
<div class="color-item">
<div class="color-arrow" style="background-color: #888;"></div>
<span>Tree Edge</span>
</div>
<div class="color-item">
<div class="dotted-line"></div>
<span>Thread Edge</span>
</div>
<div class="color-item">
<div class="color-arrow" style="background-color: #e91e63;"></div>
<span>Reversed Edge</span>
</div>
</div>
<button id="nextStep">Next Step</button>
<div id="container"></div>
<div class="explanation" id="treeExplanation">
Post-order traversal output: <span id="output"></span>
</div>
</div>
</div>
<script>
// 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: <span id="output">' +
document.getElementById('output').innerText + '</span><br>' + 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;
updateExplanation("Reversing the right edge starting from 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 PostOrder 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 in left subtree");
await waitForNext();
let rightmost = current.left;
highlightNode(rightmost, '#2196f3');
layer.draw();
while (rightmost.right !== null && rightmost.right !== current) {
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 + " - 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;
}
} 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();
}
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() {
document.getElementById('nextStep').disabled = false;
postorderTraversal(node1);
};
</script>
</body>
</html>