Binary-Tree Interpretations: Issues, and Designs
-
Binary-Trees are a recursive data structure
- Binary-Trees are not trees: they are a (ML like)
recursive data type. Interpretations then factor into two steps:
- Interpretation of recursive data types
- The problem is how to define a constructive sort to implement
the recursive sort. A primary technique is to introduce a sort of atoms
("objects" or "identifiers") for the elements and interpret the constructors
and destructors as maps over the atoms. A refinement of this technique is
to introduce a product (record) structure and interpret each recursive
structure (e.g., binary-tree) as a map from identifiers to tuples some of
whose components are identifiers.
- Interpretation of the data types introduced
- For example, interpretation of the maps as ARRAYs or LISTs.
- BINARY-TREEs as NODE-ID-ARRAYs
-
The interpretation and mediator are in
binary-tree-as-node-id-array.re
The interpretationof BINARY-TREEs as NODE-ID-ARRAYs bypasses maps
because the map interpretation requires so much structure (FINITE-SORT
domain etc) that the resulting spec is equivalent to ARRAY. Hence, the map
step just adds a layer of complex complex specs and interpretations. See the
discussion of the interpretation below.
Binary-Trees as Array and Root
Abstractly, binary trees are maps from Node-Ids to tuples of Val,
(left) Node-Id, and (right) Node-Id together with a distinguished Node-Id
representing the root of the tree. In order to facilate the interpretation
NODE-ID is specified as a definitional extension of NAT.
The actual interpretation is constructed using the
SORT-AS-QUOTIENT-OF-SUBSORT template as follows.
Interpretation of the sorts
-
Concrete-Sort
- The Concrete-Sort is Array-and-Root. I.e.,
tuples of a root Node-Id and an Array of tuples of
Val, Node-Id, Node-Id
- Admissible-Rep-Subsort
- The admissible-rep? predicate is: valid-tree?
Abstractly, an Array-and-Root pair <A r> is a
valid-tree? iff either r is the empty-node-id?
(represents the empty-bbt) or r is a join-node-id? and
the left and right Node-Ids represent binary-trees in A. In
fact, we use an even stronger predicate: the Array-and-Root
represents a binary-tree and the representation contains no junk
(each element in the defined-dom of the Array represents
some node of the tree) and no holes (if n is in the defined-dom
of the array,
then so is each m < n). This is equivalent to: the root represents a
tree in A and the number of join nodes is one less than the size of A.
- Note on order
- The order (pre-, infix-, post-, etc) of the nodes in the array is not
fixed by the admissible-rep? predicate. Fixing this order permits
further optimization of the basic operations. In particular, once an
order is specified, there will be just one element in each equivalence class.
Hence, tree-equiv? would reduce to Array equal. Although the
admissable reps are not restricted to reps with a particular order, the
interpretations of the basic operations generate only post-order reps.
In fact, it generates reps that are in what we will call a strong post-order
form: every Node-Id of the left subtree precedes every
Node-Id of the right subtree which in turn precedes the root (with
necesary provisos for the empty Node-Id).
- Abstract-Sort
- The Abstract-Sort is obtained by factoring the
valid-tree?s via the equivalence relation tree-equiv?.
<A1 r1> is tree-equiv? <A2 r2> iff either r1 and r2 are
both empty Node-Id-ids (= zero) or both are join Node-Id
and the value at r1 in A1 equals the value at r2 in A2 and the corresponding
left and right subtrees are tree-equiv?.
Some design issues
- Order of traversal: post-fix
- The order of traversal is apparent in two places: in recoding,
the order used determines which result we get; in constructing
a result, we get the same result regardless of the order used. The
particular traversal order we use for generating new codes (node-ids) is
(strong) post-fix order: recode the left subtree, then recode the right
subtree, and then recode the current node.
- Totality
- Simple totality of arrays (defined on each nat less than their
size) is achieved by: using null-val for the value of the
empty-bbt; allocating an array of the precise size needed for the
binary-tree being constructed (in each case the size of the result of a basic
operation is a simple function of the size of the args).
Recoding
-
(bt-recode next-code old root new-so-far code-so-far)
-
- next-code: (Node-Id -> Node-Id)
- A recoding function which takes a single arg, code-so-far.
The actual recoding function used is least-unused-code which
simply adds one to the last new code used.
- old: Array; root: Node-Id
- A concrete binary-tree rep in the form: old (Array) and root.
- new-so-far: Array
- A partial result (whose incremented value is returned).
- code-so-far: Node-Id
- Contains the history of the recoding (i.e., the map from old node-ids
to new node-ids, here simplified to just the last new node-id used)
- returns:
tuple of new-array-so-far, new-root, new-code-so-far
-
Interpretation of the basic operations
- empty-bbt
- The empty-bbt is represented by (the equivalence class
containing) the pair: (empty-tree-array one), zero.
(empty-tree-array n) is a constant array of size n whose elements
are all of the form: <null-val zero zero>
- (join l-btr r-btr)
- The array and root pair for
(join* <Al rl> <Ar rr>) is constructed as follows:
- allocate an empty-tree-array rslt of size = size of Al + size of Ar
- recode/copy Al into rslt yielding l-rslt
- recode/copy Ar into l-rslt yielding r-rslt
- construct join node
- (left btr)
- The array and root pair for
(left* <A r> is constructed as follows:
- allocate an empty-tree-array rslt of size of left subtree
- recode/copy the left subtree of A into rslt
- (right btr)
- The array and root pair for
(right* <A r> is constructed as follows:
- allocate an empty-tree-array rslt of size of right subtree
- recode/copy the right subtree of A into rslt