541 lines
18 KiB
JavaScript
Executable file
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;
|
|
};
|
|
|