updating morri post order
This commit is contained in:
committed by
JamesFlare1212
parent
efc2fbaaa3
commit
083ae76743
@@ -3,143 +3,10 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Morris Post‑Order Traversal Animation & Code Display</title>
|
<title>Morris Post‑Order Traversal Animation & Code Display</title>
|
||||||
<style>
|
<link rel="stylesheet" href="style.css">
|
||||||
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: 87vh;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-line {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-line.highlighted {
|
|
||||||
background-color: #ffeb3b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-container {
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 15px;
|
|
||||||
width: 48%;
|
|
||||||
height: 80vh;
|
|
||||||
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 -->
|
<!-- 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>
|
||||||
|
<script src="script.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Morris Post-Order Traversal Visualization</h1>
|
<h1>Morris Post-Order Traversal Visualization</h1>
|
||||||
@@ -189,531 +56,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
|
||||||
// Define code content
|
|
||||||
const codeContent = [
|
|
||||||
'<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> }'
|
|
||||||
];
|
|
||||||
|
|
||||||
// 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: <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;
|
|
||||||
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) {
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
while (current !== null) {
|
|
||||||
highlightLine(3);
|
|
||||||
updateExplanation("Checking if current is null");
|
|
||||||
await waitForNext();
|
|
||||||
|
|
||||||
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');
|
|
||||||
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');
|
|
||||||
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;
|
|
||||||
} 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');
|
|
||||||
layer.draw();
|
|
||||||
|
|
||||||
highlightLine(14);
|
|
||||||
updateExplanation("Processing left subtree of current (" + current.val + ")");
|
|
||||||
await waitForNext();
|
|
||||||
|
|
||||||
await reverseTraverseRightEdge(current.left);
|
|
||||||
|
|
||||||
highlightLine(15);
|
|
||||||
updateExplanation("Moving current to its right child");
|
|
||||||
await waitForNext();
|
|
||||||
|
|
||||||
unhighlightNode(current, 'white');
|
|
||||||
current = current.right;
|
|
||||||
}
|
|
||||||
} 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 => {
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
561
animations/trees/morris/script.js
Normal file
561
animations/trees/morris/script.js
Normal file
@@ -0,0 +1,561 @@
|
|||||||
|
// Define code content
|
||||||
|
const codeContent = [
|
||||||
|
'<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> }'
|
||||||
|
];
|
||||||
|
|
||||||
|
// 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: <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;
|
||||||
|
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);
|
||||||
|
};
|
||||||
166
animations/trees/morris/style.css
Normal file
166
animations/trees/morris/style.css
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
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: 1600px; /* Increased from 1200px */
|
||||||
|
margin: 0 auto;
|
||||||
|
height: 88vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-container {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 15px;
|
||||||
|
width: 55%; /* Increased from 48% */
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 87vh;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: Consolas, "Courier New", monospace;
|
||||||
|
white-space: pre;
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 750px; /* Ensure code is wide enough for longest line */
|
||||||
|
}
|
||||||
|
|
||||||
|
#codeDisplay {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-number {
|
||||||
|
color: #666;
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-line {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fix for line highlighting - make sure the entire line is highlighted */
|
||||||
|
.code-line.highlighted {
|
||||||
|
background-color: #ffeb3b;
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-container {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 15px;
|
||||||
|
width: 42%; /* Reduced from 48% to compensate for code container increase */
|
||||||
|
height: 80vh;
|
||||||
|
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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user