3 * Insertion and removal for AVL trees
5 * (c) 2024 Straylight/Edgeware
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of Xyla, a library of binary trees.
12 * Xyla is free software: you can redistribute it and/or modify it under
13 * the terms of the GNU Lesser General Public License as published by the
14 * Free Software Foundation; either version 3 of the License, or (at your
15 * option) any later version.
17 * Xyla is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
20 * License for more details.
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with Xyla. If not, see <https://www.gnu.org/licenses/>.
26 /*----- Header files ------------------------------------------------------*/
31 /*----- Main code ---------------------------------------------------------*/
33 int xyla_avl_insert(struct xyla_bt_nodecls *cls,
34 struct xyla_avl_path *path, struct xyla_avl_node *node)
36 /* Add a new node to the tree, in the place determined by the empty PATH.
37 * It's the caller's responsibility to ensure that inserting the node here
38 * respects any ordering invariants. Returns `XYLA_RC_HTCHG' if the tree
39 * has increased in height, or `XYLA_RC_OK' if its height remains
42 * The new node is not copied, and its storage must remain valid until the
43 * node is removed or the tree is discarded.
46 struct xyla_avl_node *p, *n, *c;
47 struct xyla_bt_node **nlink, **plink;
48 xyla_bt_updfn *updfn = cls ? cls->ops->upd : 0;
50 unsigned trail = 0, bal;
52 /* The trail. Rather than keep looking things up in the tree nodes, we
53 * keep track of information in a bitmask. Records build up at the low
54 * end, and get shifted up as we ascend the tree, so more-significant bit
55 * positions refer to lower nodes in the tree. The iteration ends when we
56 * find an imbalanced ancestor; as we ascend, we adjust the balance
57 * annotations which end up matching the direction of the child from which
58 * we ascended. So there's no point tracking both: the balance state of a
59 * node is equal to the direction of its child.
62 #define NODE (0*TRAILSH)
63 #define CHILD (1*TRAILSH)
64 #define DIR(lv) ((trail >> (lv))&3u)
65 #define BAL(lv) ((trail >> ((lv) + TRAILSH))&3u)
67 /* Check that the path is well-formed and describes a failed probe. */
68 nlink = path->p[k]; XYLA__ASSERT(!*nlink);
70 /* Initialize the new node's links and hook it onto the tree. */
71 node->avl.f = (node->avl.f&~XYLA_AVLF_BALMASK) | XYLA_AVLBAL_EVEN;
72 node->bt.left = node->bt.right = 0;
75 /* And now we have to rebalance the tree. Getting started is a little
76 * tricky. Start with the easy case: if we're at the top of the tree
77 * already, then the whole thing has just gotten taller.
80 if (updfn) updfn(cls, &node->bt);
81 return (XYLA_RC_HTCHG);
84 /* Propagate the change up the tree. */
85 n = node; c = 0; /* unnecessary */
87 /* The node n has not yet been updated. */
89 /* Collect the parent node and enter the details in the trail. */
90 plink = path->p[--k]; p = AVL_NODE(*plink);
91 trail |= (nlink == &p->bt.left ?
92 XYLA_AVLBAL_LBIAS : XYLA_AVLBAL_RBIAS) << NODE;
93 bal = p->avl.f&XYLA_AVLF_BALMASK; if (bal != XYLA_AVLBAL_EVEN) break;
94 V( if (xyla__verbout && (xyla__verbose&XYLA__VF_INSERT))
95 fprintf(xyla__verbout, "XYLA-AVL INSERT "
96 "parent = (%s child): %s tree\n",
97 DIR(NODE) == XYLA_AVLBAL_LBIAS ? "left" : "right",
98 k ? "ascend" : "extend"); )
100 /* The parent node was equally balanced. It's now heavy on the side of
101 * the current node. That's fine, but the parent's height has changed in
110 /* Update the balance annotation. */
111 p->avl.f |= DIR(NODE);
113 /* If there's no more tree to ascend then it got taller. */
115 if (updfn) { updfn(cls, &n->bt); updfn(cls, &p->bt); }
116 return (XYLA_RC_HTCHG);
119 /* Ascend the tree. */
120 if (updfn) updfn(cls, &n->bt);
121 c = n; n = p; nlink = plink; trail <<= TRAILSH;
124 /* Finishing touches. */
125 if (DIR(NODE) != bal) {
126 /* The parent node was imbalanced, and the current node is on what was
127 * previously the light side. It's now been evened out.
135 V( if (xyla__verbout && (xyla__verbose&XYLA__VF_INSERT))
136 fprintf(xyla__verbout, "XYLA-AVL INSERT parent %c (%s child)\n",
138 DIR(NODE) == XYLA_AVLBAL_LBIAS ? "left" : "right"); )
139 p->avl.f = (p->avl.f&~XYLA_AVLF_BALMASK) | XYLA_AVLBAL_EVEN;
140 if (updfn) { updfn(cls, &n->bt); updfn(cls, &p->bt); }
143 else if (DIR(NODE) == XYLA_AVLBAL_LBIAS)
144 #define FIXUP_INSERT(left, LBIAS, right, RBIAS, EEL, REE) do { \
145 /* The parent node was biased to the left, and the current node is \
146 * the parent's left child. That means that we must have ascended \
147 * a level: the original node's parent lacked a child before we \
148 * added one, so must have balanced evenly or biased in the \
149 * opposite direction. \
152 if (BAL(NODE) == XYLA_AVLBAL_##LBIAS) { \
153 /* The current node is already biased in the same direction as \
154 * the parent. We can resolve the situation by applying a \
155 * rotation and adjusting balance annotations. \
160 * (-) n ---> h + 1 (=) \
164 * The final height of the subtree remains unchanged at h + 2, so \
165 * we're done propagating the height change. \
168 V( if (xyla__verbout && (xyla__verbose&XYLA__VF_INSERT)) \
169 fprintf(xyla__verbout, "XYLA-AVL INSERT parent %c, node %c " \
170 "(" #left " child)\n", \
171 AVL_BALCH(p), AVL_BALCH(n)); ) \
172 p->bt.left = n->bt.right; n->bt.right = &p->bt; *plink = &n->bt; \
173 n->avl.f = (n->avl.f&~XYLA_AVLF_BALMASK) | XYLA_AVLBAL_EVEN; \
174 p->avl.f = (p->avl.f&~XYLA_AVLF_BALMASK) | XYLA_AVLBAL_EVEN; \
175 if (updfn) { updfn(cls, &p->bt); updfn(cls, &n->bt); } \
177 /* The current node is already biased in the opposite direction \
178 * from its parent. We can resolve the situation by applying two \
179 * rotations and adjusting balance annotations. \
184 * (+) h n ,---' `---, p \
185 * / \ c ---> (*) (*) \
191 * The resulting balance annotations depend on the child's \
192 * initial state. The child node c has height h + 1. The new \
193 * node may be one of c's descendants: in that case, c must \
194 * previously have been evenly balanced, or we wouldn't have got \
195 * this far up the tree; the subtree containing the new node now \
196 * has height h and the sibling subtree still has height h - 1. \
197 * Alternatively, the child node may be the new node itself, in \
198 * which case it has no children and is evenly balanced, and \
201 * In any event, at most one of c's subtrees has height h - 1, so \
202 * c always ends up evenly balanced; the new balance annotations \
203 * for nodes n and p are as follows. \
211 * The final height of the subtree remains unchanged at h + 2, so \
212 * we're done propagating the height change. \
215 V( if (xyla__verbout && (xyla__verbose&XYLA__VF_INSERT)) \
216 fprintf(xyla__verbout, \
217 "XYLA-AVL INSERT parent %c, node %c, child %c " \
218 "(" #left " child)\n", \
219 AVL_BALCH(p), AVL_BALCH(n), AVL_BALCH(c)); ) \
220 n->bt.right = c->bt.left; c->bt.left = &n->bt; \
221 p->bt.left = c->bt.right; c->bt.right = &p->bt; *plink = &c->bt; \
223 c->avl.f = (c->avl.f&~XYLA_AVLF_BALMASK) | XYLA_AVLBAL_EVEN; \
224 n->avl.f = (n->avl.f&~XYLA_AVLF_BALMASK) | AVL_REBAL_##EEL(bal); \
225 p->avl.f = (p->avl.f&~XYLA_AVLF_BALMASK) | AVL_REBAL_##REE(bal); \
227 /* We've updated c once already. I can't see a good way to \
228 * avoid this without introducing a bunch of additional \
229 * conditional logic into the other cases. \
231 updfn(cls, &n->bt); updfn(cls, &p->bt); updfn(cls, &c->bt); \
235 FIXUP_INSERT(left, LBIAS, right, RBIAS, EEL, REE);
237 FIXUP_INSERT(right, RBIAS, left, LBIAS, REE, EEL);
240 /* Follow the rest of the path up to the root, updating the nodes. */
241 if (updfn) while (k) updfn(cls, *path->p[--k]);
243 /* All done. The tree didn't change height. */
253 int xyla_avl_remove(struct xyla_bt_nodecls *cls, struct xyla_avl_path *path)
255 /* Remove the node identified by the PATH. The node was allocated by the
256 * caller, and should be freed by the caller in whatever way is
257 * appropriate. Returns `XYLA_RC_HTCHG' if the tree has decreased in
258 * height, or `XYLA_RC_OK' if its height remains unchanged.
261 struct xyla_avl_node *p, *s, *t;
262 struct xyla_bt_node **nlink, **plink;
263 struct xyla_bt_node *del, *subst, *replace;
264 unsigned bal, pf, sf, tf;
265 xyla_bt_updfn *updfn = cls ? cls->ops->upd : 0;
268 /* Unlink the node from the tree. */
269 rc = xyla_bt_remove(&del, &subst, &replace,
270 path->p, &path->k, XYLA_AVL_HTMAX);
272 t = AVL_NODE(del); s = AVL_NODE(subst); k = path->k;
274 /* If the node was substituted, then fix up the balance annotation. */
276 s->avl.f = (s->avl.f&~XYLA_AVLF_BALMASK) | (t->avl.f&XYLA_AVLF_BALMASK);
278 /* We've cut off a node, which makes the containing subtree shorter by one.
279 * Ascend the tree, fixing the balance annotations, and restructure it as
284 /* The containing subtree is short. Examine the parent node to decide
288 /* If we're already at the top, then the tree as a whole just got
292 V( if (xyla__verbout && (xyla__verbose&XYLA__VF_REMOVE))
293 fputs("XYLA-AVL REMOVE shorten tree\n", xyla__verbout); )
294 return (XYLA_RC_HTCHG);
297 /* Collect the parent. We'll want to keep the link address if we
300 plink = path->p[--k]; p = AVL_NODE(*plink); pf = p->avl.f;
302 /* And now we split into cases according to which side of the parent our
303 * current node is on.
305 if (nlink == &p->bt.left)
306 #define FIXUP_REMOVE(left, LBIAS, right, RBIAS, EEL, REE, XRE, XLE) do { \
307 /* The current node is the left child. */ \
309 switch (pf&XYLA_AVLF_BALMASK) { \
311 case XYLA_AVLBAL_EVEN: \
312 /* The parent was previously evenly balanced, so it's now \
313 * biased to the right. But the right subtree is still the \
314 * same height, so the change stops propagating here. \
322 V( if (xyla__verbout && (xyla__verbose&XYLA__VF_REMOVE)) \
323 fputs("XYLA-AVL REMOVE parent = (" #left " child)\n", \
325 p->avl.f = (pf&~XYLA_AVLF_BALMASK) | XYLA_AVLBAL_##RBIAS; \
326 if (updfn) updfn(cls, &p->bt); \
329 case XYLA_AVLBAL_##LBIAS: \
330 /* The parent was previously biased to the left, so it's now \
331 * evenly balanced. But it's now shorter by one as a whole, \
332 * so the change keeps propagating. \
337 * h h - 1 h - 1 h - 1 \
340 V( if (xyla__verbout && (xyla__verbose&XYLA__VF_REMOVE)) \
341 fprintf(xyla__verbout, "XYLA-AVL REMOVE parent %c " \
342 "(" #left " child)\n", AVL_BALCH(p)); ) \
343 p->avl.f = (pf&~XYLA_AVLF_BALMASK) | XYLA_AVLBAL_EVEN; \
344 if (updfn) updfn(cls, &p->bt); \
347 case XYLA_AVLBAL_##RBIAS: \
348 /* The parent was previously biased to the right, so it's now \
349 * out of balance. There are two subcases to examine, \
350 * depending on the sibling node s. \
353 s = AVL_NODE(p->bt.right); sf = s->avl.f; \
354 if ((sf&XYLA_AVLF_BALMASK) == XYLA_AVLBAL_##LBIAS) { \
355 /* The sibling is biased to the left. In particular, then, \
356 * it must have a left child node t, the `nibling'. We \
357 * restructure the tree, but it's going to get shorter as a \
358 * whole, so we'll have to continue ascending afterwards. \
363 * h - 1 (-) p ,---' `---, s \
364 * t / \ ---> (*) (*) \
365 * (*) h - 1 / \ / \ \
366 * / \ h - 1 h - 1 h - 1 h - 1 \
367 * h - 1 h - 1 h - 2 h - 2 \
370 * The resulting balance annotations depend on the initial \
371 * state of the nibling. \
380 t = AVL_NODE(s->bt.left); tf = t->avl.f; \
381 V( if (xyla__verbout && (xyla__verbose&XYLA__VF_REMOVE)) \
382 fprintf(xyla__verbout, "XYLA-AVL REMOVE " \
383 "parent %c, sibling %c, nibling %c " \
384 "(" #left " child)\n", \
385 AVL_BALCH(p), AVL_BALCH(s), AVL_BALCH(t)); ) \
386 p->bt.right = t->bt.left; t->bt.left = &p->bt; \
387 s->bt.left = t->bt.right; t->bt.right = &s->bt; \
388 t->avl.f = (tf&~XYLA_AVLF_BALMASK) | XYLA_AVLBAL_EVEN; \
389 bal = tf&XYLA_AVLF_BALMASK; \
390 p->avl.f = (pf&~XYLA_AVLF_BALMASK) | AVL_REBAL_##EEL(bal); \
391 s->avl.f = (sf&~XYLA_AVLF_BALMASK) | AVL_REBAL_##REE(bal); \
393 updfn(cls, &p->bt); updfn(cls, &s->bt); \
394 updfn(cls, &t->bt); \
398 /* The sibling is not biased to the left. Again, we \
399 * restructure the tree, and it might or might not get \
400 * shorter, depending on the balance state of s: we \
401 * continue ascending if s is evenly balanced, and stop if \
402 * it's biased to the right. (If s were left biased, then \
403 * we'd be in the other case.) \
408 * h - 1 (*) ---> (*) h \
420 V( if (xyla__verbout && (xyla__verbose&XYLA__VF_REMOVE)) \
421 fprintf(xyla__verbout, "XYLA-AVL REMOVE " \
422 "parent %c, sibling %c (" #left " child)\n", \
423 AVL_BALCH(p), AVL_BALCH(s)); ) \
424 p->bt.right = s->bt.left; s->bt.left = &p->bt; \
425 bal = sf&XYLA_AVLF_BALMASK; \
426 p->avl.f = (pf&~XYLA_AVLF_BALMASK) | AVL_REBAL_##XRE(bal); \
427 s->avl.f = (sf&~XYLA_AVLF_BALMASK) | AVL_REBAL_##XLE(bal); \
428 if (updfn) { updfn(cls, &p->bt); updfn(cls, &s->bt); } \
430 if (bal == XYLA_AVLBAL_EVEN) goto update; \
435 FIXUP_REMOVE(left, LBIAS, right, RBIAS, EEL, REE, XRE, XLE);
437 FIXUP_REMOVE(right, RBIAS, left, LBIAS, REE, EEL, ELX, ERX);
440 /* Continue ascending the tree. */
444 /* Follow the rest of the path up to the root, updating the nodes. */
446 if (updfn) while (k) updfn(cls, *path->p[--k]);
450 /*----- That's all, folks -------------------------------------------------*/