In comparison to nodes in binary search trees, nodes in rank-balanced trees contain one more piece of information, and that is \textit{rank}. Each type of tree that can be implemented using this representation, e.g. red-black, 2-3-4, AVL or WAVL, has a specific set of rules that ensure the resulting tree is balanced.
\section{Terminology related to rank-balanced trees}
In the text and pseudocode we adopt these functions or properties~\cite{wavl}:
\begin{itemize}
\item function $r(x)$ or property $x.rank$ that returns rank of a node; in case of $r(x)$ there is a special case: $r(nil)=-1$
\item function $parent(x)$ or property $x.parent$ returns parent of a node; analogically for the left and right children of a node
\item\textit{rank-difference} of \textit{x} is defined as $r(parent(x))- r(x)$
\item$x$ is an \textit{i-child} if its rank-difference is $i$
As we have mentioned at the beginning of \hyperref[chap:rank-balanced-trees]{this chapter}, it is possible to implement different kinds of self-balancing binary search trees via different rules for ranks.
In the case of the AVL tree, rank represents height. Here we can notice an ingenious way of using the \textit{(i, j)-node} definition. If we go back to the definition and want to be explicit about the nodes that are allowed with the \textit{AVL Rule}, then we get (1, 1), (1, 2) \textbf{or} (2, 1) nodes. However, it is possible to find implementations of the AVL tree that allow leaning \textbf{to only one side} as opposed to the original requirements given by \textit{Adelson-Velsky and Landis}~\cite{avl}. Forbidding interchangeability of (i, j) with (j, i)-nodes would still yield AVL trees that lean to one side.
The meaning of the \textit{AVL Rule} is quite simple since rank represents the height in that case. We can draw analogies using the notation used for the AVL trees, where we mark nodes with a trit (or a sign) or use a balance factor. We have two cases to discuss:
\item\textbf{(1, 1) node} represents a tree where both of its subtrees have the same height. In this case, we are talking about the nodes with balance factor $0$ (respectively being signed with a $0$).
\item\textbf{(1, 2) node} represents a tree where one of its subtrees has a bigger height. In this case, we are talking about the nodes with balance factor $-1$ or $1$ (respectively being signed with a $-$ or a $+$).
In the case of red-black trees, rank represents a number of black nodes on a path from the node to a leaf (excluding the node itself). Based on that, we can discuss the \textit{Red-Black Rule} in detail:
\item\textit{All rank differences are 0 or 1} inductively enforces a monotonically non-decreasing (at most by 1) count of black nodes from the leaves. In detail:
\item In case the \textbf{current node is black}, the rank difference must be 1, since we have one more black node on the path from the parent to the leaves than from the current node.
\item In case the \textbf{current node is red}, the rank difference must be 0, since from the parent, the count of black nodes on the path to leaves has not changed.
\item And finally, all other differences are invalid since adding a node to the beginning of a path to the leaf, we can either add red node (0-child) or black node (1-child), i.e. there is one more black node on the path or not, which implies the change can be only 0 or 1.
\item\textit{No parent of a 0-child is a 0-child} ensures that there are no two consecutive red nodes, since the 0-child node is equivalent to the red node.
An example of the red-black tree that uses ranks instead of colours can be seen in \autoref{fig:ranked:rbt}. Red nodes are also coloured for convenience.
Majority of the red-black tree implementations colour nodes of the tree, following that notation and \textbf{precise} definition of the red-black tree it is pretty common to ask the following questions:
However, it is also possible to colour edges instead of the nodes as is presented in \textit{Průvodce labyrintem algoritmů} by \textit{Mareš and Valla}.~\cite{labyrint} In this representation colour of the edge represents the colour of the child node. This representation is much more „natural“ for the representation using rank as it can be seen in \autoref{fig:ranked:rbt}, where edges connecting nodes with rank-difference $1$ represent \textit{black edges} and edges connecting nodes with rank-difference $0$ represent \textit{red edges}. It is also apparent that using this representation root of the tree does not hold any colour anymore.
Implementation of the insertion is trivial, since it is described by \textit{Haeupler et al.}~\cite{wavl} and is used in the WAVL tree. All we need to implement is the deletion from the AVL tree. We will start with a short description of the deletion rebalance as given by \textit{Mareš and Valla} in \textit{Průvodce labyrintem algoritmů}.
When propagating the error, we can encounter 3 cases (we explain them for propagating deletion from the left subtree, propagation from right is mirrored, and role of trits $+$ and $-$ swaps)~\cite{labyrint}:
\item\textit{Node was marked with $-$.} In this case, heights of left and right subtrees are equal now, and node is marked with $0$, but propagation must be continued since the height of the whole subtree has changed.\label{avl:rules:delete:1}
\item\textit{Node was marked with $0$.} In this case, the node is marked with $+$, and the height of the subtree has not changed; therefore, we can stop the propagation.\label{avl:rules:delete:2}
\item\textit{Node was marked with $+$.} In this case, the node would acquire a balance factor of $+2$, which is not allowed. In this situation, we decide based on the mark of the node from which we are propagating the deletion in the following way (let $x$, the current node marked with $+$ and $y$, be the right child of $x$):\label{avl:rules:delete:3}
\item If $y$ is marked with $+$, we rotate by $x$ to the left. After that, both $x$ and $y$ can be marked with $0$. Height from the point of the parent has changed, so we continue the propagation.\label{avl:rules:delete:3a}
\item If $y$ is marked with $0$, we rotate by $x$ to the left. After the rotation, $x$ can be marked with $+$ and $y$ with $-$. The height of the subtree has not changed, and propagation can be stopped.\label{avl:rules:delete:3b}
\item$y$ is marked with $-$. Let $z$ be the left son of $y$. We double rotate by $z$ to the right and then by $x$ to the left. After the double-rotation, $x$ can be marked by either $0$ or $-$, $y$ by $0$ or $+$ and $z$ gets $0$. The height of the subtree has changed; therefore, we must propagate further.\label{avl:rules:delete:3c}
\item\avlDeleteRebalance{} that handles updating the current node and its parent and iteratively calls subroutine handling previously described \textit{as one step of a rebalancing}.
\texttt{deleteRebalance}, as can be seen in \autoref{algorithm:avl:deleteRebalance}, is relatively straightforward. In the beginning, we early return in case there is nothing to be rebalanced, which happens when deleting the last node from the tree. Then we handle a case where we are given only parent by correctly setting $y$ and $parent$. Following up on that, as long as we have a node to be checked, we call \autoref{algorithm:avl:deleteFixNode} to fix the balancing of the current node. The algorithm for fixing a node returns $true$ or $false$ depending on the need to propagate the height change further, which is utilized in the \texttt{while}-loop condition.
\texttt{deleteFixNode} implements the algorithm described in \hyperref[avl:rules:delete]{the list} with all possible cases above. We start by checking the balance factor of the given node. In case there is no need to rotate, the rank gets updated if necessary, and then we return the information on whether there is a need to propagate further or not. If the node has acquired a balance factor of $2$, we call \autoref{algorithm:avl:deleteRotate} to fix the balancing locally.
\texttt{deleteRotate} is handling only fixes where the rotations are required. Both \autoref{algorithm:avl:deleteFixNode} and \autoref{algorithm:avl:deleteRotate} include comments to highlight which rules are handled. This function is also done generically regardless of the subtree from which the height change is being propagated. This is done by passing in functions used for rotations (since it is mirrored) and passing in the balance factor required for just one rotation.
There is a crucial difference between \autoref{algorithm:avl:deleteFixNode} and \autoref{algorithm:avl:deleteRotate} compared to the AVL tree implementations without ranks. If we compare the rules for deletion with algorithms for rank-balanced implementation, we can see a crucial difference. During propagation of the height change, the balance factors of the closest nodes are already adjusted. That is caused by the calculation of the balance factor based on the subtrees, not the node's rank. Furthermore, subtrees are already adjusted. This fact needs to be reflected in the implementation accordingly. It shifts the meaning of the rules described above and is written for the implementations that directly store the trit in the nodes, updated manually during rebalancing.