Recently tried to implement the AVL tree, found that this thing to write up or a lot of details, messed up half a day, so here to record.
An AVL tree is a balanced binary search tree. On the one hand, it is a BST, i.e., a binary search tree, and on the other hand, it is balanced, i.e., the difference between the height of the left and right subtrees of any subtree whose node is the root node is less than one.
Fundamentals
Not much to say about BST, it is relatively simple, that is, the left child of each node is smaller than the root node, the right child is larger than the root node, just pay attention when inserting.
But there is a problem with BST, for example, inserting 1, 2, 3, 4, 5, 6 in sequence, then we will end up with a tree with only right children, in fact, this BST has degenerated into an ordinary chain table, so we need some way to keep this tree balanced, and the way to keep it balanced is as follows:
Specific implementation
Four types of imbalance
Left-right situation
In this case to rotate right and then return the new root node after the rotation, the code is as follows:
In this case, the left child node of the root node should first be left rotated to become the left-left case, then the root node should be right rotated, and then the new root node should be returned after the rotation, with the following code:
In this case, the right child node of the root node should first be rotated right to become the right-right case, then the root node should be rotated left, and then the new root node should be returned after the rotation, with the following code:
When we do balancing, we need to determine whether we need to balance and what kind of imbalance is the case so that we can choose a different rotation method
1 2 3 4 5 6 7 8 9 10 11 12 13
#_getAvlTreeHeight(node) { // The height of the empty node is 0 if (node= null) { return0; }
// Post-order traversal is used here, because the result of each recursion requires the result of the child node recursion const leftHeight = this.#_getAvlTreeHeight(node.left); const rightHeight = this.#_getAvlTreeHeight(node.right);
// The node height is the greater of the left and right subtree heights plus 1 returnMath.max(leftHeight, rightHeight) + 1; }
Balance
Balances the subtree with node as the root node and returns the new root node.
#_balance(node) { // Empty nodes do not need to be balanced, return empty nodes directly if (node= null) { return node; }
// Get the height of the left and right subtrees const leftSubTreeHeight = this.#_getAvlTreeHeight(node.left); const rightSubTreeHeight = this.#_getAvlTreeHeight(node.right);
// If the height of the left subtree is greater than the height of the right subtree by more than 1, it is unbalanced and there is an implied logic that the left subtree of the left subtree must not be empty if (leftSubTreeHeight - rightSubTreeHeight > 1) { // Left-left situation, direct right rotation if (this.#_getAvlTreeHeight(node.left.left) >= this.#_getAvlTreeHeight(node.left.right)) { node = this.#_rotateRight(node) } else { // Left and right situation, first left rotation then right rotation node = this.#_rotateLeftRight(node); } } elseif (rightSubTreeHeight - leftSubTreeHeight > 1) { // Right-right situation, direct left rotation if (this.#_getAvlTreeHeight(node.right.right) >= this.#_getAvlTreeHeight(node.right.left)) { node = this.#_rotateLeft(node) } else { // Right-left situation, first right, then left node = this.#_rotateRightLeft(node) } }
// Return the new root node of the balanced subtree return node; }
// The most important thing to understand about this code is that this recursive function returns the root of a new subtree each time a newNode is inserted and balanced on a tree with a node node as the root node. #_insertHelper(node, newNode) { // If the root node is empty, return the newNode directly as the root node if (node= null) { return newNode; } // If the value of the new node is smaller than the current node if (newNode.val < node.val) { // If the left node is empty, then the new node will be treated as the left child node directly if (node.left= null) { node.left = newNode; } else { // If the left child node is not empty, then insert a new node into the left subtree and use the root node of the new subtree returned after the insertion as the left subtree node.left = this.#_insertHelper(node.left, newNode); } } else { // If the right node is empty, then the new node is treated as the right child node directly if (node.right= null) { node.right = newNode; } else { // If the right child node is not empty, then insert a new node into the right subtree and use the root node of the new subtree returned after the insertion as the right subtree node.right = this.#_insertHelper(node.right, newNode); } }
// Balance after insertion and return the root node of the new subtree after balancing returnthis.#_balance(node) }
insert(newNode) { // Insert the newNode from the root, and reassign the root to ensure the next call is correct this.#_root = this.#_insertHelper(this.#_root, newNode); }
Find
This is relatively simple, and not much different from BST
#_removeHelper(node, val) { // If node is null, the tree is empty and does not need to be deleted, and the root node of the new tree is also null if (node= null) returnnull; // If the node to be deleted is found if (trap= node.val) { // The node to be deleted has both left and right subtrees if (node.left && node.right) { // Then find the right subtree of the node let p = node.right; // Then look for the left subtree all the way down the right subtree while(p.left) { p = p.left; } // Then replace the result in the current node, because the leftmost child node in the right subtree must be smaller than the rest of the nodes in the right subtree, while raining all the nodes in the left subtree node.val = p.val; // The previous step just replaces the node value, but the node is still there. We need to remove the node from the right subtree and return the new right subtree as the right subtree of the node after balancing. // And remember, at this point we are deleting p.val, not val, because we have already put p.val into node.val, and this p has the property that it has no left child node at least, and the next time the seat will enter the immediately following else logic node.right = this.#_removeHelper(node.right, p.val) } else { // If you don't have both left and right subtrees, just replace them and delete the original ones let p = node.left ! null ? node.left : node.right; node = null; return p; } } elseif (val < node.val) { // If the value to be deleted is smaller than the current one, go to the left subtree and delete it node.left = this.#_removeHelper(node.left, val) } else { // If the value to be deleted is larger than the current one, go to the right subtree and delete it node.right = this.#_removeHelper(node.right, val); }
// Return the root of the new subtree after balancing returnthis.#_balance(node); }