Sie sind auf Seite 1von 43

Chapter 6

Abstract data types

Object-oriented design and implementation


Table of contents

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

PR2 Page 1 Duc M. L. 2019


References......................................................................................................................................41
4 References.............................................................................................................................................43

PR2 Page 2 Duc M. L. 2019


1 Introduction
In this topic, we will discuss the design, implementation and reasoning of two fundamental data types:
tree and list. These are important data abstractions that are frequently used in practice.

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

1.1 2.1 … 3.1 … level-2

Tree
Figure 1: An illustration of the list and tree abstractions of this document

PR2 Page 3 Duc M. L. 2019


1.1 Notation
Table 1 below explains the meaning of some special symbols that are used in the section headers.

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]

2.1 Abstract concept

2.1.1 [R] Definition


The definition in this section is taken from [1] and is concerned with rooted trees.

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.

PR2 Page 4 Duc M. L. 2019


Figure 2: A tree with seven nodes
2.1.2 [R] Informal recursive definition
The definition given in this section is taken from [1]. It is based on the observation that a larger tree can
recursively be constructed out of smaller ones.

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

PR2 Page 5 Duc M. L. 2019


is a tree; its root is n2.
Similarly, n7 alone is a tree by the basis, and by the inductive rule, the structure

is a tree; its root is n4.

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.

2.1.3 Tree-related terms


Sibling
Nodes that have the same parent are sometimes called siblings. For example, in Figure 2, nodes n2, n3,
and n4 are siblings, and n5 and n6 are siblings.

Path, ancestor, descendant


The parent-child relationship can be extended naturally to ancestors and descendants. Informally, the
ancestors of a node are found by following the unique path from the node to its parent, to its parent’s
parent, and so on. Strictly speaking, a node is also its own ancestor. The descendant relationship is the
inverse of the ancestor relationship, just as the parent and child relationships are inverses of each other.
That is, node d is a descendant of node a if and only if a is an ancestor of d.

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 .

PR2 Page 6 Duc M. L. 2019


For example, In Figure 2, n1, n2, n6 is a path of length 2 from the root n 1 to the node n6; n1 is a path of
length zero from n1 to itself.

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.

Leaf and interior nodes


A leaf is a node of a tree that has no children. An interior node is a node that has one or more children.
Thus, every node of a tree is either a leaf or an interior node, but not both. The root of a tree is normally
an interior node, but if the tree consists of only one node, then that node is both the root and a leaf.

Example
In Figure 2, the leaves are n5, n6, n3, and n7. The nodes n1, n2, and n4 are interior.

Height and Depth


In a tree, the height of a node n is the length of a longest path from n to a leaf. The height of the tree is
the height of the root. The depth, or level, of a node n is the length of the path from the root to n.

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.

[0] Ordered tree


Optionally, we can assign a left-to-right order to the children of any node. For example, the order of the
children of n1 in Figure 2 is n2 leftmost, then n3 , then n4. This left-to-right ordering can be extended to
order all the nodes in a tree. If m and n are siblings and m is to the left of n, then all of m’s descendants
are to the left of all of n’s descendants.

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.

PR2 Page 7 Duc M. L. 2019


[0] Labelled tree
A labeled tree is a tree in which a label or value is associated with each node of the tree. We can think
of the label as the information associated with a given node. The label can be something as simple,
such as a single integer, or complex, such as the text of an entire document. We can change the label of
a node, but we cannot change the name of a node.

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.

Example: Expression tree


Arithmetic expressions are representable by labeled trees, and it is often quite helpful to visualize
expressions as trees. In fact, expression trees, as they are sometimes called, specify the association of
an expression’s operands and its operators in a uniform way, regardless of whether the association is
required by the placement of parentheses in the expression or by the precedence and associativity rules
for the operators involved.

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.

Figure 3: Some examples of expression tree. Source [1]


[0] Branching factor
The branching factor of a node is the maximum number of children it can have.

PR2 Page 8 Duc M. L. 2019


2.1.4 Formal definition
2.1.4.1 Overview
In this section, we will formalise two definitions of trees based on the informal definition in Section
2.1.2. We will use these definitions as the basis for our design of trees later.

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.

2.1.4.3 Bottom-up recursion


The following is the formal definition of the informal definition in Section 2.1.2 using the tuple-based
representation of trees. Note that + is the set union operator.

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.

2.1.4.4 Top-down recursion


The following is the formal definition of a tree based on the top-down construction method.

