Beruflich Dokumente
Kultur Dokumente
1 Introduction.............................................................................................................................................3
1.1 Notation...........................................................................................................................................4
2 Tree.........................................................................................................................................................4
References............................................................................................................................................4
2.1 Abstract concept..............................................................................................................................4
2.1.1 [R] Definition..........................................................................................................................4
2.1.2 [R] Informal recursive definition.............................................................................................5
2.1.3 Tree-related terms....................................................................................................................6
2.1.4 Formal definition.....................................................................................................................9
2.1.5 Abstract properties.................................................................................................................10
2.1.6 Operations..............................................................................................................................10
2.2 Design specification......................................................................................................................11
2.2.1 Overview................................................................................................................................11
2.2.2 Recursive bottom-up design..................................................................................................12
2.2.3 Recursive top-down design....................................................................................................15
2.3 Implementation..............................................................................................................................19
2.4 [0] Reasoning................................................................................................................................19
2.4.1 Structural induction...............................................................................................................19
2.5 Application examples....................................................................................................................19
2.5.1 A bottom-up tree....................................................................................................................19
2.5.2 A top-down tree.....................................................................................................................20
2.6 [R] Exercises.................................................................................................................................21
References......................................................................................................................................21
3 List........................................................................................................................................................24
3.1 Abstract concept............................................................................................................................24
References......................................................................................................................................24
3.1.1 [R] Definition........................................................................................................................24
3.1.2 List-related terms...................................................................................................................25
3.1.3 List as a tree...........................................................................................................................27
3.1.4 Recursive definition...............................................................................................................27
3.1.5 Abstract properties.................................................................................................................27
3.1.6 Operations..............................................................................................................................28
3.2 Design specification......................................................................................................................29
3.2.1 Overview................................................................................................................................29
3.2.2 Recursive list.........................................................................................................................29
3.2.3 Linked list..............................................................................................................................34
3.2.4 [0] Array-based design..........................................................................................................37
3.3 Implementation..............................................................................................................................38
3.3.1 Recursive list.........................................................................................................................38
3.3.2 Linked list..............................................................................................................................38
3.4 Other designs.................................................................................................................................38
3.5 [0] Reasoning................................................................................................................................39
3.6 Application examples....................................................................................................................39
3.7 [R] Exercises.................................................................................................................................41
For example, Figure 1 below illustrates the tree and list abstractions of this document. One or the other
abstraction applies depending on how one views the document. When one skims through the document,
it appears as a list of section headers, one after another in sequence. However, when one reads the
document thoroughly, it appears as a hierarchical structure of headers: the level-1 section headers, the
level-2 sub-section headers appearing underneath each level-1 header, the level-3 sub-sub-section
headers appearing underneath each level-2 header, and so on. This hierarchical structure is called a
tree. At the top level of the tree in the figure (a.k.a level 0) is a node labelled d, which represents the
document. This is the root node of the tree and is also the only node at this level. This node is linked to
four level-1 nodes that are labelled 1, 2, 3, and 4. These represent the four level-1 section headers of the
document. Each level-1 node is in turn linked to one or more level-2 nodes and so on.
[1,2,3,4]
List
d level-0: root
1 2 3 4 level-1
Tree
Figure 1: An illustration of the list and tree abstractions of this document
Symbols Meaning
! The associated section is required but subject to self study
0 The associated section is optional
R Reproduced from the reference material
Table 1: Legend
2 Tree
References
• Chapter 5 [1]
Trees are sets of points, called nodes, and lines, called edges. An edge connects two distinct nodes. To
be a tree, a collection of nodes and edges must satisfy certain properties; Figure 2 is an example of a
tree.
1. In a tree, one node is distinguished and called the root. The root of a tree is generally drawn at
the top. In Figure 2, the root is n1
2. Every node c other than the root is connected by an edge to some one other node p called the
parent of c. We also call c a child of p. We draw the parent of a node above that node. For
example, in Figure 2, n1 is the parent of n2, n3, and n4, while n2 is the parent of n5 and n6. Said
another way,n2 , n3, and n4 are children of n1, while n5 and n6 are children of n2.
3. A tree is connected in the sense that if we start at any node n other than the root, move to the
parent of n, to the parent of the parent of n, and so on, we eventually reach the root of the tree.
For instance, starting at n7, we move to its parent, n4, and from there to n4’s parent, which is the
root, n1.
Basis
A single node n is a tree. We say that n is the root of this one-node tree.
Induction
Let r be a new node and let T 1, T2,...,Tk be one or more trees with roots c 1,c2,...,ck, respectively. We
require that no node appear more than once in the T i’s; and of course r, being a “new” node, cannot
appear in any of these trees. We form a new tree T from r and T1, T2,...,Tk as follows:
a) Make r the root of tree T.
b) Add an edge from r to each of c 1,c2,...,ck, , thereby making each of these nodes a child of the root
r. Another way to view this step is that we have made r the parent of each of the roots of the
trees T1, T2,...,Tk.
Example
We can use this recursive definition to construct the tree in Figure 2. This construction also verifies that
the structure in Figure 2 is a tree. The nodes n5 and n6 are each trees themselves by the basis rule, which
says that a single node can be considered a tree. Then we can apply the inductive rule to create a new
tree with n2 as the root r, and the tree T 1, consisting of n5 alone, and the tree T 2, consisting of n6 alone,
as children of this new root. The nodes c1 and c2 are n5 and n6, respectively, since these are the roots of
the trees T1 and T2 . As a result, we can conclude that the structure
Node n3 by itself is a tree. Finally, if we take the node n 1 as r, and n2, n3, and n4 as the roots of the three
trees just mentioned, we create the structure in Figure 2, verifying that it indeed is a tree.
The root is an ancestor of every node in a tree and every node is a descendant of the root .
More formally, suppose m1, m2,..., mk is a sequence of nodes in a tree such that m 1 is the parent of m2,
which is the parent of m3 , and so on, down to mk−1, which is the parent of mk. Then m1, m2 ,..., mk is
called a path from m1 to mk in the tree. The length of the path is k−1, one less than the number of nodes
on the path. Note that a path may consist of a single node (if k = 1), in which case the length of the path
is 0.
If the path is of length 1 or more, then m1 is called a proper ancestor of mk and mk a proper
descendant of m1 .
Subtree
In a tree T, a node n, together with all of its proper descendants, if any, is called a subtree of T. Node n
is the root of this subtree.
Notice that a subtree satisfies the three conditions for being a tree: it has a root, all other nodes in the
subtree have a unique parent in the subtree, and by following parents from any node in the subtree, we
eventually reach the root of the subtree.
Example
Referring again to Figure 2, node n3 by itself is a subtree, since n3 has no descendants other than itself.
As another example, nodes n2, n5, and n6 form a subtree, with root n2, since these nodes are exactly the
descendants of n2. However, the two nodes n2 and n6 by themselves do not form a subtree without node
n5. Finally, the entire tree of Figure 2 is a subtree of itself, with root n1.
Example
In Figure 2, the leaves are n5, n6, n3, and n7. The nodes n1, n2, and n4 are interior.
Example
In Figure 2, node n1 has height 2, n2 has height 1, and leaf n3 has height 0. In fact, any leaf has height 0.
The tree in Figure 2 has height 2. The depth of n1 is 0, the depth of n2 is 1, and the depth of n5 is 2.
Example
In Figure 2, the nodes of the subtree rooted at n2 — that is, n2, n5, and n6 — are all to the left of the
nodes of the subtrees rooted at n3 and n4. Thus, n2, n5, and n6 are all to the left of n3, n4, and n7.
If the name of a node is not important, we can represent a node by its label. However, the label does not
always provide a unique name for a node, since several nodes may have the same label. Thus, many
times we shall draw a node with both its label and its name. The following paragraphs illustrate the
concept of a labeled tree and offer some samples.
The general idea is that each time we form a larger expression by applying an operator to smaller
expressions, we create a new node, labelled by that operator. The new node becomes the root of the
tree for the large expression, and its children are the roots of the trees for the smaller expressions.
We observe from the informal definition that a tree is basically a tuple <r,nodes,edges>, where r is
the root, nodes is the set of nodes and edges is the set of edges (r Î nodes). We will use this tuple form
in the formal definition of trees.
Another observation that we make, which is an important one, is that in general a larger tree is
constructed from two more more smaller ones by adding suitable edges that connect them. The set of
edges introduced in the informal definition represents one method of adding such edges. We name this
the bottom-up method because the newly constructed tree extends upward from the component trees.
There is another method. This method adds edges that connect an existing node of a tree to the root
nodes of the other trees. This is demonstrated in below.
The special (or basis) case of the second method above is to treat all the other trees as single nodes and
to add edges that connect a node of the first tree to each such single node. This is demonstrated in
below. We call this method of constructing trees top-down method because the newly constructed tree
extends downward from a given tree (more precisely, from the root of this tree).
2.1.4.2 Preliminaries
Given a tree T = <r,nodes,edges>, followings are the some terms that we will use to define trees:
• T.root = r, T.nodes = nodes, T.edges = edges.
• edge(p,n) is an edge that connects the parent node p to the child node n.
Basis
For any node r, T = <r,{r},{}> is a tree.
Induction
For all natural number k >= 1 and k trees T1, T2,...,Tk, and for all node r:
r is not in T1,...,Tk → T = <r, T1.nodes+...+Tk.nodes+{r},{edge(r,T1.root),...,edge(r,Tk.root)}
+T1.edges+...+Tk.edges> is a tree.
Induction
For all node n and tree T', and for some node p in T':
n is not in T' → T = <T'.root, T'.nodes+{n}, T'.edges+{edge(p,n)}> is a tree.
PT: there exists exactly one path that connects any two nodes of the tree.
which means:
• there is a path that connects any two nodes, and
• there is exactly one such path for any two nodes
The first statement means the graph of the tree is connected, while the second means that this graph is
acyclic (i.e. has no cycles).
A rooted tree, in addition, must have at least one node, which is the root.
2.1.6 Operations
2.1.6.1 Overview
Since tree is recursive in nature, the operations that may be performed on tree are inherently recursive.
The design logic of such operations is consisted of two parts. The first part is a recursion and the
second part is the actual computation that is performed against the recursion.
The first part is typically applied at a specific node of the tree. This node is the root node if the
computation is to be performed over the whole tree. Otherwise, it is an internal or leaf node. The basis
case of the recursion is applied at the leaf nodes while the induction is applied at the internal ones.
The second part depends on the applications. This is a list of some computations, adapted from [1], that
are common to all trees:
• height: return the height of the tree or a subtree
• countif: count the number of nodes of the tree or a subtree that satisfy a condition
• count: (a special form of countif) count the number of nodes of the tree or a subtree
• eval: evaluate the expression that is represented by the tree
• toString: turn a tree or a subtree into a string
We will describe below the recursive definitions of operations count and toString. Details of the
other operations are given in [1].
toString
Suppose NIL is the empty string, NL is the next line character, and “|” is the vertical bar character. We
have:
nodeLine(n ,l ) if n is leaf
toString (n ,l ) = nodeLine(n ,l )+ toString(c 1 ,l +1)+…+ toString(c k , l +1)
if n is not leaf, child(c i ,n)∀ i =1…k
2.2.1 Overview
As discussed in [1], trees can be represented by an array- or list-based data structure (list is discussed in
Section 3). Here, however, we will discuss an alternative design for trees using recursive class. This
design, though not necessarily the most efficient, is arguably more natural than array and list as it
conforms to the recursive definitions in Section 2.1.4.
In certain applications where edges are used outside the class or where they carry application-specific
information (e.g. label, weight) then we need to create a separate class to represent them.
2.2.2.1 Operations
Constructors
The Tree class in Listing 1 has two constructors: one for the basis rule and the other is for the inductive
rule of the recursive definition. It is easy to see that the one-argument constructor matches the basis
rule, while the other constructor matches the inductive one.
count
This operation is a simple form of the countif operation mentioned in Section 2.1.6. Although the
parameter list does not state the condition against which the operation is performed, it is clear that the
tree-node condition is implied. The computation performed is to count the number of tree nodes visited.
toString
Conceptually, this operation recursively traverses the tree starting at the input node and constructs a
string consisting of all the nodes that are visited.
Listing 1 shows that this operation is overloaded. The public operation is the default operation, and is
one that is invoked by the using code. The private one, however, is a recursive, helper operation that
is invoked by the public operation to carry out the task
2.2.2.2 Specification
Tree
- root: Object
- nodes: Vector
- edges: Vector
+ Tree(Object)
+ Tree(Object, Tree...)
+ count(Object)
+ toString()
- toString(StringBuffer, Object, int)
+ repOK()
package t9.tree.bottomup;
import java.util.Vector;
import userlib.DomainConstraint;
/**
* @overview A tree is a set of nodes that are connected to to each other by
* edges such that one node, called the root, is connected to some nodes,
* each of these nodes is connected to some other nodes that have not been
* connected, and so on.
*
* <p>The following is a bottom-up recursive tree design that
* incrementally builds a new tree by adding roots.
*
* @attributes
* root Object
* nodes Set<Object> Vector<Object>
* edges Set<(Object,Object)> Vector<[Object,Object]>
*
* @object A typical tree T is the tuple <r,N,E>, where
* root(r), nodes(N), and edges(E).
*
* <p>Trees are defined recursively as follows:
* Basis
* For any node r, T = <r,{r},{}> is a tree.
* Induction
* For all natural number k >= 1 and k trees T1, T2,...,Tk
* and for all node r:
* r is not in T1,...,Tk ->
* T = <r,
* T1.nodes+...+Tk.nodes+{r},
* {edge(r,T1.root),...,edge(r,Tk.root)} +T1.edges+...+Tk.edges> is a tree
*
// constructors
/**
* @requires r != null
* @effects initialise this as <r,{r},{}>
*/
public Tree(Object r)
/**
* @requires r != null /\ trees.length >= 1 /\
* for all t in trees. r not in t.nodes
*
* @effects initialise this as a tree T =
* <r,
* T1.nodes+...+Tk.nodes+{r},
* {edge(r,T1.root),...,edge(r,Tk.root)} +T1.edges+...+Tk.edges>,
* where Tis are in trees
*/
public Tree(Object r, Tree...trees)
/**
* A recursive procedure to count the number of nodes in a subtree
* rooted at n.
*
* @effects
* if n is a leaf
* return 1
* else
* return the number of nodes in the sub-tree rooted at n
*/
public int count(Object n)
@Override
public String toString()
/**
* @effects
* if this satisfies abstract properties
* return true
* else
* return false
*/
public boolean repOK()
} // end Tree
As explained earlier, the general form of the inductive rule of the top-down definition appends one or
more trees to an existing node of an existing tree. We could translate this general form directly into the
design, resulting in a constructor similar to the second constructor of the bottom-up design (see Listing
2). A draw-back of this design, however, is that tree objects need to be created before hand and passed
in as argument. This is rather cumbersome and not really needed. We will use an alternative design
which follows the special case discussed in Section 2.1.4.1. This design is simpler and overcomes the
draw-back of the general design.
Listing 2: Design specification of a constructor that implements the general form of the inductive rule
of the top-down design
The above design is replaced by a simpler design shown in Listing 3 below. This design uses a public
operation, addNode, to add one new node to the tree. Compared to the general design in Listing 2, this
design basically treats the current tree object as the first parameter, parameter parent as the second
parameter, and a single node, n, instead of the array Tree[] as the third parameter.
/**
* @requires n != null /\ parent != null
* @effects
* if parent is in nodes
* add n as a child of parent, i.e. edge(parent,n)
* return true
* else
* return false
*/
public boolean addNode(Object parent, Object n)
2.2.3.2 Specification
Tree
- root: Object
- nodes: Vector
- edges: Vector
+ Tree(Object)
+ addNode(Object, Object)
+ count(Object)
+ toString()
- toString(StringBuffer, Object, int)
+ repOK()
/**
* @overview A tree is a set of nodes that are connected to to each other by
* edges such that one node, called the root, is connected to some nodes,
* each of these nodes is connected to some other nodes that have not been
// constructors
/**
* @requires r != null
* @effects initialise this as <r,{r},{}>
*/
public Tree(Object r)
/**
* @requires n != null /\ parent != null
* @effects
* if parent is in nodes
* add n as a child of parent, i.e. edge(parent,n)
* return true
/**
* A recursive procedure to count the number of nodes in a subtree
* rooted at n.
*
* @effects
* if n is a leaf
* return 1
* else
* return the number of nodes in the sub-tree rooted at n
*/
public int count(Object n)
@Override
public String toString()
/**
* @requires n != null
* return the string representation of the sub-tree rooted at n
*/
public String toString(Object n)
/**
* @effects
* update sb with the string representation of the sub-tree
* rooted at n.
*/
private void toString(StringBuffer sb, int level, Object n)
/**
* @effects
* if this satisfies abstract properties
* return true
* else
* return false
*/
public boolean repOK()
} // end Tree
2.3 Implementation
Refer to the attached Java source code for details.
Exercise
1. Implement another version of the private operation toString which produces a horizontal
representation of the tree. The following is an example of the tree shown in Listing 6:
Listing 5: A code example that shows how to create a Tree object of the bottom-up Tree class
Listing 6: The console output of the code example in Listing 5. The tree is printed as a hierarchical
structure.
t.addNode(2,3);
t.addNode(2,6);
t.addNode(1,10);
t.addNode(10,11);
t.addNode(10,12);
t.addNode(3,4);
t.addNode(3,5);
t.addNode(6,7);
t.addNode(7,8);
t.addNode(7,9);
}
}
Listing 7: A code example that shows how to create a Tree object of the top-down Tree class
References
• [1]
5.2.1: In Figure 4 we see a tree. Tell what is described by each of the following phrases:
a) root of the tree
b) leaves of the tree
c) interior nodes of the tree
d) siblings of node 6
e) subtree with root 5
f) ancestors of node 10
g) descendants of node 10
h) [0] nodes to the left of node 10
i) [0] nodes to the right of node 10
j) longest path in the tree
k) height of node 3
l) depth of node 13
m) height of the tree
5.2.2: Can a leaf in a tree ever have any (a) descendants? (b) proper descendants?
[0] 5.2.4*: Prove that the two definitions of trees in this section are equivalent.
[0] 5.2.5: Suppose we have a graph consisting of four nodes, r, a, b, and c. Node r is an isolated node
and has no edges connecting it. The remaining three nodes form a cycle; that is, we have an edge
connecting a and b, an edge connecting b and c, and an edge connecting c and a. Why is this graph not
a tree?
[0] 5.2.8: Show that if x and y are two distinct nodes in an ordered tree, then exactly one of the
following conditions must hold:
a) x is a proper ancestor of y
b) x is a proper descendant of y
c) x is to the left of y
d) x is to the right of y
5.3.6: In a tree, a node c is the lowest common ancestor of nodes x and y if c is an ancestor of both x
and y, and no proper descendant of c is an ancestor of x and y. Write a program that will find the lowest
common ancestor of any pair of nodes in a given tree. What is a good design specification for trees in
such a program?
[0] 5.4.1 Write a recursive program to count the number of nodes in a tree that is represented by
leftmost-child and right-sibling pointers.
5.4.2: Write a recursive program to find the maximum label of the nodes of a tree. Assume that a
node's label is the string representation of its state.
[!] 5.4.4*: Write a recursive program that computes for a tree, represented by leftmost- child and right-
sibling pointers, the number of left-right pairs, that is, pairs of nodes n and m such that n is to the left of
node m. For example, in Figure 5, node 5 is to the left of the nodes labeled ∗, −, 10, 3, and 2; node 10 is
to the left of nodes 3 and 2; and node − is to the left of node 2. Thus, the answer for this tree is eight
pairs.
Hint : Let your recursive function return two pieces of information when called on a node n: the
number of left-right pairs in the subtree rooted at n, and also the number of nodes in the subtree rooted
at n.
[0] 5.4.5: List the nodes of the tree in Figure 4 in (a) preorder and (b) postorder.
[0] 5.4.8: Write a function that “circumnavigates” a tree, printing the name of a node
each time it is passed.
[0] 5.4.9: What are the actions A0 , A1 , and so forth, for the implementation of the postorder function.
References
• Chapter 6 [1]
If the elements are all of type T, then we say that the type of the list is “list of T .” Thus we can have
lists of integers, lists of real numbers, lists of structures, lists of lists of integers, and so on.
We generally expect the elements of a list to be of some one type. However, since a type can be the
union of several types, the restriction to a single “type” can be circumvented. A list is often written
with its elements separated by commas and enclosed in parentheses:
(a1,a2,...,an)
where the ai ’s are the elements of the list.
In some situations we shall not show commas or parentheses explicitly. In particular, we shall study
character strings, which are lists of characters. Character strings are generally written with no comma
or other separating marks and with no surrounding parentheses. Elements of character strings will
normally be written in typewriter font. Thus foo is the list of three characters of which the first is f and
the second and third are o.
As another example, a document can be viewed as a list. Here the elements of the list are the lines of
text. Thus a document is a list whose elements that are themselves lists, character strings in particular.
Example 6.3. A point in n-dimensional space can be represented by a list of n real numbers. For
example, the vertices of the unit cube can be represented by the triples shown in Fig. 6.1. The three
elements on each list represent the coordinates of a point that is one of the eight corners (or “vertices”)
of the cube. The first component represents the x-coordinate (horizontal), the second represents the y-
coordinate (into the page), and the third represents the z-coordinate (vertical).
Example 6.4. The length of list (1) in Example 6.1 is 8, and the length of list (2) is 6. The length of list
(3) is 12, since there is one position for each month. The fact that there are only three different numbers
on the list is irrelevant as far as the length of the list is concerned.
A subsequence of the list L = (a1, a2, . . . ,an) is a list formed by striking out zero or more elements of L.
The remaining elements, which form the subsequence, must appear in the same order in which they
appear in L, but the elements of the subsequence need not be consecutive in L. Note that e and the list
L itself are always subsequences, as well as sublists, of L.
Example 6.5. Let L be the character string abc. The sublists of L are
e, a, b, c, ab, bc, abc
These are all subsequences of L as well, and in addition, ac is a subsequence, but not a sublist.
For another example, let L be the character string abab. Then the sublists are
e, a, b, ab, ba, aba, bab, abab
These are also subsequences of L, and in addition, L has the subsequences aa, bb, aab, and abb. Notice
that a character string like bba is not a subsequence of L. Even though L has two b’s and an a, they do
not appear in such an order in L that we can form bba by striking out elements of L. That is, there is no
a after the second b in L.
Example 6.6. The prefixes of the list abc are e, a, ab, and abc. Its suffixes are e, c, bc, and abc.
For example, the list of prime numbers less than 20 is shown in below.
Basis
() is a list.
Induction
"x "list L. (x, L.elements) is a list.
(L.elements is the sequence of elements of L)
Exercise
1. Prove that the above recursive definition satisfies the definition of list in Section 3.1.1.
3.1.6 Operations
The followings are definitions of the operations on list adapted from [1].
insert
To insert an element x into a list (a1,...,an). The element can appear anywhere in the list and duplicates
are allowed. A special case is to push x into the first position of the list to become the new head, hence
creating a new list (x,a1,...,an).
remove
To remove an occurrence of an element x from a list. The behaviour needs to be defined for the case in
which the element is found in more than one positions (e.g. delete the first occurrence). If x is not in the
list, the method should have no effect. A special case is to delete the head of the list. This operation is
called pop.
lookup
To return true or false depending on whether or not an element x is in the list.
add
To add an element to the end of the list.
concatenate
We concatenate two lists L and M by forming the list that begins with the elements of L and then
continues with the elements of M . That is, if L = (a 1,a2,...,an) and M = (b1,b2,...,bk), then LM, the
concatenation of L and M, is the list
(a1,a2,...,an,b1,b2,...,bk)
Note that the empty list is the identity for concatenation. That is, for any list L, we have eL = Le = L.
head
To return the head of the list.
tail
To return the remainder of the list.
retrieve
To return an element at a given position of the list.
length
To return the length of the list.
3.2.1 Overview
The most important design decision that we have to make for list is which concrete type to use to
represent its elements. There are a number of alternatives. The two common ones are linked list and
array [1]. Another concrete type that would be used is tree (based on the representation in Section
3.1.3).
The argument that we can use linked list to represent a list seems circular because linked list sounds
like another list that we have to design. However, this is not the case as linked list is a data structure
that is formed by linking the objects representing the elements of a list. This will be explained shortly
in Section 3.2.3.
Overall, the designs fall into one of two types: recursive and non-recursive. A design based on linked
list or tree is a recursive design as it conforms to the recursive definition given in Section 3.1.4. A
design based on array, however, is non-recursive as it does not follow this recursive definition.
In this note, we will focus on two recursive designs: one is recursive list, which is a pure recursive
design based directly on the definition, and the other is linked list. We also briefly discuss an object-
oriented version of the array-based design in [1].
It may appear at first that attribute tail does not exactly match the inductive rule specification in
Section 3.1.4, which requires that only the elements of the existing list (not the list itself) are used to
construct the new list. However, a closer look reveals that it logically is the same. The fact that the
elements of a list becomes part of another list means that the list itself also belongs to that list. Further
in object-oriented design, the fact that tail references a List object is an internal design detail, and is
hidden from code that uses the class List. As far as the using code is concerned, List still represents a
sequence of elements.
Note that since derived attributes are not essential to the abstract concept, they need not be listed in the
ATTRIBUTES section of the header specification. They are only defined in the class body as shown in
Listing 9.
Other operations
length()
This operation returns the value of the derived attribute length, instead of computing it using
recursion.
tail()
This operation does not return tail directly. Instead it makes a copy of the List object that is
referenced by this attribute and returns a reference to it. This helps protect this attribute from
modifications outside of class. However, this also slows down program execution.
clone()
This operation returns a copy of the current list object. It is used by the tail() operation mentioned
above. The copy is another List object that has the same content as the current object.
3.2.2.5 Specification
Below is the complete specification of the List class.
import userlib.DomainConstraint;
/**
* ...omitted (as above)...
*/
public class List {
@DomainConstraint(type="Object",mutable=false,optional=true)
private Object head;
@DomainConstraint(type="List",mutable=true,optional=true)
private List tail;
// derived attribute
@DomainConstraint(type="Integer",mutable=false,optional=true)
private int length;
/**
* This constructor implements the basis rule.
*
* @effects initialises this to be ()
*/
public List()
/**
* This constructor implements the inductive rule.
*
* @requires x != null
/**
* This constructor implements the inductive rule.
*
* @requires h != null
* @effects initialise this to be (h,x1,...,xn) where t = (x1,...,xn)
*/
private List(Object h, List t)
/**
* @requires x != null
* @modifies this
* @effects
* if this is empty
* head = x
* else
* inserts x into the first position of this (pushing
* the element at the current position to the right)
*/
public void push(Object x)
/**
* @requires x != null
* @effects
* if this is empty or x is not in this
* do nothing
* else
* remove the first occurrence of x from this
*/
public void remove(Object x)
/**
* @requires x != null
* @effects
* if x is in this
* return true
* else
* return false
*/
public boolean lookUp(Object x)
/**
* @effects
* return head
*/
public Object head()
/**
* @effects
* if this is empty or a single-element list
* return null
* else
/**
* @requires index >= 0
* @effects
* if this is empty or index is out of range
* return null
* else
* return the element at the position index
*/
public Object retrieve(int index)
/**
* @effects
* return the number of occurrences of the elements of this
*/
public int length()
/**
* @effects
* if this is empty
* return true
* else
* return false
*/
public boolean isEmpty()
@Override
public String toString()
/**
* @effects
* return a copy of this
*/
@Override
public List clone()
/**
* @effects
* if this satisfies abstract properties
* return true
* else
* return false
*/
public boolean repOK()
}
To accommodate this requirement, we create a new class named Cell and make it the inner class of the
list class, which is now called LinkedList. Since class Cell now has head and tail, it becomes the
recursive class. Class LinkedList is no longer recursive. It simply contains a reference to first Cell
object of the list.
/**
* @overview Represents a (possibly empty) list of elements of arbitrary types.
*
* @attributes
* cell Cell
*
* @object
* A typical list is c = (x1,...,xn), where cell.head(x1) and
* cell.tail(x2,...,xn) are elements.
*
* <p>Recursive definition:
* Basis
* () is a list
* Induction
* for all object x and for all list L, a new list is formed by inserting x
* into the first position of L to become a head, thereby making the
// derived attribute
@DomainConstraint(type="Integer",mutable=false,optional=true)
private int length;
// constructors
/**
* This constructor implements the basis rule.
*
* @effects initialises this to be ()
*/
public LinkedList()
/**
* @requires n != null
* @effects initialises this to be a list (n.head)
*/
private LinkedList(Cell n)
...
/**
* @overview Represents a linked cell that contains a head value of the
* current list and a nullable reference pointer to another cell
* representing the tail of the current list.
*
* @attributes
* head Object
* tail Cell
*
* @object
* A typical cell is <v,r>, where head(v) and tail(r).
*
* @abstract_properties
* mutable(head)=false /\ optional(head)=false /\
* mutable(tail)=false /\ optional(tail)=true
*
* @author dmle
*/
private class Cell {
private Object head;
private Cell tail;
/**
* @requires v != null
* @effects
* initialise this to be <v,null>
*/
public Cell(Object v)
/**
* @effects
* return a deep copy of this
*/
@Override
public Cell clone()
/**
* @effects
* if this satisfies abstract properties
* return true
* else
* return false
*/
public boolean repOK()
} // end Cell
} // end LinkedList
3.3 Implementation
In this section, we will first give the Java implementations of the two basic class designs: recursive and
compositional. To be consistent with the literature, we will use the name linked list to refer to the these
two implementations. We will leave the implementations of the doubly-linked list and array based list
as exercises.
Exercise
1. Discuss the possibility of converting the implementations of the iterative operations to use
recursion and vice versa.
Exercise
1. Discuss the possibility of converting the implementations of the iterative operations to use
recursion and vice versa.
2. Compare and contrast the implementations of two operations LinkedList.push and
List.push.
Note that a similar program can also created for the class LinkedList.
import t9.list.List;
Object a = 1;
int index = 1;
System.out.printf("lookUp(%s) -> %b%n", a,l.lookUp(a));
System.out.printf("retrieve(%d) -> %s%n", index, l.retrieve(index));
System.out.printf("length() -> %d%n", l.length());
System.out.printf("head() -> %s%n", l.head());
System.out.printf("tail() -> %s%n", l.tail());
System.out.printf("isEmpty() -> %b%n", l.isEmpty());
// insert
System.out.println("push:\n=====================");
for (Object o : objs) {
l.push(o);
System.out.printf("push(%s) -> %s%n", o,l);
}
// retrieve
System.out.println("retrieve:\n=====================");
int length = l.length();
for (int i = 0; i < length; i++) {
Object o = l.retrieve(i);
System.out.printf("retrieve(%d) -> %s%n", i,o);
}
index = 5;
a = l.retrieve(index);
System.out.printf("retrieve(%d) -> %s%n", index,a);
// remove
System.out.println("remove:\n=====================");
for (Object o : objs) {
l.remove(o);
System.out.printf("remove(%s) -> %s%n", o,l);
}
System.out.printf("after: %s%n", l);
initial: ()
lookUp(1) -> false
retrieve(1) -> null
length() -> 0
head() -> null
tail() -> null
isEmpty() -> true
push:
=====================
push(hello world!) -> (hello world!)
push(2) -> (2,hello world!)
References
• [1]
6.2.1: Answer the following questions about the list (2, 7, 1, 8, 2).
a) What is the length?
b) [0] What are all the prefixes?
c) [0] What are all the suffixes?
6.2.3**: In a list of length n ≥ 0, what are the largest and smallest possible
numbers of (a) prefixes (b) sublists (c) subsequences?
6.2.4: If the tail of the tail of the list L is the empty list, what is the length of L?
6.2.5*: Bea Fuddled wrote a list whose elements are themselves lists of integers, but omitted the
parentheses: 1,2,3. There are many lists of lists that could have been represented, such as (1), (2, 3) .
What are all the possible lists that do not have the empty list as an element?
[0] 6.3.3**: Let x be an element and L a list. Under what conditions are the following
equations true?
a) delete(x, insert(x, L)) = L
b) insert(x, delete(x, L)) = L
c) first(L) = retrieve(1, L)
d) last(L) = retrieve(length(L), L )