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;
|
let newRoot = x.parent;
|
||||||
|
|
||||||
if (!y || nodeDifference(y) == 2) {
|
if (!y || nodeDifference(y) == 2) {
|
||||||
newRoot = rotateRight(z);
|
newRoot = rotateRight(z);
|
||||||
z.demote();
|
z.demote();
|
||||||
|
|
||||||
|
if (rotatingAroundRoot) {
|
||||||
|
this.root = newRoot;
|
||||||
|
}
|
||||||
|
this.record();
|
||||||
} else if (nodeDifference(y) == 1) {
|
} else if (nodeDifference(y) == 1) {
|
||||||
rotateLeft(x);
|
rotateLeft(x);
|
||||||
|
this.record();
|
||||||
|
|
||||||
newRoot = rotateRight(z);
|
newRoot = rotateRight(z);
|
||||||
|
if (rotatingAroundRoot) {
|
||||||
|
this.root = newRoot;
|
||||||
|
}
|
||||||
|
this.record();
|
||||||
|
|
||||||
y.promote();
|
y.promote();
|
||||||
x.demote();
|
x.demote();
|
||||||
z.demote();
|
z.demote();
|
||||||
}
|
|
||||||
|
|
||||||
return newRoot;
|
this.record();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
insertRebalance(x) {
|
insertRebalance(x) {
|
||||||
|
@ -78,6 +89,8 @@ class AVLTree extends RankedTree {
|
||||||
x.parent.promote();
|
x.parent.promote();
|
||||||
x = x.parent;
|
x = x.parent;
|
||||||
|
|
||||||
|
this.record();
|
||||||
|
|
||||||
diffs = nodeDifferences(x.parent).sort();
|
diffs = nodeDifferences(x.parent).sort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +99,6 @@ class AVLTree extends RankedTree {
|
||||||
}
|
}
|
||||||
|
|
||||||
let rotatingAroundRoot = x.parent.parent == null;
|
let rotatingAroundRoot = x.parent.parent == null;
|
||||||
let newRoot = x.parent;
|
|
||||||
|
|
||||||
let rankDifference = nodeDifference(x);
|
let rankDifference = nodeDifference(x);
|
||||||
if (rankDifference != 0) {
|
if (rankDifference != 0) {
|
||||||
|
@ -96,18 +108,24 @@ class AVLTree extends RankedTree {
|
||||||
let xParent = x.parent;
|
let xParent = x.parent;
|
||||||
|
|
||||||
if (rankDifference == 0 && x.parent.left === x) {
|
if (rankDifference == 0 && x.parent.left === x) {
|
||||||
newRoot = this.fix0Child(
|
this.fix0Child(
|
||||||
x, x.right, xParent, rotateLeft, rotateRight
|
x,
|
||||||
|
x.right,
|
||||||
|
xParent,
|
||||||
|
rotatingAroundRoot,
|
||||||
|
rotateLeft,
|
||||||
|
rotateRight
|
||||||
);
|
);
|
||||||
} else if (rankDifference == 0 && x.parent.right === x) {
|
} else if (rankDifference == 0 && x.parent.right === x) {
|
||||||
newRoot = this.fix0Child(
|
this.fix0Child(
|
||||||
x, x.left, xParent, rotateRight, rotateLeft
|
x,
|
||||||
|
x.left,
|
||||||
|
xParent,
|
||||||
|
rotatingAroundRoot,
|
||||||
|
rotateRight,
|
||||||
|
rotateLeft
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rotatingAroundRoot) {
|
|
||||||
this.root = newRoot;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteRotate(x, y, leaning, rotatingAroundRoot, rotateLeft, rotateRight) {
|
deleteRotate(x, y, leaning, rotatingAroundRoot, rotateLeft, rotateRight) {
|
||||||
|
@ -124,14 +142,18 @@ class AVLTree extends RankedTree {
|
||||||
newRoot = rotateLeft(x);
|
newRoot = rotateLeft(x);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
[newRoot.left, newRoot.right, newRoot].filter(x => x).forEach(n => {
|
|
||||||
updateRank(n)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (rotatingAroundRoot) {
|
if (rotatingAroundRoot) {
|
||||||
this.root = newRoot;
|
this.root = newRoot;
|
||||||
}
|
}
|
||||||
|
this.record();
|
||||||
|
|
||||||
|
[newRoot.left, newRoot.right, newRoot]
|
||||||
|
.filter((x) => x)
|
||||||
|
.forEach((n) => {
|
||||||
|
updateRank(n);
|
||||||
|
this.record();
|
||||||
|
});
|
||||||
|
|
||||||
return factor == 0;
|
return factor == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,6 +164,9 @@ class AVLTree extends RankedTree {
|
||||||
switch (factor) {
|
switch (factor) {
|
||||||
case 0:
|
case 0:
|
||||||
updateRank(x);
|
updateRank(x);
|
||||||
|
|
||||||
|
this.record();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
case -1:
|
case -1:
|
||||||
case 1:
|
case 1:
|
||||||
|
@ -149,12 +174,29 @@ class AVLTree extends RankedTree {
|
||||||
}
|
}
|
||||||
|
|
||||||
let rotatingAroundRoot = x.parent == null;
|
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) {
|
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) {
|
deleteRebalance(node, parent) {
|
||||||
|
|
|
@ -19,10 +19,11 @@ class Queue {
|
||||||
class RankedTree {
|
class RankedTree {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.root = null;
|
this.root = null;
|
||||||
|
this.recorder = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getUnwrappedGraph() {
|
_getUnwrappedGraph() {
|
||||||
let result = "";
|
let result = [];
|
||||||
|
|
||||||
let q = new Queue();
|
let q = new Queue();
|
||||||
q.enqueue(this.root);
|
q.enqueue(this.root);
|
||||||
|
@ -34,24 +35,45 @@ class RankedTree {
|
||||||
continue;
|
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]);
|
edges.push([node, child]);
|
||||||
q.enqueue(child);
|
q.enqueue(child);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
edges.forEach(vertices => {
|
result.push("");
|
||||||
|
|
||||||
|
edges.forEach((vertices) => {
|
||||||
let [u, v] = 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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toDot() {
|
||||||
|
return ["digraph {", ...this._getUnwrappedGraph(), "}"];
|
||||||
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return `digraph {\n${this._getUnwrappedGraph()}}\n`;
|
return this.toDot().join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
record() {
|
||||||
|
if (!this.recorder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.recorder.record(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
rank() {
|
rank() {
|
||||||
|
@ -71,6 +93,7 @@ class RankedTree {
|
||||||
|
|
||||||
if (!this.root) {
|
if (!this.root) {
|
||||||
this.root = insertedNode;
|
this.root = insertedNode;
|
||||||
|
this.record();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,6 +109,7 @@ class RankedTree {
|
||||||
} else {
|
} else {
|
||||||
parent.right = insertedNode;
|
parent.right = insertedNode;
|
||||||
}
|
}
|
||||||
|
this.record();
|
||||||
|
|
||||||
this.insertRebalance(insertedNode);
|
this.insertRebalance(insertedNode);
|
||||||
}
|
}
|
||||||
|
@ -134,6 +158,8 @@ class RankedTree {
|
||||||
node.left.parent = successor;
|
node.left.parent = successor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.record();
|
||||||
|
|
||||||
return [y, parent];
|
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