updated with pointers

This commit is contained in:
Jidong Xiao
2025-04-15 16:37:08 -04:00
committed by JamesFlare1212
parent 6670134276
commit 727b4fccd0

View File

@@ -5,33 +5,25 @@
<title>Morris PostOrder Traversal Animation & Code Display</title> <title>Morris PostOrder Traversal Animation & Code Display</title>
<style> <style>
html, body { margin: 0; padding: 0; font-family: Arial, sans-serif; } html, body { margin: 0; padding: 0; font-family: Arial, sans-serif; }
h1 { text-align: center; margin-top: 20px; } h1 { text-align: center; margin-top: 10px; margin-bottom: 5px; }
p { text-align: center; margin: 10px 0 30px; } p { text-align: center; margin: 5px 0 15px; }
.container {
display: flex;
justify-content: space-between;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.visualization-container { .visualization-container {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
max-width: 1200px; max-width: 1200px;
margin: 0 auto; margin: 0 auto;
height: 80vh; height: 88vh;
} }
.code-container { .code-container {
background-color: #f0f0f0; background-color: #f0f0f0;
border-radius: 10px; border-radius: 10px;
padding: 20px; padding: 15px;
width: 48%; width: 48%;
overflow-y: auto; overflow-y: auto;
height: 80vh; height: 88vh;
font-size: 16px; font-size: 15px;
} }
pre { pre {
@@ -54,16 +46,16 @@
.tree-container { .tree-container {
background-color: #f0f0f0; background-color: #f0f0f0;
border-radius: 10px; border-radius: 10px;
padding: 20px; padding: 15px;
width: 48%; width: 48%;
height: 80vh; height: 88vh;
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
#nextStep { #nextStep {
margin: 10px; margin: 10px auto;
padding: 8px 15px; padding: 8px 15px;
font-size: 16px; font-size: 16px;
width: fit-content; width: fit-content;
@@ -72,31 +64,72 @@
color: white; color: white;
border: none; border: none;
border-radius: 4px; border-radius: 4px;
display: block;
} }
#nextStep:hover { #nextStep:hover { background-color: #45a049; }
background-color: #45a049; #nextStep:disabled { background-color: #cccccc; cursor: not-allowed; }
}
#nextStep:disabled { #container { flex-grow: 1; width: 100%; }
background-color: #cccccc;
cursor: not-allowed;
}
#container {
flex-grow: 1;
width: 100%;
}
.explanation { .explanation {
margin-top: 15px; margin-top: 10px;
font-size: 16px; font-size: 14px;
padding: 15px; padding: 10px;
background-color: #e8e8e8; background-color: #e8e8e8;
border-radius: 5px; 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> </style>
<!-- Load Konva.js from CDN --> <!-- Load Konva.js -->
<script src="https://cdn.jsdelivr.net/npm/konva@8.3.13/konva.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/konva@8.3.13/konva.min.js"></script>
</head> </head>
<body> <body>
@@ -126,7 +159,7 @@
<span class="line-number">18.</span> current = current->right; <span class="line-number">18.</span> current = current->right;
<span class="line-number">19.</span> } <span class="line-number">19.</span> }
<span class="line-number">20.</span> } <span class="line-number">20.</span> }
<span class="line-number">21.</span> reverseTraverseRightEdge(root); <span style="color:green">// traverse the final right edge</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">22.</span> <span style="color:blue">return</span>;
<span class="line-number">23.</span> } <span class="line-number">23.</span> }
<span class="line-number">24.</span> <span class="line-number">24.</span>
@@ -149,11 +182,43 @@
<span class="line-number">41.</span> std::cout << current->val << " "; <span class="line-number">41.</span> std::cout << current->val << " ";
<span class="line-number">42.</span> current = current->right; <span class="line-number">42.</span> current = current->right;
<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 the original tree structure</span> <span class="line-number">44.</span> reverse(tail); <span style="color:green">// restore structure</span>
<span class="line-number">45.</span> }</code></pre> <span class="line-number">45.</span> }</code></pre>
</div> </div>
<div class="tree-container"> <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> <button id="nextStep">Next Step</button>
<div id="container"></div> <div id="container"></div>
<div class="explanation" id="treeExplanation"> <div class="explanation" id="treeExplanation">
Post-order traversal output: <span id="output"></span> Post-order traversal output: <span id="output"></span>
@@ -213,46 +278,93 @@
fontFamily: 'Arial', fontFamily: 'Arial',
fill: 'black' fill: 'black'
}); });
this.leftEdge = null;
this.rightEdge = null;
this.threadEdge = null;
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 - 40, 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);
// Draw an edge between two nodes. // Calculate connection points for arrow drawing.
function drawEdge(parent, child, color = '#888') { function calculateConnectionPoints(fromNode, toNode) {
const line = new Konva.Line({ const nodeRadius = 20;
points: [parent.x, parent.y, child.x, child.y], const dx = toNode.x - fromNode.x;
stroke: color, const dy = toNode.y - fromNode.y;
strokeWidth: 2 const angle = Math.atan2(dy, dx);
}); return {
layer.add(line); fromX: fromNode.x + nodeRadius * Math.cos(angle),
return line; 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. // Create an arrow between two nodes.
function highlightNode(node, color = '#8bc34a') { function createArrow(fromNode, toNode, color = '#888', dashed = false) {
if (node && node.shape) { const points = calculateConnectionPoints(fromNode, toNode);
node.shape.to({ fill: color, duration: 0.25 }); 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') { function unhighlightNode(node, color = 'white') {
if (node && node.shape) { if (node && node.shape) { node.shape.to({ fill: color, duration: 0.25 }); }
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 = 50; const topY = 40;
const levelHeight = stageHeight / 4; const levelHeight = stageHeight / 4;
// Build the 9-node binary tree. // Build the 9-node binary tree.
@@ -281,15 +393,14 @@
layer.add(n.label); layer.add(n.label);
}); });
const edges = []; drawEdge(node1, node2, true);
edges.push(drawEdge(node1, node2)); drawEdge(node1, node3, false);
edges.push(drawEdge(node1, node3)); drawEdge(node2, node4, true);
edges.push(drawEdge(node2, node4)); drawEdge(node2, node5, false);
edges.push(drawEdge(node2, node5)); drawEdge(node5, node6, true);
edges.push(drawEdge(node5, node6)); drawEdge(node5, node7, false);
edges.push(drawEdge(node5, node7)); drawEdge(node3, node8, false);
edges.push(drawEdge(node3, node8)); drawEdge(node8, node9, true);
edges.push(drawEdge(node8, node9));
nodes.forEach(n => { nodes.forEach(n => {
n.shape.moveToTop(); n.shape.moveToTop();
@@ -297,11 +408,81 @@
}); });
layer.draw(); layer.draw();
// Reverse a chain of right pointers. // Visualize the reversal process.
function reverseChain(head) { 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 prev = null;
let curr = head; current = head;
let next; 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) { while (curr !== null) {
next = curr.right; next = curr.right;
curr.right = prev; curr.right = prev;
@@ -314,19 +495,15 @@
// Reverse traverse the right edge of a subtree. // Reverse traverse the right edge of a subtree.
async function reverseTraverseRightEdge(head) { async function reverseTraverseRightEdge(head) {
if (!head) return; if (!head) return;
updateExplanation("Reversing the right edge starting from node " + head.val); updateExplanation("Reversing the right edge starting from node " + head.val);
await waitForNext(); await waitForNext();
let tail = await reverseChain(head);
let tail = reverseChain(head);
layer.draw(); layer.draw();
updateExplanation("Visiting nodes along the reversed right edge"); updateExplanation("Visiting nodes along the reversed right edge");
let cur = tail; let cur = tail;
while (cur !== null) { while (cur !== null) {
highlightNode(cur, '#4caf50'); highlightNode(cur, '#4caf50');
layer.draw(); layer.draw();
appendOutput(cur.val + " "); appendOutput(cur.val + " ");
updateExplanation("Visited node: " + cur.val); updateExplanation("Visited node: " + cur.val);
await waitForNext(); await waitForNext();
@@ -334,58 +511,50 @@
layer.draw(); layer.draw();
cur = cur.right; cur = cur.right;
} }
updateExplanation("Restoring the original right edge"); updateExplanation("Restoring the original right edge");
await waitForNext(); await waitForNext();
await restoreChain(tail);
reverseChain(tail);
layer.draw(); layer.draw();
} }
// Morris PostOrder Traversal Animation. // Morris PostOrder Traversal Animation.
async function postorderTraversal(root) { async function postorderTraversal(root) {
let current = root; let current = root;
while (current !== null) { while (current !== null) {
highlightNode(current, '#ffeb3b'); highlightNode(current, '#ffeb3b');
layer.draw(); layer.draw();
if (current.left !== null) { 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(); await waitForNext();
let rightmost = current.left; let rightmost = current.left;
highlightNode(rightmost, '#2196f3'); highlightNode(rightmost, '#2196f3');
layer.draw(); layer.draw();
while (rightmost.right !== null && rightmost.right !== current) { 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(); await waitForNext();
unhighlightNode(rightmost, 'white'); unhighlightNode(rightmost, 'white');
rightmost = rightmost.right; rightmost = rightmost.right;
highlightNode(rightmost, '#2196f3'); highlightNode(rightmost, '#2196f3');
layer.draw(); layer.draw();
} }
if (rightmost.right === null) { if (rightmost.right === null) {
updateExplanation("Rightmost: " + rightmost.val + " - Creating thread to current: " + current.val); updateExplanation("Rightmost: " + rightmost.val + " - Creating thread to current: " + current.val);
await waitForNext(); await waitForNext();
createThreadEdge(rightmost, current);
rightmost.right = current; rightmost.right = current;
highlightNode(rightmost, '#03a9f4'); highlightNode(rightmost, '#03a9f4');
layer.draw(); layer.draw();
await waitForNext();
unhighlightNode(rightmost, 'white');
unhighlightNode(current, 'white'); unhighlightNode(current, 'white');
current = current.left; current = current.left;
} else { } else {
updateExplanation("Thread detected at rightmost: " + rightmost.val + " - Processing subtree"); updateExplanation("Thread detected at rightmost: " + rightmost.val + " - Removing thread and processing subtree");
await waitForNext(); await waitForNext();
removeThreadEdge(rightmost);
rightmost.right = null; rightmost.right = null;
unhighlightNode(rightmost, 'white'); unhighlightNode(rightmost, 'white');
layer.draw(); layer.draw();
await reverseTraverseRightEdge(current.left); await reverseTraverseRightEdge(current.left);
unhighlightNode(current, 'white'); unhighlightNode(current, 'white');
current = current.right; current = current.right;
@@ -393,25 +562,25 @@
} else { } else {
updateExplanation("Current: " + current.val + " - No left child, moving right"); updateExplanation("Current: " + current.val + " - No left child, moving right");
await waitForNext(); await waitForNext();
unhighlightNode(current, 'white'); unhighlightNode(current, 'white');
current = current.right; current = current.right;
} }
} }
updateExplanation("Processing final right edge"); updateExplanation("Processing final right edge");
await waitForNext(); await waitForNext();
await reverseTraverseRightEdge(root); await reverseTraverseRightEdge(root);
updateExplanation("Morris Post-Order Traversal complete"); updateExplanation("Morris Post-Order Traversal complete");
await waitForNext(); await waitForNext();
updateExplanation("Final post-order traversal result: " + document.getElementById('output').innerText); 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() { 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); stage.height(document.querySelector('.tree-container').clientHeight - 140);
layer.draw(); layer.draw();
}); });