1.\" 2.\" This file and its contents are supplied under the terms of the 3.\" Common Development and Distribution License ("CDDL"), version 1.0. 4.\" You may only use this file in accordance with the terms of version 5.\" 1.0 of the CDDL. 6.\" 7.\" A full copy of the text of the CDDL should have accompanied this 8.\" source. A copy of the CDDL is also available via the Internet at 9.\" http://www.illumos.org/license/CDDL. 10.\" 11.\" 12.\" Copyright 2015 Joyent, Inc. 13.\" Copyright 2024 Oxide Computer Company 14.\" 15.Dd January 27, 2024 16.Dt LIBAVL 3LIB 17.Os 18.Sh NAME 19.Nm libavl 20.Nd generic self-balancing binary search tree library 21.Sh SYNOPSIS 22.Lb libavl 23.In sys/avl.h 24.Sh DESCRIPTION 25The 26.Nm 27library provides a generic implementation of AVL trees, a form of 28self-balancing binary tree. 29The interfaces provided allow for an efficient way of implementing an ordered 30set of data structures and, due to its embeddable nature, allow for a single 31instance of a data structure to belong to multiple AVL trees. 32.Lp 33Each AVL tree contains entries of a single type of data structure. 34Rather than allocating memory for pointers for those data structures, 35the storage for the tree is embedded into the data structures by 36declaring a member of type 37.Vt avl_node_t . 38When an AVL tree is created, through the use of 39.Fn avl_create , 40it encodes the size of the data structure, the offset of the data 41structure, and a comparator function which is used to compare two 42instances of a data structure. 43A data structure may be a member of multiple AVL trees by creating AVL trees 44which use different offsets (different members) into the data structure. 45.Lp 46AVL trees support both look up of an arbitrary item and ordered 47iteration over the contents of the entire tree. 48In addition, from any node, you can find the previous and next entries in the 49tree, if they exist. 50In addition, AVL trees support arbitrary insertion and deletion. 51.Ss Performance 52AVL trees are often used in place of linked lists. 53Compared to the standard, intrusive, doubly linked list, it has the following 54performance characteristics: 55.Bl -hang -width Ds 56.It Sy Lookup One Node 57.Bd -filled -compact 58Lookup of a single node in a linked list is 59.Sy O(n) , 60whereas lookup of a single node in an AVL tree is 61.Sy O(log(n)) . 62.Ed 63.It Sy Insert One Node 64.Bd -filled -compact 65Inserting a single node into a linked list is 66.Sy O(1) . 67Inserting a single node into an AVL tree is 68.Sy O(log(n)) . 69.Pp 70Note, insertions into an AVL tree always result in an ordered tree. 71Insertions into a linked list do not guarantee order. 72If order is required, then the time to do the insertion into a linked list will 73depend on the time of the search algorithm being employed to find the place to 74insert at. 75.Ed 76.It Sy Delete One Node 77.Bd -filled -compact 78Deleting a single node from a linked list is 79.Sy O(1), 80whereas deleting a single node from an AVL tree takes 81.Sy O(log(n)) 82time. 83.Ed 84.It Sy Delete All Nodes 85.Bd -filled -compact 86Deleting all nodes from a linked list is 87.Sy O(n) . 88With an AVL tree, if using the 89.Xr avl_destroy_nodes 3AVL 90function then deleting all nodes 91is 92.Sy O(n) . 93However, if iterating over each entry in the tree and then removing it using 94a while loop, 95.Xr avl_first 3AVL 96and 97.Xr avl_remove 3AVL 98then the time to remove all nodes is 99.Sy O(n\ *\ log(n)). 100.Ed 101.It Sy Visit the Next or Previous Node 102.Bd -filled -compact 103Visiting the next or previous node in a linked list is 104.Sy O(1) , 105whereas going from the next to the previous node in an AVL tree will 106take between 107.Sy O(1) 108and 109.Sy O(log(n)) . 110.Ed 111.El 112.Pp 113In general, AVL trees are a good alternative for linked lists when order 114or lookup speed is important and a reasonable number of items will be 115present. 116.Sh INTERFACES 117The shared object 118.Sy libavl.so.1 119provides the public interfaces defined below. 120See 121.Xr Intro 3 122for additional information on shared object interfaces. 123Individual functions are documented in their own manual pages. 124.Bl -column -offset indent ".Sy avl_is_empty" ".Sy avl_destroy_nodes" 125.It Sy avl_add Ta Sy avl_create 126.It Sy avl_destroy Ta Sy avl_destroy_nodes 127.It Sy avl_find Ta Sy avl_first 128.It Sy avl_insert Ta Sy avl_insert_here 129.It Sy avl_is_empty Ta Sy avl_last 130.It Sy avl_nearest Ta Sy avl_numnodes 131.It Sy avl_remove Ta Sy avl_swap 132.It Sy avl_update Ta Sy avl_update_gt 133.It Sy avl_update_lt Ta 134.El 135.Pp 136In addition, the library defines C pre-processor macros which are 137defined below and documented in their own manual pages. 138.\" 139.\" Use the same column widths in both cases where we describe the list 140.\" of interfaces, to allow the manual page to better line up when rendered. 141.\" 142.Bl -column -offset indent ".Sy avl_is_empty" ".Sy avl_destroy_nodes" 143.It Sy AVL_NEXT Ta Sy AVL_PREV 144.El 145.Sh TYPES 146The 147.Nm 148library defines the following types: 149.Lp 150.Sy avl_tree_t 151.Lp 152Type used for the root of the AVL tree. 153Consumers define one of these for each of the different trees that they want to 154have. 155.Lp 156.Sy avl_node_t 157.Lp 158Type used as the data node for an AVL tree. 159One of these is embedded in each data structure that is the member of an AVL 160tree. 161.Lp 162.Sy avl_index_t 163.Lp 164Type used to locate a position in a tree. 165This is used with 166.Xr avl_find 3AVL 167and 168.Xr avl_insert 3AVL . 169.Sh LOCKING 170The 171.Nm 172library provides no locking. 173Callers that are using the same AVL tree from multiple threads need to provide 174their own synchronization. 175If only one thread is ever accessing or modifying the AVL tree, then there are 176no synchronization concerns. 177If multiple AVL trees exist, then they may all be used simultaneously; however, 178they are subject to the same rules around simultaneous access from a single 179thread. 180.Lp 181All routines are both 182.Sy Fork-safe 183and 184.Sy Async-Signal-Safe . 185Callers may call functions in 186.Nm 187from a signal handler and 188.Nm 189calls are all safe in face of 190.Xr fork 2 ; 191however, if callers have their own locks, then they must make sure that 192they are accounted for by the use of routines such as 193.Xr pthread_atfork 3C . 194.Sh EXAMPLES 195The following code shows examples of exercising all of the functionality 196that is present in 197.Nm . 198It can be compiled by using a C compiler and linking 199against 200.Nm . 201For example, given a file named avl.c, with gcc, one would run: 202.Bd -literal 203$ gcc -Wall -o avl avl.c -lavl 204.Ed 205.Bd -literal 206/* 207 * Example of using AVL Trees 208 */ 209 210#include <sys/avl.h> 211#include <assert.h> 212#include <stddef.h> 213#include <stdlib.h> 214#include <stdio.h> 215 216static avl_tree_t inttree; 217 218/* 219 * The structure that we're storing in an AVL tree. 220 */ 221typedef struct intnode { 222 int in_val; 223 avl_node_t in_avl; 224} intnode_t; 225 226static int 227intnode_comparator(const void *l, const void *r) 228{ 229 const intnode_t *li = l; 230 const intnode_t *ri = r; 231 232 if (li->in_val > ri->in_val) 233 return (1); 234 if (li->in_val < ri->in_val) 235 return (-1); 236 return (0); 237} 238 239/* 240 * Create an AVL Tree 241 */ 242static void 243create_avl(void) 244{ 245 avl_create(&inttree, intnode_comparator, sizeof (intnode_t), 246 offsetof(intnode_t, in_avl)); 247} 248 249/* 250 * Add entries to the tree with the avl_add function. 251 */ 252static void 253fill_avl(void) 254{ 255 int i; 256 intnode_t *inp; 257 258 for (i = 0; i < 20; i++) { 259 inp = malloc(sizeof (intnode_t)); 260 assert(inp != NULL); 261 inp->in_val = i; 262 avl_add(&inttree, inp); 263 } 264} 265 266/* 267 * Find entries in the AVL tree. Note, we create an intnode_t on the 268 * stack that we use to look this up. 269 */ 270static void 271find_avl(void) 272{ 273 int i; 274 intnode_t lookup, *inp; 275 276 for (i = 10; i < 30; i++) { 277 lookup.in_val = i; 278 inp = avl_find(&inttree, &lookup, NULL); 279 if (inp == NULL) { 280 printf("Entry %d is not in the tree\en", i); 281 } else { 282 printf("Entry %d is in the tree\en", 283 inp->in_val); 284 } 285 } 286} 287 288/* 289 * Walk the tree forwards 290 */ 291static void 292walk_forwards(void) 293{ 294 intnode_t *inp; 295 for (inp = avl_first(&inttree); inp != NULL; 296 inp = AVL_NEXT(&inttree, inp)) { 297 printf("Found entry %d\en", inp->in_val); 298 } 299} 300 301/* 302 * Walk the tree backwards. 303 */ 304static void 305walk_backwards(void) 306{ 307 intnode_t *inp; 308 for (inp = avl_last(&inttree); inp != NULL; 309 inp = AVL_PREV(&inttree, inp)) { 310 printf("Found entry %d\en", inp->in_val); 311 } 312} 313 314/* 315 * Determine the number of nodes in the tree and if it is empty or 316 * not. 317 */ 318static void 319inttree_inspect(void) 320{ 321 printf("The tree is %s, there are %ld nodes in it\en", 322 avl_is_empty(&inttree) == B_TRUE ? "empty" : "not empty", 323 avl_numnodes(&inttree)); 324} 325 326/* 327 * Use avl_remove to remove entries from the tree. 328 */ 329static void 330remove_nodes(void) 331{ 332 int i; 333 intnode_t lookup, *inp; 334 335 for (i = 0; i < 20; i+= 4) { 336 lookup.in_val = i; 337 inp = avl_find(&inttree, &lookup, NULL); 338 if (inp != NULL) 339 avl_remove(&inttree, inp); 340 } 341} 342 343/* 344 * Find the nearest nodes in the tree. 345 */ 346static void 347nearest_nodes(void) 348{ 349 intnode_t lookup, *inp; 350 avl_index_t where; 351 352 lookup.in_val = 12; 353 if (avl_find(&inttree, &lookup, &where) != NULL) 354 abort(); 355 inp = avl_nearest(&inttree, where, AVL_BEFORE); 356 assert(inp != NULL); 357 printf("closest node before 12 is: %d\en", inp->in_val); 358 inp = avl_nearest(&inttree, where, AVL_AFTER); 359 assert(inp != NULL); 360 printf("closest node after 12 is: %d\en", inp->in_val); 361} 362 363static void 364insert_avl(void) 365{ 366 intnode_t lookup, *inp; 367 avl_index_t where; 368 369 lookup.in_val = 12; 370 if (avl_find(&inttree, &lookup, &where) != NULL) 371 abort(); 372 inp = malloc(sizeof (intnode_t)); 373 assert(inp != NULL); 374 avl_insert(&inttree, inp, where); 375} 376 377static void 378swap_avl(void) 379{ 380 avl_tree_t swap; 381 382 avl_create(&swap, intnode_comparator, sizeof (intnode_t), 383 offsetof(intnode_t, in_avl)); 384 avl_swap(&inttree, &swap); 385 inttree_inspect(); 386 avl_swap(&inttree, &swap); 387 inttree_inspect(); 388} 389 390static void 391update_avl(void) 392{ 393 intnode_t lookup, *inp; 394 avl_index_t where; 395 396 lookup.in_val = 9; 397 inp = avl_find(&inttree, &lookup, &where); 398 assert(inp != NULL); 399 inp->in_val = 25; 400 avl_update(&inttree, inp); 401} 402 403/* 404 * Remove all remaining nodes in the tree. We first use 405 * avl_destroy_nodes to empty the tree, then avl_destroy to finish. 406 */ 407static void 408cleanup(void) 409{ 410 intnode_t *inp; 411 void *c = NULL; 412 413 while ((inp = avl_destroy_nodes(&inttree, &c)) != NULL) { 414 free(inp); 415 } 416 avl_destroy(&inttree); 417} 418 419int 420main(void) 421{ 422 create_avl(); 423 inttree_inspect(); 424 fill_avl(); 425 find_avl(); 426 walk_forwards(); 427 walk_backwards(); 428 inttree_inspect(); 429 remove_nodes(); 430 inttree_inspect(); 431 nearest_nodes(); 432 insert_avl(); 433 inttree_inspect(); 434 swap_avl(); 435 update_avl(); 436 cleanup(); 437 return (0); 438} 439.Ed 440.Sh INTERFACE STABILITY 441.Sy Committed 442.Sh MT-Level 443See 444.Sx Locking. 445.Sh SEE ALSO 446.Xr Intro 3 , 447.Xr pthread_atfork 3C 448.Lp 449.Xr avl_add 3AVL , 450.Xr avl_create 3AVL , 451.Xr avl_destroy 3AVL , 452.Xr avl_destroy_nodes 3AVL , 453.Xr avl_find 3AVL , 454.Xr avl_first 3AVL , 455.Xr avl_insert 3AVL , 456.Xr avl_insert_here 3AVL , 457.Xr avl_is_empty 3AVL , 458.Xr avl_last 3AVL , 459.Xr avl_nearest 3AVL , 460.Xr avl_numnodes 3AVL , 461.Xr avl_remove 3AVL , 462.Xr avl_swap 3AVL , 463.Xr avl_update 3AVL , 464.Xr avl_update_gt 3AVL , 465.Xr avl_update_lt 3AVL 466.Rs 467.%A Adel'son-Vel'skiy, G. M. 468.%A Landis, Ye. M. 469.%T "An Algorithm for the Organization of Information" 470.%Q Deklady Akademii Nauk 471.%C USSR, Moscow 472.%P 263-266 473.%V Vol. 16 474.%N No. 2 475.%D 1962 476.Re 477