My implementation of the lowest common ancestor (LCA) algorithm for my tree data structure. I selected the non-preprocessed (naïve) implementation for this first implementation. Support for any number of input positions (1+) seemed appropriate. The tree is not binary and a node-based implementation. Any aspect of the code posted is fair game for feedback and criticism.
//! @brief Finds the nearest, ancestorial, common element of the positions.
//!
//! @details The lowest common ancestor (LCA) element of two or more elements in
//! a tree is the lowest, deepest element that has both elements as descendants.
//! That is the last, shared ancestor located farthest from the root. The LCA
//! element of a single valid position iterator is that element iterator itself.
//! The LCA element of the single end position iterator is the end iterator
//! similarly, the LCA element of a collection of position iterators one or more
//! of which is the end iterator is the end iterator because there exists no
//! valid LCA element for the collection.
//!
//! @param first Tree iterator to the first element position of the collection
//! to find the LCA.
//!
//! @param positions The optional second and other remaining element iterators
//! of the collection to find the LCA.
//!
//! @return Iterator to the common most ancestorial element of the elements.
//! Returns the `end` iterator if any position is the `end` iterator which is
//! equal to the iterator to the element past the container's last element.
//!
//! @complexity Quadratic in the number of nodes sought from by the height of
//! the tree container.
[[nodiscard]] constexpr auto
lowest_common_ancestor_element(TreeIterator auto first,
TreeIterator auto... positions)
{
if constexpr (sizeof...(positions)) {
return [](TreeIterator auto first, TreeIterator auto second,
TreeIterator auto... positions) {
using iterator_type = decltype(first);
if (first == second) {
return lowest_common_ancestor_element(first, positions...);
}
if (!first.node) {
return first;
}
if (!second.node) {
return second;
}
auto *first_ancestor = first.node;
do {
if (!first_ancestor->parent) {
return lowest_common_ancestor_element(iterator_type{ first_ancestor },
positions...);
}
auto *second_ancestor = second.node;
while ((second_ancestor = second_ancestor->parent)) {
if (first_ancestor == second_ancestor) {
return lowest_common_ancestor_element(
iterator_type{ first_ancestor }, positions...);
}
};
} while ((first_ancestor = first_ancestor->parent));
// Unreachable code under nominal use case. Other invalid cases may have
// returned early. The result of the application of library functions to
// invalid ranges is undefined per 23.3.1
// [iterator.requirements.general]/12.
return iterator_type{};
}(first, positions...);
}
return first;
}
Unclear areas:
- Is my understanding of the complexity
O(Ih)
correct? WhereI
is the number of input node andh
the tree height, for a quadratic complexity or simplifies to linear complexity inh
forO(h)
? It seems to be accepted asO(h)
for the typical case for two nodesI = 2
but is it applicable here? - Is this implementation actually recursive in the number of input position?
The code is tested and updated here.