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