Chapter 7. Coordinate Structures
7.1. Bifurcate Coordinates
Iterators allow us to traverse linear structures, which have a single successor at each position. While there are data structures with an arbitrary number of successors, in this chapter we study an important case of structures with exactly two successors at every position, labeled left and right. In order to define algorithms on these structures, we define the following concept:
The WeightType type function returns a type capable of counting all the objects in a traversal that uses a bifurcate coordinate. WeightType is analogous to DistanceType for an iterator type.
The predicate empty is everywhere defined. If it returns true, none of the other procedures are defined. empty is the negation of the definition-space predicate for both has_left_successor and has_right_successor. has_left_successor is the definition-space predicate for left_successor, and has_right_successor is the definition-space predicate for right_successor. In other words, if a bifurcate coordinate is not empty, has_left_successor and has_right_successor are defined; if either one of them returns true, the corresponding successor function is defined. With iterators, algorithms use a limit or count to indicate the end of a range. With bifurcate coordinates, there are many positions at which branches end. Therefore it is more natural to introduce the predicates has_left_successor and has_right_successor for determining whether a coordinate has successors.
In this book we describe algorithms on BifurcateCoordinate, where all the operations are regular. This is different from the Iterator concept, where the most fundamental algorithms, such as find, do not require regularity of successor and where there are nonregular models, such as input streams. Structures where application of left_successor and right_successor change the shape of the underlying binary tree require a concept of WeakBifurcateCoordinate, where the operations are not regular.
The shape of a structure accessed via iterators is possibly cyclic for a weak range and is a linear segment for a counted or bounded range. In order to discuss the shape of a structure accessed via bifurcate coordinates, we need a notion of reachability.
A bifurcate coordinate y is a proper descendant of another coordinate x if y is the left or right
successor of x or if it is a proper descendant of the left or right successor of x. A bifurcate coordinate y is a descendant of a coordinate x if y = x or y is a proper descendant of x.
The descendants of x form a directed acyclic graph (DAG) if for all y in the descendants of x, y is not its own descendant. In other words, no sequence of successors of any coordinate leads back to itself.
x is called the root of the DAG of its descendants. If the descendants of x form a DAG and are finite in
number, they form a finite DAG. The height of a finite DAG is one more than the maximum sequence of successors starting from its root, or zero if it is empty.
A bifurcate coordinate y is left reachable from x if it is a descendant of the left successor of x, and similarly for right reachable.
The descendants of x form a tree if they form a finite DAG and for all y, z in the descendants of x, z is not both left reachable and right reachable from y. In other words, there is a unique sequence of successors from a coordinate to any of its descendants. The property of being a tree serves the same
BifurcateCoordinate(T)
Regular(T)
WeightType: BifurcateCoordinate Integer
empty: T bool
has_left_successor: T bool has_right_successor: T bool left_successor: T T
right_successor: T T
( i, j T)(left_successor (i) = j right_successor (i) = j) ¬
purpose for the algorithms in this chapter as the properties of being a bounded or counted range served in Chapter 6, with finiteness guaranteeing termination:
These are the recursive algorithms for computing the weight and height of a tree:
template<typename C> requires(BifurcateCoordinate(C)) WeightType(C) weight_recursive(C c) { // Precondition: tree(c) typedef WeightType(C) N; if (empty(c)) return N(0); N l(0); N r(0); if (has_left_successor(c)) l = weight_recursive(left_successor(c)); if (has_right_successor(c)) r = weight_recursive(right_successor(c)); return_successor(l + r); } template<typename C> requires(BifurcateCoordinate(C)) WeightType(C) height_recursive(C c) { // Precondition: tree(c) typedef WeightType(C) N; if (empty(c)) return N(0); N l(0); N r(0); if (has_left_successor(c)) l = height_recursive(left_successor(c)); if (has_right_successor(c)) r = height_recursive(right_successor(c)); return successor(max(l, r)); } Lemma 7.1.
height_recursive correctly computes the height of a DAG but visits each coordinate as many times as there are paths to it; this fact means that weight_recursive does not correctly compute the weight of a DAG. Algorithms for traversing DAGs and cyclic structures require marking: a way of remembering which coordinates have been previously visited.
There are three primary depth-first tree-traversal orders. All three fully traverse the left descendants and then the right descendants. Preorder visits to a coordinate occur before the traversal of its descendants; inorder visits occur between the traversals of the left and right descendants; postorder visits occur after traversing all descendants. We name the three visits with the following type definition:
enum visit { pre, in, post }; property(C :BifurcateCoordinate)
tree : C
x the descendants of x form a tree
We can perform any combination of the traversals with a single procedure that takes as a parameter another procedure taking the visit together with the coordinate:
template<typename C, typename Proc> requires(BifurcateCoordinate(C) &&
Procedure(Proc) && Arity(Proc) == 2 && visit == InputType(Proc, 0) &&
C == InputType(Proc, 1)) Proc traverse_nonempty(C c, Proc proc) {
// Precondition: tree(c) ¬empty(c) proc(pre, c);
if (has_left_successor(c))
proc = traverse_nonempty(left_successor(c), proc); proc(in, c);
if (has_right_successor(c))
proc = traverse_nonempty(right_successor(c), proc); proc(post, c);
return proc; }
Algorithms C++ Software Engineering Programming Alexander Stepanov Paul McJones Addison-Wesley Professional Elements of Programming