PR2 Page 9 Duc M. L. 2019


Basis
For any node r, T = <r,{r},{}> 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.

2.1.5 Abstract properties


A general tree is an acyclic, connected graph. This is described by the following property:

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].

PR2 Page 10 Duc M. L. 2019


count
Suppose child(c,n) = true if c is a child of n, false if otherwise. We have:
1 if n is leaf
count(n) =
1+ ∑ count(c i ) if n is not leaf
child (c i ,n)

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

nodeLine(n , l) = bol (l ) + spaceMarkers (l ) + n. toString

bol(l ) = NIL if l = 0 spaceMarkers (l ) = NIL if l = 0


NL+ "|" if l > 0 "-⋯-" if l > 0

2.2 Design specification

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.

The following paragraphs describe a number of design issues concerning tree.

Where to place the recursive definition?


As shown in the figure, the recursive definition is placed in the OBJECT section of the class header
specification. The reason is because the definition describes how objects are constructed.

Design-oriented abstract property


The fundamental tree property PT mentioned in Section 2.1.5 may be useful for comparing trees with
graphs in general, but it is not easy to check programmatically. We observe that with rooted trees, in
particular, the combination of the following two properties is equivalent to PT, yet easier to verify:
• every non-root node has exactly one parent
• tree is connected
The first property can be verified using the nodes and edges sets. The second property can be verified
using the operation count. If the result of this count is smaller than the cardinality of the nodes set then
we know that the tree is not connected.

PR2 Page 11 Duc M. L. 2019


How to represent a node?
We represent nodes using the class Object. In this representation, a node is treated in its most basic
form, which is a value label with no extra information. In certain applications where nodes carry
application-specific information then we need to create a separate class to capture these information.

How to represent an edge?


We represent edges by the array Object[]. In this representation, edges are treated in their most basic
form: a binary tuple of nodes with no extra information. Another reason why this is a suitable
representation is because edges are not used by code outside the class and, thus, there is no need to
invent a separate data type for them.

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.

How to represent sets of nodes and edges?


We adapt the design decision made in an earlier chapter for the integer set concept (IntSet) to represent
the set of nodes and the set of edges using the Vector class of Java. This is more productive and
efficient than array since it allows us to dynamically update the tree as nodes and edges are added to
(and also potentially removed from) it.

2.2.2 Recursive bottom-up design


Listing 1 below is the design specification of the Tree class that follows the recursive, bottom-up
method.

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

PR2 Page 12 Duc M. L. 2019


repOK
As usual, this operation validates a Tree object against the rep invariant. It is used to test the
implementation of the Tree class.

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
*

PR2 Page 13 Duc M. L. 2019


* @abstract_properties <pre>
* mutable(root)=false /\ optional(root)=false /\
* mutable(nodes)=true /\ optional(nodes)=false /\
* mutable(edges)=true /\ optional(edges)=true /\
* root in nodes /\
* for all n in nodes.
* (exists p in nodes. parent(n)=p /\
* for all p, q in nodes. (parent(n)=p /\ parent(n)=q -> p=q)) /\
* for all n in nodes.
* (n != root -> exists a sequence of edges from root to n)
* </pre>
*
* @author dmle
*/
public class Tree {
@DomainConstraint(type="Object",mutable=false,optional=false)
private Object root;
@DomainConstraint(type="Vector",mutable=true,optional=false)
private Vector nodes;
@DomainConstraint(type="Vector",mutable=true,optional=true)
private Vector edges;

// 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()

PR2 Page 14 Duc M. L. 2019


/**
* @effects
* update sb with a 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

Listing 1: The bottom-up, recursive design specification of the Tree class

2.2.3 Recursive top-down design


2.2.3.1 Overview
It is easy to see that the top-down tree design is similar to the bottom-up one except for the operation
that implements the inductive rule of the recursive definition. This is where the two definitions differ.

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.

Alternative design of the inductive rule


Listing 2 below is the design specification of the constructor, similar to the one used in the bottom up
design, that implements the general form of the inductive rule.

PR2 Page 15 Duc M. L. 2019


/**
* @requires t != null /\ n is in t.nodes /\ trees.length >= 1 /\
* for all t' in trees. (t.nodes – t'.nodes = t.nodes)
*
* @effects initialise this as a tree T =
* <t.root,
* t.nodes+t1.nodes+...+tk.nodes,
* {edge(n,t1.root),...,edge(n,tk.root)}+t1.edges+...+tk.edges>,
* where tis are in trees
*/
public Tree(Tree t, Object n, Tree...trees)

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)

