web/tree-creation.js
Matej Focko 2beb2d9fc7
feat: First working version
Signed-off-by: Matej Focko <me@mfocko.xyz>
2021-07-28 21:20:02 +02:00

541 lines
18 KiB
JavaScript
Executable file

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;
};