C++ Trees Part 2: Basic core::tree Functionality ...

8 downloads 0 Views 1MB Size Report
Peitgen, Heinz-Otto, Hartmut Jurgens and Dietmar Saupe. Chaos and Fractals: New Frontiers of Science. Springer-Verlag. pp 63-66. [8]. Stroustrup, Bjarne.
C++ Trees Part 2: Basic core::tree Functionality Justin Gottschlich ([email protected]) 1. Introduction One of the most highly respected computer scientists of the twenty-first century, Dr. Donald Knuth, describes trees as, “ t hemosti mpor t antnonl i near structures that arise in computeral gor i t hms. ”[ 1] As Knuth argues above, trees are important. Unfortunately, trees are also complex. In his revered series of computer science books, The Art of Computer Programming, Dr. Knuth dedicates nearly one-fifth of his first volume solely to the discussion of trees, explaining their driving concepts and numerous uses. As detailed by Knuth in his text, the concepts surrounding trees are so vast it takes a great deal of energy to convey them correctly. It is my hope that this installment of the C++ tree series will explain the core::tree design and implementation correctly (and clearly) that when finishing this article, the core::tree design, implementation and use will seem simple and natural. This article is presented in the following fashion: 1. 2. 3. 4. 5. 6. 7. 8. 9.

Introduction Terminology Concept of Design Subtleties of the core::tree The core::tree API Conclusion Acknowledgements References core::tree and core::multitree source

A detailed analysis of the design concepts used to build the core::tree and a thorough explanation of how those design concepts fit together, will be given. Moreover, all the current interfaces for the core::tree and the core::tree::iterator will be explained in detail. Coding examples are provided using the core::tree throughout the article. 2. Terminology The acronym “ STL”r ef er st oC++’ sSt andar dTempl at eLi br ar y[4]. Thet er m“ si ngl e-l evel cont ai ner ”refers to a container with a single-level, such as a vector, set, list, deque, or any of the STL containers -- not a tree container, which can potentially contain many levels. Thet er ms“ t r ee”and“ subt r ee”will be used interchangeably throughout this article. In general, both terms refer to the same concept, except; 1) a tree contains a root node that has no parent and 2) a subtree has a root node that has a parent and, thus, is a child of a parent tree (as noted by Knut h’ sdef i ni t i onoft r eesbel ow) . Thet er m“ node”wi l lbeusedt oi dent i f ythe wrapper of a single element within a container, including tree containers. Thet er m“ branch”wi l l beusedt oi dent i f yasi ngl enode within a tree container and only a tree container.

Thet er m“ t r ee”and“ subt r ee”wi l l f ol l owKnut h’ sdef i ni t i onf r om The Art of Computer Programming [2]: A finite set T of one or more nodes such that: a) there is one specially designated node called the root of the tree, root(T); and b) the remaining nodes (excluding the root) are partitioned into m >= 0 disjoint sets T1, ..., Tm, and each of these sets in turn is a tree. The trees T1, ..., Tm are called the subtrees of the root.

Figure 2.1: Simple Tree In short, the above definition say s“ trees are defined by trees” . Truthfully, there is no distinction between a node in a tree and the tree itself (as all nodes in a tree are trees themselves). Knuth notes that his definition of trees is a recursive one, as he defines trees in terms of trees. He further explains that trees can be defined in nonrecursive ways, yet it is most appropriate to define trees recursively as recursion is an innate characteristic of tree structures [3]. Figure 2.1 illustrates the basic concepts of a tree. All nodes within the blue border make up the t r eecont ai nedbynode1’ sr oot .Al l nodes within the green border on the left make up the subtree contained by node 2’ sr oot . All nodes within the green border on the right make up the subtree cont ai nedbynode3’ sr oot . Node 1 is the root node of the entire tree. There is only one root node for the tree in its entirety. However, for each subtree there is a root node for that subtree, therefore each node of a tree is considered a root node for the subtree it creates (even if that subtree contains only the root node) [11]. For example, node 2 is the root node for the subtree created from 2, 4, 5 and 6, and node 3 is the root node for the subtree created from 3, 7, 8 and 9. Node 4 is also the root node for the subtree