Listing 3: An alternative design to the one in Listing 2 using a public operation

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

PR2 Page 16 Duc M. L. 2019


* connected, and so on.
*
* <p>The following is a top-down recursive design that incrementally
* build a tree by adding leaf nodes.
*
* @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 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
*
* @abstract_properties <pre>
* mutable(root)=false /\ optional(root)=false /\
* mutable(nodes)=true /\ optional(nodes)=false /\
* mutable(edges)=true /\ optional(edges)=true /\
* root in nodes /\
* for all n in nodes.
* (exists p in nodes. parent(n)=p /\
* for all p, q in nodes. (parent(n)=p /\ parent(n)=q -> p=q)) /\
* for all n in nodes.d
* (n != root -> exists a sequence of edges from root to n)
* </pre>
* @author dmle
*/
public class Tree {
@DomainConstraint(type="Object",mutable=false,optional=false)
private Object root;
@DomainConstraint(type="Vector",mutable=true,optional=false)
private Vector nodes;
@DomainConstraint(type="Vector",mutable=true,optional=true)
private Vector edges;

// 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

PR2 Page 17 Duc M. L. 2019


* else
* return false
*/
public boolean addNode(Object parent, Object n)

/**
* 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

Listing 4: The recursive, top-down design specification of the class 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:

PR2 Page 18 Duc M. L. 2019


1(2(3(4 5) 6(7(8 9))) 10(11 12))

2.4 [0] Reasoning

2.4.1 Structural induction


Refer to [1] for details.

2.5 Application examples

2.5.1 A bottom-up tree


public class BottomUpTreeTest {
public static void main(String[] args) {

Tree t4 = new Tree(4);


Tree t5 = new Tree(5);
Tree t8 = new Tree(8);
Tree t9 = new Tree(9);
Tree t7 = new Tree(7,t8,t9);

Tree t11 = new Tree(11);


Tree t12 = new Tree(12);
Tree t10 = new Tree(10,t11,t12);

Tree t3 = new Tree(3,t4,t5);


Tree t6 = new Tree(6,t7);
Tree t2 = new Tree(2, t3, t6);
Tree t1 = new Tree(1, t2,t10);

boolean repOk = t1.repOK();


if (repOk)
System.out.println(t1);

System.out.println("valid: " + repOk);


}
}

Listing 5: A code example that shows how to create a Tree object of the bottom-up Tree class

PR2 Page 19 Duc M. L. 2019


1
|-2
|--3
|---4
|---5
|--6
|---7
|----8
|----9
|-10
|--11
|--12
valid: true

Listing 6: The console output of the code example in Listing 5. The tree is printed as a hierarchical
structure.

2.5.2 A top-down tree


public class TopDownTreeTest {
public static void main(String[] args) {

Tree t = new Tree(1);

boolean aok = false;


aok = t.addNode(1,2);
//System.out.printf("addNode(1,2): %b%n",aok);

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);

boolean repOk = t.repOK();


if (repOk)
System.out.println(t);

System.out.println("valid: " + repOk);

}
}

Listing 7: A code example that shows how to create a Tree object of the top-down Tree class

PR2 Page 20 Duc M. L. 2019


2.6 [R] Exercises

References
• [1]

Figure 4: Tree of Exercise 5.2.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?

5.2.3: Prove that in a tree no leaf can be an ancestor of another leaf.

[0] 5.2.4*: Prove that the two definitions of trees in this section are equivalent.

PR2 Page 21 Duc M. L. 2019


Hint: To show that a tree according the nonrecursive definition is a tree according the recursive
definition, use induction on the number of nodes in the tree. In the opposite direction, use induction on
the number of rounds used in the recursive definition.

[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.

PR2 Page 22 Duc M. L. 2019


Figure 5: An expression tree

[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.

PR2 Page 23 Duc M. L. 2019


3 List
3.1 Abstract concept

References
• Chapter 6 [1]

3.1.1 [R] Definition


A list is a finite sequence of zero or more elements. If the number of elements is zero, then the list is
said to be empty. We use the Greek letter e (epsilon) to represent the empty list. We can also represent
the empty list by a pair of parentheses surrounding nothing: ().

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.

Example 6.1. Here are some examples of lists.


1. The list of prime numbers less than 20, in order of size:
(2, 3, 5, 7, 11, 13, 17, 19)
2. The list of noble gasses, in order of atomic weight:
(helium, neon, argon, krypton, xenon, radon)
3. The list of the numbers of days in the months of a non-leap year:
(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
As this example reminds us, the same element can appear more than once on a list.

PR2 Page 24 Duc M. L. 2019


Example 6.2. A line of text is another example of a list. The individual characters making up the line
are the elements of this list, so the list is a character string. This character string usually includes
several occurrences of the blank character, and normally the last character in a line of text is the
“newline” character.

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).

Figure 6: The vertices of the unit cube represented as triples

3.1.2 List-related terms


3.1.2.1 [R] Length of a List
The length of a list is the number of occurrences of elements on the list. It is important to remember
that length counts positions, not distinct symbols, and so a symbol appearing k times on a list adds k to
the length of the list.

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.

PR2 Page 25 Duc M. L. 2019


3.1.2.2 [R] Parts of a List

Head and tail


If a list is not empty, then it consists of a first element, called the head and the remainder of the list,
called the tail. For instance, the head of list (2) in Example 6.1 is helium, while the tail is the list
consisting of the remaining five elements,
(neon, argon, krypton, xenon, radon)

Sublist and subsequence


If L = (a1, a2,..., an) is a list, then for any i and j such that 1 ≤ i ≤ j ≤ n, (a i, ai+1 , . . . ,aj ) is said to be a
sublist of L. That is, a sublist is formed by starting at some position i, and taking all the elements up to
some position j. We also say that e, the empty list, is a sublist of any list.

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.

Prefix and suffix


A prefix of a list is any sublist that starts at the beginning of the list. A suffix is a sublist that
terminates at the end of the list. As special cases, we regard e as both a prefix and a suffix of any list.

Example 6.6. The prefixes of the list abc are e, a, ab, and abc. Its suffixes are e, c, bc, and abc.

3.1.2.3 [R] Element position


Each element on a list is associated with a position. If (a 1, a2,..., an) is a list and n ≥ 1, then a1 is said to
be first element, a2 the second, and so on, with a n the last element. We also say that element a i occurs at
position i. In addition, ai is said to follow ai−1 and to precede ai+1. A position holding element a is said to
be an occurrence of a.

PR2 Page 26 Duc M. L. 2019


The number of positions on a list equals the length of the list. It is possible for the same element to
appear at two or more positions. Thus it is important not to confuse a position with the element at that
position. For instance, list (3) in Example 6.1 has twelve positions, seven of which hold 31 — namely,
positions 1, 3, 5, 7, 8, 10, and 12.

3.1.3 List as a tree


Every list can be represented as a binary tree whose every left child is a leaf [1]. More specifically, we
could construct such a list recursively as follows:
• the root is a node labelled “,” (comma).
We will name this node comma node. This node has two children:
◦ the first child is the head of the list
◦ the second child is determined as follows:
if tail has more than one elements then it is another (2 nd) comma node; otherwise it is an
empty node (node labelled by the empty string “”).
• the 2nd comma node (if used) has two children:
◦ the first child is the head of the tail sub-list
◦ the second child is determined as above based on the tail of the tail sub-list
• and so on until the last tail sub-list which contains one element

For example, the list of prime numbers less than 20 is shown in below.

3.1.4 Recursive definition


List can be defined recursively based on the concepts of head and tail as follows [2]:

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.5 Abstract properties


The followings are properties of list that are derived from the two definitions in Sections 3.1.1 and
3.1.4:

PR2 Page 27 Duc M. L. 2019


• list is a sequence of elements
• a non-empty list must have a head

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.

PR2 Page 28 Duc M. L. 2019


isEmpty
To return true or false depending on whether or not the list is empty.

3.2 Design specification

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].

3.2.2 Recursive list


3.2.2.1 Overview
This design directly transforms the recursive definition into a recursive class definition named List.
However, unlike class Tree, which does not have any attributes of the same type, class List has an
attribute name tail whose type is also List. This attribute keeps the reference to the list's tail.

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.

3.2.2.2 Header specification


Listing 8 below is the header specification of the class List. Note the followings:

PR2 Page 29 Duc M. L. 2019


• the recursive definition is inserted in the OBJECT section
• attribute head has the type Object
• attribute tail has the type List
• rep invariant states the abstract properties in Section 3.1.5 in terms of the two attributes head,
tail
/**
* @overview Represents a (possibly empty) list of elements of arbitrary types.
*
* @attributes
* head Object
* tail List
*
* @object
* A typical list is c = (x1,...,xn), where head(x1) and tail(x2,...,xn) are
elements.
*
* <p>Recursive definition:
* Basis
* () is a list
* Induction
* for all object x and for all list L. (x,L.elements) is a list
*
* @abstract_properties
* mutable(head)=false /\ optional(head)=true /\
* mutable(tail)=true /\ optional(tail)=true /\
* isEmpty(tail)=false -> head is initialised
*
* @author dmle
*/
public class List

Listing 8: The header specification of the recursive design of List

3.2.2.3 Derived attributes


Because the length() operation involves recursively counting the number of occurrences of all the tail
sub-lists of a list, invoking it many times may affect the overall performance of the program. To
overcome this, we create a new derived attribute named length and use it to record the current number
of elements of the list. To keep this attribute current, it is updated when we insert and remove elements
to and from the list.

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.

3.2.2.4 Operational specification


The remainder of Listing 9 below is the operational specification of class List. The specification
includes an essential sub-set of the operations listed in Section 3.1.6.

PR2 Page 30 Duc M. L. 2019


Constructors
As shown in Listing 9, class List has three constructors: one public and two private. The public
constructor implements the basis rule of the recursive definition. The one-argument private constructor
together with the operation push implements the inductive rule of the definition. The two-argument
private constructor is used by the operation clone to copy the list tail before returning it.

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

PR2 Page 31 Duc M. L. 2019


* @effects initialises this to be (x)
*/
private List(Object x)

/**
* 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

PR2 Page 32 Duc M. L. 2019


* return tail as a new list
*/
public List tail()

/**
* @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()
}

Listing 9: The design specification of the class List

PR2 Page 33 Duc M. L. 2019


3.2.3 Linked list
3.2.3.1 Overview
Another design, which is suggested in [1], is to encapsulate both head and tail using a cell. Figure 7
below illustrates this design. Given a list L = (a 1,a2,..., an), the list object variable for L points to the first
cell, the tail of this cell points to the second cell, and so on. The tail of the last cell points is null since
it points to nowhere (the list terminates here). The head of each cell contains the value of each element.

Figure 7: A cell-based design for list. Source: [1]

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.

3.2.3.2 Partial specification


Listing 10 below is the partial specification of class LinkedList. We highlight the differences between
this specification and the specification of the class List in Section 3.2.2. The operational specification
is omitted as it is the same as the specification of that class.
Note the followings:
• typical object and abstraction function is defined in terms of cell.head and cell.tail
• the rep invariant regarding head and tail is moved to class Cell
• class Cell is a private inner class
• operation clone is moved to class Cell as tail is defined there
import userlib.DomainConstraint;

/**
* @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

PR2 Page 34 Duc M. L. 2019


* existing elements of L become the tail of the new list.
*
* @abstract_properties
* mutable(cell)=true /\ optional(cell)=true /\
* P_Cell
*
*
* @author dmle
*/
public class LinkedList {
@DomainConstraint(type="Cell",mutable=true,optional=true)
private Cell cell;

// 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;

PR2 Page 35 Duc M. L. 2019


// constructors
/**
* @requires v != null
* @effects
* initialise this to be <v,r>
*/
public Cell(Object v, Cell r)

/**
* @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

Listing 10: The paritial specification of the class LinkedList

3.2.4 [0] Array-based design


3.2.4.1 Definition
Array stores elements in a sequence, and thus can be used as a concrete type for list. However, an
array-based design has two draw-backs compared to other designs:
• fixed size:
This has two implications. First is the waste of space. The array size needs to be initialised up to
a maximum value, even though the elements may not fill up. Second is performance overhead
in adding more elements than an array can hold. This requires creating a new array and copying
all the elements over.
• homogeneous type: all elements in an array are of the same type
This prevents the list from supporting parameterised type (it can only either support a specific
element type (e.g. int) or support the type Object).

PR2 Page 36 Duc M. L. 2019


3.2.4.2 Design guidelines
The array-based design does not conform to the recursive definition and, thus, there is no concept of
head and tail in this design. The following design points should be observed:
• recursive definition is removed
• abstract concept has two attributes: elements and length
◦ elements has type array
◦ length is integer, which is the actual number of elements currently in the list
• abstraction function puts the array elements together to form a list
• rep invariant ensures that elements is initialised and that length is within the limit of the
number of initialised slots in elements

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.

3.3.1 Recursive list


Refer to the attached source code for the complete code of the class List.

Iterative vs recursive implementation


Some operations are implemented using iterative code to process the list elements, while others using
recursive code. Operations that use iterative code are retrieve, toString, and those that use recursive
one are push, remove, and lookUp.

Exercise
1. Discuss the possibility of converting the implementations of the iterative operations to use
recursion and vice versa.

3.3.2 Linked list


Refer to the attached source code for the complete code of the class LinkedList.

Iterative vs recursive implementation


All operations are implemented using iterative code.

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.

PR2 Page 37 Duc M. L. 2019


3.4 Other designs
Exercise
1. Design and implement a class named ArrayList that follows the array-based design.
Hint: the rep should use a dynamic array data type (e.g. the Vector class) for elements.
2. Design and implement a class named DoubleLinkedList that is a linked list in which each cell
maintains a reference to the previous cell. Refer to [1] for details about double linked list.

3.5 [0] Reasoning

3.6 Application examples


Listing 11 is a test program that shows how to work with lists. It uses the class List to create a list
object and invokes list operations on this object. Listing 12 shows the output of the test program.

Note that a similar program can also created for the class LinkedList.
import t9.list.List;

public class ListTest {


public static void main(String[] args) {
// test data
Object[] objs = {"hello world!", 2, 0, 1, 3};
Object[] nonMembers = {null, -1, 0, "", 4};

List l = new List();

System.out.printf("initial: %s%n", l);

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);
}

System.out.printf("after: %s%n", l);


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());

PR2 Page 38 Duc M. L. 2019


// look up
System.out.println("lookup:\n=====================");
boolean tf;
for (Object o : objs) {
tf = l.lookUp(o);
System.out.printf("lookUp(%s) -> %b%n", o,tf);
}

for (Object o : nonMembers) {


tf = l.lookUp(o);
System.out.printf("lookUp(%s) -> %b%n", o,tf);
}

// 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);

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());
}
}

Listing 11: A test program that uses the class List

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!)

PR2 Page 39 Duc M. L. 2019


push(0) -> (0,2,hello world!)
push(1) -> (1,0,2,hello world!)
push(3) -> (3,1,0,2,hello world!)
after: (3,1,0,2,hello world!)
length() -> 5
head() -> 3
tail() -> (1,0,2,hello world!)
isEmpty() -> false
lookup:
=====================
lookUp(hello world!) -> true
lookUp(2) -> true
lookUp(0) -> true
lookUp(1) -> true
lookUp(3) -> true
lookUp(null) -> false
lookUp(-1) -> false
lookUp(0) -> true
lookUp() -> false
lookUp(4) -> false
retrieve:
=====================
retrieve(0) -> 3
retrieve(1) -> 1
retrieve(2) -> 0
retrieve(3) -> 2
retrieve(4) -> hello world!
retrieve(5) -> null
remove:
=====================
remove(hello world!) -> (3,1,0,2)
remove(2) -> (3,1,0)
remove(0) -> (3,1)
remove(1) -> (3)
remove(3) -> ()
after: ()
length() -> 0
head() -> null
tail() -> null
isEmpty() -> true

Listing 12: The program output

3.7 [R] Exercises

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?

PR2 Page 40 Duc M. L. 2019


d) What are all the sublists?
e) How many subsequences are there?
f) What is the head?
g) What is the tail?
h) [0] How many positions are there?

6.2.2: Repeat Exercise 6.2.1 for the character string banana.

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.1: Let L be the list (3, 1, 4, 1, 5, 9).


a) What is the value of delete(5, L)?
b) What is the value of delete(1, L)?
c) What is the result of popping L?
d) What is the result of pushing 2 onto list L?
e) What is returned if we perform lookup with the element 6 and list L?
f) If M is the list (6, 7, 8), what is the value of LM (the concatenation of L and M )? What is M L?
g) What is f irst(L)? What is last(L)?
h) What is the result of retrieve(3, L)?
i) What is the value of length(L)?
j) What is the value of isEmpty(L)?

[0] 6.3.2**: If L and M are lists, under what conditions is LM = M L?

[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 )

PR2 Page 41 Duc M. L. 2019


4 References
1: Aho A. V and Ullman J. D., Foundations of Computer Science: C Edition, W. H. Freeman, 1994
2: Broda K., et al., Reasoned Programming, Prentice Hall Trade, 1994

PR2 Page 42 Duc M. L. 2019

Das könnte Ihnen auch gefallen