diff --git a/avl.js b/avl.js index 9479c13..95344c9 100644 --- a/avl.js +++ b/avl.js @@ -41,14 +41,15 @@ class AVLTree extends RankedTree { if (rotatingAroundRoot) { this.root = newRoot; } - this.record("Fixing 0-child by single rotation: by z"); + this.record("Fixing 0-child by single rotation: by z", z); z.demote(); - this.record("Fixing 0-child by single rotation: demoting z"); + this.record("Fixing 0-child by single rotation: demoting z", z); } else if (nodeDifference(y) == 1) { rotateLeft(x); this.record( - "Fixing 0-child by double-rotation: first rotation by x" + "Fixing 0-child by double-rotation: first rotation by x", + x ); newRoot = rotateRight(z); @@ -56,17 +57,18 @@ class AVLTree extends RankedTree { this.root = newRoot; } this.record( - "Fixing 0-child by double-rotation: second rotation by z" + "Fixing 0-child by double-rotation: second rotation by z", + z ); y.promote(); - this.record("Fixing 0-child by double-rotation: promoting y"); + this.record("Fixing 0-child by double-rotation: promoting y", y); x.demote(); - this.record("Fixing 0-child by double-rotation: demoting x"); + this.record("Fixing 0-child by double-rotation: demoting x", x); z.demote(); - this.record("Fixing 0-child by double-rotation: demoting z"); + this.record("Fixing 0-child by double-rotation: demoting z", z); } } @@ -74,10 +76,12 @@ class AVLTree extends RankedTree { let diffs = nodeDifferences(x.parent).sort(); while (x.parent && diffs.equals([0, 1])) { x.parent.promote(); + this.record( + "Insertion rebalance: promoting (0, 1) parent", + x.parent + ); + x = x.parent; - - this.record("Insertion rebalance: promoting (0, 1) parent"); - diffs = nodeDifferences(x.parent).sort(); } @@ -126,7 +130,7 @@ class AVLTree extends RankedTree { break; default: rotateRight(y); - this.record("AVL deletion, fixing by double-rotation: by y"); + this.record("AVL deletion, fixing by double-rotation: by y", y); newRoot = rotateLeft(x); break; @@ -134,13 +138,13 @@ class AVLTree extends RankedTree { if (rotatingAroundRoot) { this.root = newRoot; } - this.record("AVL deletion, fixing by rotation by x"); + this.record("AVL deletion, fixing by rotation by x", x); [newRoot.left, newRoot.right, newRoot] .filter((x) => x) .forEach((n) => { updateRank(n); - this.record("Updating rank of nodes affected by rotations"); + this.record("Updating rank of nodes affected by rotations", n); }); return factor == 0; @@ -154,7 +158,8 @@ class AVLTree extends RankedTree { case 0: updateRank(x); this.record( - "Updating rank of node affected by the propagation of delete" + "Updating rank of node affected by the propagation of delete", + x ); return false; diff --git a/ranked_tree.js b/ranked_tree.js index 86c3bf3..45b4d64 100644 --- a/ranked_tree.js +++ b/ranked_tree.js @@ -22,7 +22,7 @@ class RankedTree { this.recorder = null; } - _getUnwrappedGraph() { + _getUnwrappedGraph(highlightedNode) { let result = []; let q = new Queue(); @@ -36,7 +36,11 @@ class RankedTree { } let [value, rank] = [node.value, node.rank]; - result.push(`\t"Node(${value})" [label="${value}, ${rank}"];`); + let highlight = + node === highlightedNode ? ', color="blue", penwidth=3' : ""; + result.push( + `\t"Node(${value})" [label="${value}, ${rank}"${highlight}];` + ); [node.left, node.right] .filter((child) => child) @@ -60,19 +64,19 @@ class RankedTree { return result; } - toDot() { - return ["digraph {", ...this._getUnwrappedGraph(), "}"]; + toDot(highlightedNode) { + return ["digraph {", ...this._getUnwrappedGraph(highlightedNode), "}"]; } toString() { return this.toDot().join("\n"); } - record(message) { + record(message, highlightedNode) { if (!this.recorder) { return; } - this.recorder.record(this, message); + this.recorder.record(this, message, highlightedNode); } rank() { @@ -92,7 +96,7 @@ class RankedTree { if (!this.root) { this.root = insertedNode; - this.record("Inserting key to the root"); + this.record("Inserting key to the root", insertedNode); return; } @@ -108,7 +112,7 @@ class RankedTree { } else { parent.right = insertedNode; } - this.record("Inserting key"); + this.record("Inserting key", insertedNode); this.insertRebalance(insertedNode); } @@ -147,13 +151,14 @@ class RankedTree { node.value = successor.value; successor.value = null; this.record( - "Replacing the value of deleted node with successor value" + "Replacing the value of deleted node with successor value", + node ); return this.deleteNode(successor); } - this.record("Replacing the node with one of its children"); + this.record("Replacing the node with one of its children", node); return [y, parent]; } diff --git a/recorder.js b/recorder.js index a0c270d..ff71bb5 100644 --- a/recorder.js +++ b/recorder.js @@ -1,4 +1,4 @@ -const DURATION = 500; +const DURATION = 750; class Record { constructor(tree, message) { @@ -81,9 +81,11 @@ class Recorder { throw "not implemented!"; } - record(tree, message) { + record(tree, message, highlightedNode) { // TODO: adjust join if necessary - this.states.push(new Record(tree.toDot().join(""), message)); + this.states.push( + new Record(tree.toDot(highlightedNode).join(""), message) + ); this.render(); } } diff --git a/wavl.js b/wavl.js index 51eb31e..33a09f7 100644 --- a/wavl.js +++ b/wavl.js @@ -37,25 +37,28 @@ class WAVLTree extends AVLTree { this.root = newRoot; } this.record( - "Final step of deletion rebalance: single rotation by parent of y" + "Final step of deletion rebalance: single rotation by parent of y", + y.parent ); y.promote(); - this.record("Final step of deletion rebalance: promotion of y"); + this.record("Final step of deletion rebalance: promotion of y", y); z.demote(); - this.record("Final step of deletion rebalance: demotion of z"); + this.record("Final step of deletion rebalance: demotion of z", z); if (z.type() == LEAF) { z.demote(); this.record( - "Final step of deletion rebalance: demotion of leaf z" + "Final step of deletion rebalance: demotion of leaf z", + z ); } } else if (wDiff == 2 && v && v.parent) { rotateRight(v.parent); this.record( - "Final step of deletion rebalance: first of double rotation by parent of v" + "Final step of deletion rebalance: first of double rotation by parent of v", + v.parent ); newRoot = rotateLeft(v.parent); @@ -63,20 +66,23 @@ class WAVLTree extends AVLTree { this.root = newRoot; } this.record( - "Final step of deletion rebalance: second of double rotation by parent of v" + "Final step of deletion rebalance: second of double rotation by parent of v", + v.parent ); v.promote().promote(); this.record( - "Final step of deletion rebalance: double promotion of v" + "Final step of deletion rebalance: double promotion of v", + v ); y.demote(); - this.record("Final step of deletion rebalance: demotion of y"); + this.record("Final step of deletion rebalance: demotion of y", y); z.demote().demote(); this.record( - "Final step of deletion rebalance: double demotion of z" + "Final step of deletion rebalance: double demotion of z", + z ); } } @@ -97,11 +103,11 @@ class WAVLTree extends AVLTree { (yDiff == 2 || nodeDifferences(y).equals([2, 2])) ) { parent.demote(); - this.record("Propagating error by demoting parent"); + this.record("Propagating error by demoting parent", parent); if (yDiff != 2) { y.demote(); - this.record("Demoting y"); + this.record("Demoting y", y); } x = parent; @@ -151,12 +157,18 @@ class WAVLTree extends AVLTree { deleteFixup(y, parent) { if (nodeDifferences(y).equals([2, 2])) { y.demote(); - this.record("Starting deletion rebalance by demoting (2, 2) node"); + this.record( + "Starting deletion rebalance by demoting (2, 2) node", + y + ); parent = y.parent; } else if (nodeDifferences(parent).equals([2, 2])) { parent.demote(); - this.record("Starting deletion rebalance by demoting (2, 2) node"); + this.record( + "Starting deletion rebalance by demoting (2, 2) node", + parent + ); parent = parent.parent; }