122 lines
5.9 KiB
ReStructuredText
122 lines
5.9 KiB
ReStructuredText
.. _rbtree_api:
|
|
|
|
Balanced Red/Black Tree
|
|
=======================
|
|
|
|
For circumstances where sorted containers may become large at runtime,
|
|
a list becomes problematic due to algorithmic costs of searching it.
|
|
For these situations, Zephyr provides a balanced tree implementation
|
|
which has runtimes on search and removal operations bounded at
|
|
O(log2(N)) for a tree of size N. This is implemented using a
|
|
conventional red/black tree as described by multiple academic sources.
|
|
|
|
The :c:struct:`rbtree` tracking struct for a rbtree may be initialized
|
|
anywhere in user accessible memory. It should contain only zero bits
|
|
before first use. No specific initialization API is needed or
|
|
required.
|
|
|
|
Unlike a list, where position is explicit, the ordering of nodes
|
|
within an rbtree must be provided as a predicate function by the user.
|
|
A function of type :c:func:`rb_lessthan_t` should be assigned to the
|
|
``lessthan_fn`` field of the :c:struct:`rbtree` struct before any tree
|
|
operations are attempted. This function should, as its name suggests,
|
|
return a boolean True value if the first node argument is "less than"
|
|
the second in the ordering desired by the tree. Note that "equal" is
|
|
not allowed, nodes within a tree must have a single fixed order for
|
|
the algorithm to work correctly.
|
|
|
|
As with the slist and dlist containers, nodes within an rbtree are
|
|
represented as a :c:struct:`rbnode` structure which exists in
|
|
user-managed memory, typically embedded within the the data structure
|
|
being tracked in the tree. Unlike the list code, the data within an
|
|
rbnode is entirely opaque. It is not possible for the user to extract
|
|
the binary tree topology and "manually" traverse the tree as it is for
|
|
a list.
|
|
|
|
Nodes can be inserted into a tree with :c:func:`rb_insert` and removed
|
|
with :c:func:`rb_remove`. Access to the "first" and "last" nodes within a
|
|
tree (in the sense of the order defined by the comparison function) is
|
|
provided by :c:func:`rb_get_min` and :c:func:`rb_get_max`. There is also a
|
|
predicate, :c:func:`rb_contains`, which returns a boolean True if the
|
|
provided node pointer exists as an element within the tree. As
|
|
described above, all of these routines are guaranteed to have at most
|
|
log time complexity in the size of the tree.
|
|
|
|
There are two mechanisms provided for enumerating all elements in an
|
|
rbtree. The first, :c:func:`rb_walk`, is a simple callback implementation
|
|
where the caller specifies a C function pointer and an untyped
|
|
argument to be passed to it, and the tree code calls that function for
|
|
each node in order. This has the advantage of a very simple
|
|
implementation, at the cost of a somewhat more cumbersome API for the
|
|
user (not unlike ISO C's :c:func:`bsearch` routine). It is a recursive
|
|
implementation, however, and is thus not always available in
|
|
environments that forbid the use of unbounded stack techniques like
|
|
recursion.
|
|
|
|
There is also a :c:macro:`RB_FOR_EACH` iterator provided, which, like the
|
|
similar APIs for the lists, works to iterate over a list in a more
|
|
natural way, using a nested code block instead of a callback. It is
|
|
also nonrecursive, though it requires log-sized space on the stack by
|
|
default (however, this can be configured to use a fixed/maximally size
|
|
buffer instead where needed to avoid the dynamic allocation). As with
|
|
the lists, this is also available in a :c:macro:`RB_FOR_EACH_CONTAINER`
|
|
variant which enumerates using a pointer to a container field and not
|
|
the raw node pointer.
|
|
|
|
Tree Internals
|
|
--------------
|
|
|
|
As described, the Zephyr rbtree implementation is a conventional
|
|
red/black tree as described pervasively in academic sources. Low
|
|
level details about the algorithm are out of scope for this document,
|
|
as they match existing conventions. This discussion will be limited
|
|
to details notable or specific to the Zephyr implementation.
|
|
|
|
The core invariant guaranteed by the tree is that the path from the root of
|
|
the tree to any leaf is no more than twice as long as the path to any
|
|
other leaf. This is achieved by associating one bit of "color" with
|
|
each node, either red or black, and enforcing a rule that no red child
|
|
can be a child of another red child (i.e. that the number of black
|
|
nodes on any path to the root must be the same, and that no more than
|
|
that number of "extra" red nodes may be present). This rule is
|
|
enforced by a set of rotation rules used to "fix" trees following
|
|
modification.
|
|
|
|
.. figure:: rbtree.png
|
|
:align: center
|
|
:alt: rbtree example
|
|
:figclass: align-center
|
|
|
|
A maximally unbalanced rbtree with a black height of two. No more
|
|
nodes can be added underneath the rightmost node without
|
|
rebalancing.
|
|
|
|
These rotations are conceptually implemented on top of a primitive
|
|
that "swaps" the position of one node with another in the list.
|
|
Typical implementations effect this by simply swapping the nodes
|
|
internal "data" pointers, but because the Zephyr :c:struct:`rbnode` is
|
|
intrusive, that cannot work. Zephyr must include somewhat more
|
|
elaborate code to handle the edge cases (for example, one swapped node
|
|
can be the root, or the two may already be parent/child).
|
|
|
|
The :c:struct:`rbnode` struct for a Zephyr rbtree contains only two
|
|
pointers, representing the "left", and "right" children of a node
|
|
within the binary tree. Traversal of a tree for rebalancing following
|
|
modification, however, routinely requires the ability to iterate
|
|
"upwards" from a node as well. It is very common for red/black trees
|
|
in the industry to store a third "parent" pointer for this purpose.
|
|
Zephyr avoids this requirement by building a "stack" of node pointers
|
|
locally as it traverses downward through the tree and updating it
|
|
appropriately as modifications are made. So a Zephyr rbtree can be
|
|
implemented with no more runtime storage overhead than a dlist.
|
|
|
|
These properties, of a balanced tree data structure that works with
|
|
only two pointers of data per node and that works without any need for
|
|
a memory allocation API, are quite rare in the industry and are
|
|
somewhat unique to Zephyr.
|
|
|
|
Red/Black Tree API Reference
|
|
--------------------------------
|
|
|
|
.. doxygengroup:: rbtree_apis
|