it creates, however, its subtree consists only of itself (node 4). The same is true of nodes 5, 6, 7, 8 and 9. 3. Concept of Design 3.1 A core::tree must be concretely constructed Although this point is probably well understood, it bears mentioning here. Following the notion all STL containers follow, the core::tree container must be concretely constructed to exist. Nothing fancy is going on here, but this is an important clarification to ensure the concepts of the core::tree are clearly understood. Asst at edi nKnut h’ sdef i ni t i onoft r ees,t her ei sonespeci al l ydesi gnat ednodeof the tree called the root. While each subtree has its own root, only one root node exists for the tree in its entirety –the root for the entire tree. Conceptually, root node of the entire tree is the initial core::tree object. Therefore, for all uses of core::tree and core::tree::iterator, at least one core::tree object must be concretely constructed. To concretely construct a core::tree, you might do the following: core::tree t; In the above code sample, t is the root node of the entire tree and is a concrete core::tree object. All modifications made through iterators based off of t are stored within the core::tree t. When t is destroyed, the entire tree t contains is destroyed. This is identical to the behavior of STL containers [5]. The point being made here may seem overly obvious (and t hat ’ sgood if it is), but it bears mentioning. The reasoning is, the core::tree and the core::tree::iterator are mostly interchangeable. However, simply constructing a core::tree::iterator will not create a core::tree. Additionally, each time a core::tree is concretely constructed, an entire tree is created. That being said, each time a core::tree::iterator is constructed, nothing more than your typical iterator is constructed. When it is destructed, only the iterator (not the tree it points to) is destroyed. 3.2 Understanding the core::tree recursive characteristic The recursive characteristic of trees is important to conceptualize when working with trees or understanding any generic framework or algorithm for trees [6] (i.e. the core::tree framework). A child node of one tree may be the parent node of another tree. While there is always one root node for the entire tree (as explained in section 3.1), a tree can have any number of children nodes. Each of these children nodes are root nodes to the subtrees they create. Therefore, the role of a node in a tree changes depending on how it is viewed. In one case, a node may be a child node for a parent, merely existing to be iterated over for the larger container it resides in. Yet, in another case, the same node may be a parent node for several children, now acting as the container of perhaps, many children nodes. This brings us to a fundamental rule for trees, which paraphrases Knuth’ sdefinition of trees: All branches within a tree are trees themselves [7]. It is from this understanding that the core::tree implementation was designed, instinctively following Knut h’ srecursive tree definition and the notion of self-similarity (the concept that some

“ t hi ngs”ar emade up of pieces that are similar t ot he“ t hi ng”t heyar emaki ngup, for example, trees) common in the field of chaos [7]. The following two concepts are fundamental to understanding the design of the core::tree family: a core::tree is a core::tree::iterator a core::tree::iterator is a core::tree The above two concepts (trees as iterators and iterators as trees) are “ most l y ”true. Thus, it is not 100% accurate to say a core::tree is a core::tree::iterator or that a core::tree::iterator is a core::tree. In actuality, a core::tree is merely wrapped within a core::tree::iterator when an iterator is needed from a tree. Moreover, a core::tree::iterator merely passes on messages to the core::tree it points to when core::tree container functionality is needed (although it is technically correct to say a core::tree::iterator is a core::tree as the core::tree::iterator class derives from the core::tree class). In both cases (trees as iterators and iterators as trees), it is important to understand that; 1) when given a core::tree, you also are given a core::tree::iterator –as any tree is also a node of a tree and 2) when given a core::tree::iterator, you are also given a core::tree –as any node within a tree is also a tree, itself. The following two sections breakdown the above two concepts; 3.3) trees as iterators and 3.4) iterators as trees. A great deal of detail is spent clarifying these concepts as they are building blocks of the core::tree functionality. 3.3 A core::tree is core::tree::iterator Eachnodewi t hi nat r eei sat r eei t sel f .Thi sf ol l owsKnut h’ sdef i ni t i onoft r ees.However ,each node within a tree is just that as well, a node. Therefore, any level of a tree can be thought of as 1) a single-level store of trees and 2) a single-level store of nodes. For example, consider the following tree in figure 3.1:

Figure 3.1: Isolating a single-level of a tree

In the green highlighted section of figure 3.1, two nodes (b and c) are identified as children of node a. While b and c are subtrees themselves (i.e. containers), they must also be accessible from their containing parent. In order to first conceptualize this, consider b and c outside of the context of a tree and instead, in a single-level container. If b and c were contained in a single-level (outside of a tree), they might look something like this:

Figure 3.2: Single-level view of a tree Figure 3.2 shows how b and c would be contained by themselves outside of the context of a tree. Given this context, b and c could be wrapped in single-level container. For example, b and c could be stored within an std::set: std::set s; s.insert( ‘ b’ ) ; s.insert( ‘ c’ ) ; To iterate through the single-level set, the following could be done: for (std::set::iterator iter = s.begin(); iter != s.end(); ++iter) { std::cout