feat: implement recorder and visualization
Signed-off-by: Matej Focko <mfocko@redhat.com>
This commit is contained in:
parent
70c3b55f1a
commit
eb0b2a39f6
5 changed files with 190 additions and 729 deletions
82
avl.js
82
avl.js
|
@ -54,22 +54,33 @@ class AVLTree extends RankedTree {
|
|||
);
|
||||
}
|
||||
|
||||
fix0Child(x, y, z, rotateLeft, rotateRight) {
|
||||
fix0Child(x, y, z, rotatingAroundRoot, rotateLeft, rotateRight) {
|
||||
let newRoot = x.parent;
|
||||
|
||||
if (!y || nodeDifference(y) == 2) {
|
||||
newRoot = rotateRight(z);
|
||||
z.demote();
|
||||
|
||||
if (rotatingAroundRoot) {
|
||||
this.root = newRoot;
|
||||
}
|
||||
this.record();
|
||||
} else if (nodeDifference(y) == 1) {
|
||||
rotateLeft(x);
|
||||
this.record();
|
||||
|
||||
newRoot = rotateRight(z);
|
||||
if (rotatingAroundRoot) {
|
||||
this.root = newRoot;
|
||||
}
|
||||
this.record();
|
||||
|
||||
y.promote();
|
||||
x.demote();
|
||||
z.demote();
|
||||
}
|
||||
|
||||
return newRoot;
|
||||
this.record();
|
||||
}
|
||||
}
|
||||
|
||||
insertRebalance(x) {
|
||||
|
@ -78,6 +89,8 @@ class AVLTree extends RankedTree {
|
|||
x.parent.promote();
|
||||
x = x.parent;
|
||||
|
||||
this.record();
|
||||
|
||||
diffs = nodeDifferences(x.parent).sort();
|
||||
}
|
||||
|
||||
|
@ -86,7 +99,6 @@ class AVLTree extends RankedTree {
|
|||
}
|
||||
|
||||
let rotatingAroundRoot = x.parent.parent == null;
|
||||
let newRoot = x.parent;
|
||||
|
||||
let rankDifference = nodeDifference(x);
|
||||
if (rankDifference != 0) {
|
||||
|
@ -96,18 +108,24 @@ class AVLTree extends RankedTree {
|
|||
let xParent = x.parent;
|
||||
|
||||
if (rankDifference == 0 && x.parent.left === x) {
|
||||
newRoot = this.fix0Child(
|
||||
x, x.right, xParent, rotateLeft, rotateRight
|
||||
this.fix0Child(
|
||||
x,
|
||||
x.right,
|
||||
xParent,
|
||||
rotatingAroundRoot,
|
||||
rotateLeft,
|
||||
rotateRight
|
||||
);
|
||||
} else if (rankDifference == 0 && x.parent.right === x) {
|
||||
newRoot = this.fix0Child(
|
||||
x, x.left, xParent, rotateRight, rotateLeft
|
||||
this.fix0Child(
|
||||
x,
|
||||
x.left,
|
||||
xParent,
|
||||
rotatingAroundRoot,
|
||||
rotateRight,
|
||||
rotateLeft
|
||||
);
|
||||
}
|
||||
|
||||
if (rotatingAroundRoot) {
|
||||
this.root = newRoot;
|
||||
}
|
||||
}
|
||||
|
||||
deleteRotate(x, y, leaning, rotatingAroundRoot, rotateLeft, rotateRight) {
|
||||
|
@ -124,14 +142,18 @@ class AVLTree extends RankedTree {
|
|||
newRoot = rotateLeft(x);
|
||||
break;
|
||||
}
|
||||
|
||||
[newRoot.left, newRoot.right, newRoot].filter(x => x).forEach(n => {
|
||||
updateRank(n)
|
||||
});
|
||||
|
||||
if (rotatingAroundRoot) {
|
||||
this.root = newRoot;
|
||||
}
|
||||
this.record();
|
||||
|
||||
[newRoot.left, newRoot.right, newRoot]
|
||||
.filter((x) => x)
|
||||
.forEach((n) => {
|
||||
updateRank(n);
|
||||
this.record();
|
||||
});
|
||||
|
||||
return factor == 0;
|
||||
}
|
||||
|
||||
|
@ -142,6 +164,9 @@ class AVLTree extends RankedTree {
|
|||
switch (factor) {
|
||||
case 0:
|
||||
updateRank(x);
|
||||
|
||||
this.record();
|
||||
|
||||
return false;
|
||||
case -1:
|
||||
case 1:
|
||||
|
@ -149,12 +174,29 @@ class AVLTree extends RankedTree {
|
|||
}
|
||||
|
||||
let rotatingAroundRoot = x.parent == null;
|
||||
let [z, leaning, toLeft, toRight] = [x.left, -1, rotateRight, rotateLeft];
|
||||
let [z, leaning, toLeft, toRight] = [
|
||||
x.left,
|
||||
-1,
|
||||
rotateRight,
|
||||
rotateLeft,
|
||||
];
|
||||
if (factor == 2) {
|
||||
[z, leaning, toLeft, toRight] = [x.right, 1, rotateLeft, rotateRight];
|
||||
[z, leaning, toLeft, toRight] = [
|
||||
x.right,
|
||||
1,
|
||||
rotateLeft,
|
||||
rotateRight,
|
||||
];
|
||||
}
|
||||
|
||||
return this.deleteRotate(x, z, leaning, rotatingAroundRoot, toLeft, toRight);
|
||||
return this.deleteRotate(
|
||||
x,
|
||||
z,
|
||||
leaning,
|
||||
rotatingAroundRoot,
|
||||
toLeft,
|
||||
toRight
|
||||
);
|
||||
}
|
||||
|
||||
deleteRebalance(node, parent) {
|
||||
|
|
|
@ -19,10 +19,11 @@ class Queue {
|
|||
class RankedTree {
|
||||
constructor() {
|
||||
this.root = null;
|
||||
this.recorder = null;
|
||||
}
|
||||
|
||||
_getUnwrappedGraph() {
|
||||
let result = "";
|
||||
let result = [];
|
||||
|
||||
let q = new Queue();
|
||||
q.enqueue(this.root);
|
||||
|
@ -34,24 +35,45 @@ class RankedTree {
|
|||
continue;
|
||||
}
|
||||
|
||||
result += `"${node.toString()}" [label="${node.value}, ${node.rank}"];\n`;
|
||||
result.push(
|
||||
`\t"${node.toString()}" [label="${node.value}, ${node.rank}"];`
|
||||
);
|
||||
|
||||
[node.left, node.right].filter(child => child).forEach(child => {
|
||||
[node.left, node.right]
|
||||
.filter((child) => child)
|
||||
.forEach((child) => {
|
||||
edges.push([node, child]);
|
||||
q.enqueue(child);
|
||||
});
|
||||
}
|
||||
|
||||
edges.forEach(vertices => {
|
||||
result.push("");
|
||||
|
||||
edges.forEach((vertices) => {
|
||||
let [u, v] = vertices;
|
||||
result += `"${u.toString()}" -> "${v.toString()}" [label="${nodeDifference(v)}"]\n`;
|
||||
result.push(
|
||||
`\t"${u.toString()}" -> "${v.toString()}" [label="${nodeDifference(
|
||||
v
|
||||
)}"]`
|
||||
);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
toDot() {
|
||||
return ["digraph {", ...this._getUnwrappedGraph(), "}"];
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `digraph {\n${this._getUnwrappedGraph()}}\n`;
|
||||
return this.toDot().join("\n");
|
||||
}
|
||||
|
||||
record() {
|
||||
if (!this.recorder) {
|
||||
return;
|
||||
}
|
||||
this.recorder.record(this);
|
||||
}
|
||||
|
||||
rank() {
|
||||
|
@ -71,6 +93,7 @@ class RankedTree {
|
|||
|
||||
if (!this.root) {
|
||||
this.root = insertedNode;
|
||||
this.record();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -86,6 +109,7 @@ class RankedTree {
|
|||
} else {
|
||||
parent.right = insertedNode;
|
||||
}
|
||||
this.record();
|
||||
|
||||
this.insertRebalance(insertedNode);
|
||||
}
|
||||
|
@ -134,6 +158,8 @@ class RankedTree {
|
|||
node.left.parent = successor;
|
||||
}
|
||||
|
||||
this.record();
|
||||
|
||||
return [y, parent];
|
||||
}
|
||||
|
||||
|
|
541
tree-creation.js
541
tree-creation.js
|
@ -1,541 +0,0 @@
|
|||
var id = 1;
|
||||
|
||||
var rootNode = {
|
||||
id: id++,
|
||||
key: null,
|
||||
rank: -1,
|
||||
parent: null,
|
||||
children: []
|
||||
};
|
||||
var changeRoot = false;
|
||||
|
||||
// Find tree's height
|
||||
var findHeight = function (node) {
|
||||
if (node.data === null || !node) return 0;
|
||||
else {
|
||||
var left = node.children[0] ? findHeight(node.children[0]) : 0;
|
||||
var right = node.children[1] ? findHeight(node.children[1]) : 0;
|
||||
return 1 + ((left > right) ? left : right);
|
||||
}
|
||||
};
|
||||
|
||||
var getHeight = function (node) {
|
||||
if (node === null) {
|
||||
return -1;
|
||||
}
|
||||
return node.rank;
|
||||
}
|
||||
|
||||
// Binary Search Tree rotation
|
||||
var rotateLeft = function (node, callback) {
|
||||
var parent = node.parent,
|
||||
leftChild = node.children[0],
|
||||
rightChild = node.children[1];
|
||||
|
||||
if (rightChild.children.length === 0) {
|
||||
rightChild.children.push({ id: id++, key: null, rank: -1, parent: rightChild, children: [] });
|
||||
rightChild.children.push({ id: id++, key: null, rank: -1, parent: rightChild, children: [] });
|
||||
}
|
||||
|
||||
rightChild.children[0].parent = node;
|
||||
node.children[1] = rightChild.children[0];
|
||||
node.parent = rightChild;
|
||||
rightChild.children[0] = node;
|
||||
rightChild.parent = parent;
|
||||
|
||||
if (parent === null) { // Root node
|
||||
rootNode = rightChild;
|
||||
|
||||
gLinks.selectAll('path').filter(function (d) { // Update root node
|
||||
return d.data.id === node.parent.id;
|
||||
}).datum(d3.hierarchy(node).descendants()[0]);
|
||||
changeRoot = true;
|
||||
|
||||
} else if (node === parent.children[0]) { // Left child of parent
|
||||
parent.children[0] = rightChild;
|
||||
changeRoot = false;
|
||||
|
||||
} else if (node === parent.children[1]) { // Right child of parent
|
||||
parent.children[1] = rightChild;
|
||||
changeRoot = false;
|
||||
}
|
||||
|
||||
if (node.children.length !== 0 && node.children[0].key === null && node.children[1].key === null) node.children = [];
|
||||
|
||||
setTimeout(function () {
|
||||
if (callback instanceof Function) {
|
||||
callback();
|
||||
}
|
||||
}, duration);
|
||||
|
||||
return rightChild;
|
||||
};
|
||||
|
||||
var rotateRight = function (node, callback) {
|
||||
var parent = node.parent,
|
||||
leftChild = node.children[0],
|
||||
rightChild = node.children[1];
|
||||
|
||||
if (leftChild.children.length === 0) {
|
||||
leftChild.children.push({ id: id++, key: null, rank: -1, parent: leftChild, children: [] });
|
||||
leftChild.children.push({ id: id++, key: null, rank: -1, parent: leftChild, children: [] });
|
||||
}
|
||||
|
||||
if (parent === null) { // Root node
|
||||
leftChild.children[1].parent = node;
|
||||
node.children[0] = leftChild.children[1];
|
||||
|
||||
node.parent = leftChild;
|
||||
leftChild.children[1] = node;
|
||||
|
||||
leftChild.parent = parent;
|
||||
rootNode = leftChild;
|
||||
|
||||
gLinks.selectAll('path').filter(function (d) { // Update root node's link
|
||||
return d.data.id === node.parent.id;
|
||||
}).datum(d3.hierarchy(node).descendants()[0]);
|
||||
changeRoot = true;
|
||||
|
||||
} else if (node === parent.children[0]) { // Left child of parent
|
||||
leftChild.children[1].parent = node;
|
||||
node.children[0] = leftChild.children[1];
|
||||
|
||||
node.parent = leftChild;
|
||||
leftChild.children[1] = node;
|
||||
|
||||
leftChild.parent = parent;
|
||||
parent.children[0] = leftChild;
|
||||
|
||||
changeRoot = false;
|
||||
|
||||
} else if (node === parent.children[1]) { // Left child of parent
|
||||
leftChild.children[1].parent = node;
|
||||
node.children[0] = leftChild.children[1];
|
||||
|
||||
node.parent = leftChild;
|
||||
leftChild.children[1] = node;
|
||||
|
||||
leftChild.parent = parent;
|
||||
parent.children[1] = leftChild;
|
||||
|
||||
changeRoot = false;
|
||||
}
|
||||
|
||||
if (node.children.length !== 0 && node.children[0].key === null && node.children[1].key === null) node.children = [];
|
||||
|
||||
setTimeout(function () {
|
||||
if (callback instanceof Function) {
|
||||
callback();
|
||||
}
|
||||
}, duration);
|
||||
|
||||
return leftChild;
|
||||
};
|
||||
|
||||
// Node highlight for better visualization
|
||||
var highlight = function (node) {
|
||||
var hlNode = gNodes.selectAll('circle').filter(function (d) {
|
||||
return d.data.id === node.id;
|
||||
});
|
||||
hlNode.transition()
|
||||
.duration(duration / 3)
|
||||
.style('stroke', '#e74c3c')
|
||||
.style('stroke-width', '3.5px');
|
||||
};
|
||||
|
||||
var removeHighlight = function (node) {
|
||||
var hlNode = gNodes.selectAll('circle').filter(function (d) {
|
||||
return d.data.id === node.id;
|
||||
});
|
||||
hlNode.transition()
|
||||
.duration(duration / 3)
|
||||
.style('stroke', '#3498db')
|
||||
.style('stroke-width', '2.5px');
|
||||
};
|
||||
|
||||
// AVL Tree balancing
|
||||
var balance = function (node, callback) {
|
||||
highlight(node);
|
||||
var hLeft = node.children[0] ? findHeight(node.children[0]) : 0;
|
||||
var hRight = node.children[1] ? findHeight(node.children[1]) : 0;
|
||||
var hl, hr,
|
||||
defer = 0.5;
|
||||
if (hLeft - hRight >= 2) { // Left unbalance
|
||||
var leftChild = node.children[0];
|
||||
hl = leftChild.children[0] ? findHeight(leftChild.children[0]) : 0;
|
||||
hr = leftChild.children[1] ? findHeight(leftChild.children[1]) : 0;
|
||||
if (hl >= hr) { // Left of left
|
||||
rotateRight(node, updateTree);
|
||||
defer = 1;
|
||||
} else { // Right of left
|
||||
defer = 3;
|
||||
rotateLeft(leftChild, function () {
|
||||
updateTree();
|
||||
setTimeout(function () {
|
||||
rotateRight(node, updateTree);
|
||||
}, duration);
|
||||
});
|
||||
}
|
||||
} else if (hRight - hLeft >= 2) { // Right unbalance
|
||||
rotated = false;
|
||||
isChanged = true;
|
||||
var rightChild = node.children[1];
|
||||
hl = rightChild.children[0] ? findHeight(rightChild.children[0]) : 0;
|
||||
hr = rightChild.children[1] ? findHeight(rightChild.children[1]) : 0;
|
||||
if (hr >= hl) { // Right of right
|
||||
rotateLeft(node, updateTree);
|
||||
defer = 1;
|
||||
} else { // Left of right
|
||||
defer = 3;
|
||||
rotateRight(rightChild, function () {
|
||||
updateTree();
|
||||
setTimeout(function () {
|
||||
rotateLeft(node, updateTree);
|
||||
}, duration);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!node.parent) {
|
||||
node.rank = 69;
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
removeHighlight(node);
|
||||
if (!node.parent) { // End balancing
|
||||
if (callback instanceof Function) callback();
|
||||
}
|
||||
else balance(node.parent, callback);
|
||||
}, duration * defer);
|
||||
};
|
||||
|
||||
var rank = function (node) {
|
||||
if (node === null) {
|
||||
return -1;
|
||||
}
|
||||
return node.rank;
|
||||
};
|
||||
|
||||
var difference = function (node, parent) {
|
||||
return rank(parent) - rank(node);
|
||||
};
|
||||
|
||||
var differences = function (node) {
|
||||
const nodeRank = rank(node);
|
||||
|
||||
const leftRank = node ? rank(node.children[0]) : -1;
|
||||
const rightRank = node ? rank(node.children[1]) : -1;
|
||||
|
||||
return [nodeRank - leftRank, nodeRank - rightRank];
|
||||
};
|
||||
|
||||
var fix0Child = function (x, y, z, rotateL, rotateR) {
|
||||
var newRoot = x.parent;
|
||||
|
||||
if (!y || difference(y, y.parent) == 2) {
|
||||
newRoot = rotateR(z, updateTree);
|
||||
z.rank--;
|
||||
} else if (difference(y, y.parent) == 1) {
|
||||
rotateL(x, function () {
|
||||
updateTree();
|
||||
setTimeout(function () {
|
||||
newRoot = rotateR(z, updateTree);
|
||||
}, duration);
|
||||
});
|
||||
|
||||
y.rank++;
|
||||
x.rank--;
|
||||
z.rank--;
|
||||
}
|
||||
|
||||
return newRoot;
|
||||
};
|
||||
|
||||
var insertFixup = function (x, callback) {
|
||||
highlight(x);
|
||||
|
||||
let [lDiff, rDiff] = differences(x.parent);
|
||||
|
||||
while (x.parent && ((lDiff == 0 && rDiff == 1) || (lDiff == 1 && rDiff == 0))) {
|
||||
console.log(`Node with key: ${x.parent.key} is ${lDiff},${rDiff}-node`);
|
||||
x.parent.rank++;
|
||||
|
||||
removeHighlight(x);
|
||||
x = x.parent;
|
||||
highlight(x);
|
||||
|
||||
[lDiff, rDiff] = differences(x.parent);
|
||||
}
|
||||
|
||||
console.log(`Rank of node with key=${x.key}: ${x.rank}`);
|
||||
console.log(`Parent node is ${lDiff},${rDiff}-node`);
|
||||
|
||||
if (x.parent === null) {
|
||||
console.log("exiting");
|
||||
setTimeout(function () {
|
||||
removeHighlight(x);
|
||||
if (callback instanceof Function) {
|
||||
callback();
|
||||
}
|
||||
}, duration * 3);
|
||||
return;
|
||||
}
|
||||
|
||||
const rotatingAroundRoot = x.parent.parent === null;
|
||||
var newRoot = x.parent;
|
||||
|
||||
var rankDifference = difference(x, x.parent);
|
||||
console.log(x);
|
||||
console.log(`Before fix 0-child: ${rankDifference}`);
|
||||
if (rankDifference != 0) {
|
||||
setTimeout(function () {
|
||||
removeHighlight(x);
|
||||
if (callback instanceof Function) {
|
||||
callback();
|
||||
}
|
||||
}, duration * 3);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rankDifference == 0 && x.parent.children[0] === x) {
|
||||
console.log("Fixing 0-child on left");
|
||||
newRoot = fix0Child(
|
||||
x, x.children[1], x.parent, rotateLeft, rotateRight
|
||||
);
|
||||
} else if (rankDifference == 0 && x.parent.children[1] === x) {
|
||||
console.log("Fixing 0-child on right");
|
||||
newRoot = fix0Child(
|
||||
x, x.children[0], x.parent, rotateRight, rotateLeft
|
||||
);
|
||||
}
|
||||
|
||||
// if (rotatingAroundRoot) {
|
||||
// rootNode = newRoot;
|
||||
// }
|
||||
|
||||
setTimeout(function () {
|
||||
removeHighlight(x);
|
||||
if (callback instanceof Function) {
|
||||
callback();
|
||||
}
|
||||
}, duration * 3);
|
||||
};
|
||||
|
||||
// Tree insertion
|
||||
var insert = function (n, callback) {
|
||||
console.log('Insert', n);
|
||||
if (!n || !Number.isInteger(n)) return;
|
||||
if (!rootNode.key) {
|
||||
rootNode.key = n;
|
||||
rootNode.rank++;
|
||||
updateTree();
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
var walker = rootNode,
|
||||
newNode;
|
||||
|
||||
while (!newNode) {
|
||||
if (n <= walker.key) {
|
||||
if (walker.children.length === 0) { // No child
|
||||
walker.children.push({ id: id++, key: n, rank: 0, parent: walker, children: [] }); // Left child
|
||||
walker.children.push({ id: id++, key: null, rank: -1, parent: walker, children: [] }); // Empty right child
|
||||
newNode = walker.children[0];
|
||||
} else if (walker.children[0].key === null) { // Already have right child, left child is empty
|
||||
walker.children[0].key = n;
|
||||
walker.children[0].rank++;
|
||||
newNode = walker.children[0];
|
||||
} else { // Move left
|
||||
walker = walker.children[0];
|
||||
}
|
||||
} else {
|
||||
if (walker.children.length === 0) { // No child
|
||||
walker.children.push({ id: id++, key: null, rank: -1, parent: walker, children: [] }); // Empty left child
|
||||
walker.children.push({ id: id++, key: n, rank: 0, parent: walker, children: [] }); // Right child
|
||||
newNode = walker.children[1];
|
||||
} else if (walker.children[1].key === null) { // Already have left child, right child is empty
|
||||
walker.children[1].key = n;
|
||||
walker.children[1].rank++;
|
||||
newNode = walker.children[1];
|
||||
} else { // Move left
|
||||
walker = walker.children[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
updateTree();
|
||||
setTimeout(function () {
|
||||
insertFixup(newNode, callback);
|
||||
//callback();
|
||||
}, duration);
|
||||
};
|
||||
|
||||
// Tree deletion
|
||||
var deleteTree = function (n, callback) {
|
||||
if (!rootNode.key) return false;
|
||||
var walker = rootNode,
|
||||
nodeDelete = null, // Node to be deleted
|
||||
nodeReplace = null, // Node to replace deleted node
|
||||
nodeBalance = null, // After deleting, perform balance on this node to root
|
||||
parent;
|
||||
|
||||
// Find node
|
||||
if (n === walker.key) { // Deleting root
|
||||
nodeDelete = walker;
|
||||
if (nodeDelete.children.length === 0) nodeDelete.key = null; // Tree only has root node
|
||||
else {
|
||||
if (nodeDelete.children[0].key === null) { // Root does not have left subtree
|
||||
rootNode = rootNode.children[1]; // Right subtree becomes new tree
|
||||
rootNode.parent = null;
|
||||
|
||||
gLinks.selectAll('path').filter(function (d) {
|
||||
return d.data.id === rootNode.id;
|
||||
}).remove();
|
||||
} else {
|
||||
nodeReplace = nodeDelete.children[0];
|
||||
// In-order predecessor, largest child of left subtree
|
||||
while (nodeReplace) {
|
||||
if (!nodeReplace.children[1] || !nodeReplace.children[1].key) break;
|
||||
nodeReplace = nodeReplace.children[1];
|
||||
}
|
||||
|
||||
parent = nodeReplace.parent;
|
||||
nodeBalance = parent; // Will start balacing from this node
|
||||
|
||||
if (parent.children[0] === nodeReplace) {
|
||||
if (nodeReplace.children[0]) {
|
||||
nodeReplace.children[0].parent = parent;
|
||||
parent.children[0] = nodeReplace.children[0]
|
||||
} else {
|
||||
parent.children[0] = { id: id++, key: null, parent: parent, children: [] };
|
||||
}
|
||||
}
|
||||
else if (parent.children[1] === nodeReplace) {
|
||||
if (nodeReplace.children[0]) {
|
||||
nodeReplace.children[0].parent = parent;
|
||||
parent.children[1] = nodeReplace.children[0]
|
||||
} else {
|
||||
parent.children[1] = { id: id++, key: null, parent: parent, children: [] };
|
||||
}
|
||||
}
|
||||
// After moving, if nodeReplace's parent has 2 empty children
|
||||
if (parent.children.length !== 0 && parent.children[0].key === null && parent.children[1].key === null) parent.children = [];
|
||||
|
||||
// Add 2 subtrees of old root to new root
|
||||
if (nodeDelete.children[0]) {
|
||||
nodeDelete.children[0].parent = nodeReplace;
|
||||
nodeReplace.children[0] = nodeDelete.children[0];
|
||||
}
|
||||
if (nodeDelete.children[1]) {
|
||||
nodeDelete.children[1].parent = nodeReplace;
|
||||
nodeReplace.children[1] = nodeDelete.children[1];
|
||||
}
|
||||
|
||||
// Replace deleted root node by largest node of left subtree (in-order predecessor)
|
||||
nodeReplace.parent = null;
|
||||
rootNode = nodeReplace;
|
||||
|
||||
gLinks.selectAll('path').filter(function (d) {
|
||||
return d.data.id === nodeReplace.id;
|
||||
}).remove();
|
||||
}
|
||||
}
|
||||
|
||||
updateTree();
|
||||
setTimeout(function () {
|
||||
if (nodeBalance) balance(nodeBalance, callback);
|
||||
else if (callback instanceof Function) callback();
|
||||
}, duration);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Finding node
|
||||
while (walker.key) {
|
||||
if (n < walker.key) walker = walker.children[0]; // Move left
|
||||
else if (n > walker.key) walker = walker.children[1]; // Move right
|
||||
else if (n === walker.key) {
|
||||
nodeDelete = walker;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!nodeDelete) return false;
|
||||
|
||||
// Deletion
|
||||
if (nodeDelete.children.length === 0) { // Node to be deleted is leaf node
|
||||
parent = nodeDelete.parent;
|
||||
nodeBalance = parent; // Will start balacing from this node
|
||||
|
||||
if (parent.children[0] === nodeDelete) { // Remove left child
|
||||
parent.children[0] = { id: id++, key: null, parent: parent, children: [] }; // Empty child
|
||||
}
|
||||
else if (parent.children[1] === nodeDelete) { // Remove right child
|
||||
parent.children[1] = { id: id++, key: null, parent: parent, children: [] };
|
||||
}
|
||||
|
||||
if (parent.children.length !== 0 && parent.children[0].key === null && parent.children[1].key === null) parent.children = [];
|
||||
|
||||
} else { // Node to be deleted is internal node
|
||||
nodeReplace = nodeDelete.children[0].key ? nodeDelete.children[0] : null;
|
||||
// In-order predecessor, largest child of left subtree
|
||||
while (nodeReplace) {
|
||||
if (!nodeReplace.children[1] || !nodeReplace.children[1].key) break;
|
||||
nodeReplace = nodeReplace.children[1];
|
||||
}
|
||||
|
||||
if (!nodeReplace) { // No left child, right child of nodeDelete replace its position
|
||||
parent = nodeDelete.parent;
|
||||
nodeBalance = parent; // Will start balacing from this node
|
||||
|
||||
nodeDelete.children[1].parent = parent;
|
||||
if (parent.children[0] === nodeDelete) parent.children[0] = nodeDelete.children[1]; // Left child of parent
|
||||
else if (parent.children[1] === nodeDelete) parent.children[1] = nodeDelete.children[1]; // Right child of parent
|
||||
} else {
|
||||
// Update nodeReplace's parent
|
||||
parent = nodeReplace.parent;
|
||||
nodeBalance = parent; // Will start balacing from this node
|
||||
|
||||
if (parent.children[0] === nodeReplace) {
|
||||
if (nodeReplace.children[0]) {
|
||||
nodeReplace.children[0].parent = parent;
|
||||
parent.children[0] = nodeReplace.children[0]
|
||||
} else {
|
||||
parent.children[0] = { id: id++, key: null, parent: parent, children: [] };
|
||||
}
|
||||
}
|
||||
else if (parent.children[1] === nodeReplace) {
|
||||
if (nodeReplace.children[0]) {
|
||||
nodeReplace.children[0].parent = parent;
|
||||
parent.children[1] = nodeReplace.children[0]
|
||||
} else {
|
||||
parent.children[1] = { id: id++, key: null, parent: parent, children: [] };
|
||||
}
|
||||
}
|
||||
// After moving, if nodeReplace's parent has 2 empty children
|
||||
if (parent.children.length !== 0 && parent.children[0].key === null && parent.children[1].key === null) parent.children = [];
|
||||
|
||||
// Replace deleted node by largest node of left subtree (in-order predecessor)
|
||||
parent = nodeDelete.parent;
|
||||
nodeReplace.parent = parent;
|
||||
if (parent.children[0] === nodeDelete) parent.children[0] = nodeReplace; // Left child of parent
|
||||
else if (parent.children[1] === nodeDelete) parent.children[1] = nodeReplace; // Right child of parent
|
||||
|
||||
if (nodeDelete.children[0]) {
|
||||
nodeDelete.children[0].parent = nodeReplace;
|
||||
nodeReplace.children[0] = nodeDelete.children[0];
|
||||
}
|
||||
if (nodeDelete.children[1]) {
|
||||
nodeDelete.children[1].parent = nodeReplace;
|
||||
nodeReplace.children[1] = nodeDelete.children[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateTree();
|
||||
setTimeout(function () {
|
||||
if (nodeBalance) balance(nodeBalance, callback);
|
||||
else if (callback instanceof Function) callback();
|
||||
}, duration);
|
||||
return true;
|
||||
};
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
/*************Binary Search Tree Visualization using D3JS *************/
|
||||
|
||||
const NODE_SIZE = 40;
|
||||
|
||||
var duration = 400;
|
||||
|
||||
var tree = d3.tree().separation(function () { return 3 * NODE_SIZE; });
|
||||
|
||||
var svg = d3.select('svg'),
|
||||
g = svg.append('g').attr('transform', `translate(${3 * NODE_SIZE},${3 * NODE_SIZE})`);
|
||||
|
||||
var gLinks = g.append('g'),
|
||||
gNodes = g.append('g');
|
||||
|
||||
svg.attr('width', '1920')
|
||||
.attr('height', '1080');
|
||||
|
||||
var oldPos = {};
|
||||
var updateTree = function () {
|
||||
var root = d3.hierarchy(rootNode);
|
||||
|
||||
var newTreeSize = [root.descendants().length * 3 * NODE_SIZE, ((root.height + 1) * 2 - 1) * 30];
|
||||
|
||||
if (tree.size()[0] !== newTreeSize[0] || tree.size()[1] !== newTreeSize[1]) {
|
||||
tree.size(newTreeSize);
|
||||
center = pos = svg.attr('width') / 2 - tree.size()[0] / 2;
|
||||
}
|
||||
tree(root);
|
||||
|
||||
var nodes = root.descendants().filter(function (d) {
|
||||
return d.data.key !== null;
|
||||
});
|
||||
|
||||
var link = gLinks.selectAll('path')
|
||||
.data(nodes, function (d) { return d.data.id; });
|
||||
|
||||
link.exit().remove();
|
||||
|
||||
link.transition() // Update new position of old links
|
||||
.duration(duration)
|
||||
.attrTween('d', function (d) {
|
||||
|
||||
var oldDraw = d3.select(this).attr('d');
|
||||
if (oldDraw) {
|
||||
oldDraw = oldDraw.match(/(M.*)(L.*)/);
|
||||
var oldMoveto = oldMoveto = oldDraw[1].slice(1).split(',').map(Number),
|
||||
oldLineto = oldDraw[2].slice(1).split(',').map(Number);
|
||||
// If root is changed, reverse to correctly animated if rotate left
|
||||
if (changeRoot && oldMoveto[1] === 0) { // Old root node
|
||||
oldMoveto = oldDraw[2].slice(1).split(',').map(Number);
|
||||
oldLineto = oldDraw[1].slice(1).split(',').map(Number);
|
||||
changeRoot = false;
|
||||
}
|
||||
|
||||
if ((oldLineto !== [d.x, d.y]) && (oldMoveto !== [d.parent.x, d.parent.y])) {
|
||||
/*console.log(d.data.key, oldMoveto, oldLineto);
|
||||
console.log(d.data.key, [d.parent.x, d.parent.y], [d.x, d.y]);*/
|
||||
var interpolatorMX = d3.interpolateNumber(oldMoveto[0], d.parent.x);
|
||||
var interpolatorMY = d3.interpolateNumber(oldMoveto[1], d.parent.y);
|
||||
var interpolatorLX = d3.interpolateNumber(oldLineto[0], d.x);
|
||||
var interpolatorLY = d3.interpolateNumber(oldLineto[1], d.y);
|
||||
|
||||
return function (t) {
|
||||
return 'M' + interpolatorMX(t) + ',' + interpolatorMY(t) + 'L' + interpolatorLX(t) + ',' + interpolatorLY(t);
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
link.enter().append('path') // Add new element for new data
|
||||
.attr('class', 'link')
|
||||
.transition()
|
||||
.duration(duration)
|
||||
.attrTween('d', function (d) {
|
||||
if (d.parent) {
|
||||
var parentOldPos = oldPos[d.parent.data.id.toString()];
|
||||
var interpolatorMX = d3.interpolateNumber(parentOldPos[0], d.parent.x);
|
||||
var interpolatorMY = d3.interpolateNumber(parentOldPos[1], d.parent.y);
|
||||
var interpolatorLX = d3.interpolateNumber(parentOldPos[0], d.x);
|
||||
var interpolatorLY = d3.interpolateNumber(parentOldPos[1], d.y);
|
||||
|
||||
return function (t) {
|
||||
return 'M' + interpolatorMX(t) + ',' + interpolatorMY(t) + 'L' + interpolatorLX(t) + ',' + interpolatorLY(t);
|
||||
};
|
||||
}
|
||||
else {
|
||||
d3.select(this).remove();
|
||||
}
|
||||
});
|
||||
|
||||
var node = gNodes.selectAll('g')
|
||||
.data(nodes, function (d) { return d.data.id; });
|
||||
|
||||
node.exit().remove();
|
||||
|
||||
node.transition()
|
||||
.duration(duration)
|
||||
.attr('transform', function (d) {
|
||||
setTimeout(function () { // Finish transition, update old position of this node
|
||||
oldPos[d.data.id.toString()] = [d.x, d.y];
|
||||
}, duration);
|
||||
return 'translate(' + d.x + ',' + d.y + ')';
|
||||
});
|
||||
|
||||
var newNode = node.enter().append('g')
|
||||
.attr('transform', function (d) {
|
||||
if (!d.parent) return 'translate(' + d.x + ',' + (d.y - 30) + ')';
|
||||
else return 'translate(' + oldPos[d.parent.data.id.toString()][0] + ',' + (oldPos[d.parent.data.id.toString()][1] - 30) + ')';
|
||||
})
|
||||
.attr('class', 'node');
|
||||
|
||||
newNode.transition()
|
||||
.duration(duration)
|
||||
.attr('transform', function (d) {
|
||||
oldPos[d.data.id.toString()] = [d.x, d.y];
|
||||
return 'translate(' + d.x + ',' + d.y + ')';
|
||||
});
|
||||
|
||||
newNode.append('circle')
|
||||
.attr('r', NODE_SIZE);
|
||||
|
||||
newNode.append('text')
|
||||
.attr('class', 'text')
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('dy', 5)
|
||||
.text(function (d) { return `${d.data.key} (r=${d.data.rank})`; });
|
||||
};
|
||||
|
||||
var handleInsert = function (event) {
|
||||
var num = document.getElementById('insertInput').value;
|
||||
if (num) {
|
||||
document.getElementById('insertInput').value = '';
|
||||
d3.selectAll('#insertTree input').each(function () { // Disable insert
|
||||
d3.select(this).attr('disabled', '')
|
||||
});
|
||||
insert(parseInt(num), function () {
|
||||
d3.selectAll('#insertTree input').each(function () { // Enable insert
|
||||
d3.select(this).attr('disabled', null);
|
||||
});
|
||||
});
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
var handleDelete = function (event) {
|
||||
var num = document.getElementById('deleteInput').value;
|
||||
if (num && rootNode.key !== null) { // Tree is not empty
|
||||
document.getElementById('deleteInput').value = '';
|
||||
d3.selectAll('#deleteTree input').each(function () { // Disable insert
|
||||
d3.select(this).attr('disabled', '')
|
||||
});
|
||||
deleteTree(parseInt(num), function () {
|
||||
d3.selectAll('#deleteTree input').each(function () { // Enable insert
|
||||
d3.select(this).attr('disabled', null);
|
||||
});
|
||||
});
|
||||
}
|
||||
return false;
|
||||
};
|
93
visualization.js
Normal file
93
visualization.js
Normal file
|
@ -0,0 +1,93 @@
|
|||
class Recorder {
|
||||
constructor(graph) {
|
||||
this.graph = graph;
|
||||
|
||||
this.rendered = -1;
|
||||
this.rendering = false;
|
||||
|
||||
this.states = new Array();
|
||||
|
||||
this.unblockRendering = ((recorder) => () => {
|
||||
recorder.rendering = false;
|
||||
})(this);
|
||||
|
||||
this.initGraph();
|
||||
}
|
||||
|
||||
initGraph() {
|
||||
this.graph.transition(() =>
|
||||
d3
|
||||
.transition("main")
|
||||
.ease(d3.easeLinear)
|
||||
.on("end", this.unblockRendering)
|
||||
.delay(500)
|
||||
.duration(1500)
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (
|
||||
this.rendering ||
|
||||
!this.states ||
|
||||
this.rendered == this.states.length - 1
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.rendering = true;
|
||||
|
||||
this.rendered++;
|
||||
this.graph.renderDot(this.states[this.rendered]);
|
||||
}
|
||||
|
||||
previous() {
|
||||
throw "not implemented!";
|
||||
}
|
||||
|
||||
next() {
|
||||
throw "not implemented!";
|
||||
}
|
||||
|
||||
record(tree) {
|
||||
// TODO: adjust join if necessary
|
||||
this.states.push(tree.toDot().join(""));
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
let recorder = new Recorder(d3.select("#graph").graphviz());
|
||||
|
||||
let tree = new AVLTree();
|
||||
tree.recorder = recorder;
|
||||
|
||||
function insertCallback() {
|
||||
let number = document.getElementById("insertInput").value;
|
||||
if (number === "") {
|
||||
return false;
|
||||
}
|
||||
|
||||
let value = parseInt(number);
|
||||
tree.insert(value);
|
||||
|
||||
document.getElementById("insertInput").value = "";
|
||||
return false;
|
||||
}
|
||||
|
||||
function deleteCallback() {
|
||||
let number = document.getElementById("deleteInput").value;
|
||||
if (number === "") {
|
||||
return false;
|
||||
}
|
||||
|
||||
let value = parseInt(number);
|
||||
tree.delete(value);
|
||||
|
||||
document.getElementById("deleteInput").value = "";
|
||||
return false;
|
||||
}
|
||||
|
||||
function render() {
|
||||
recorder.render();
|
||||
setTimeout(render);
|
||||
}
|
||||
render();
|
Loading…
Reference in a new issue