On Coding Labeled Trees
Advisor
Prof. Rossella Petreschi
Academic Year 2006/2007
Ph.D. Candidate
Saverio Caminiti
Author Address:
Saverio Caminiti
Computer Science Department
Sapienza University of Rome
Via Salaria 113, I-00198 Rome, Italy
Email: caminiti@di.uniroma1.it
Homepage: caminiti@di.uniroma1.it
to Beatrice
Abstract
Trees are probably the most studied class of graphs in Computer Science. In
this thesis we study bijective codes that represent labeled trees by means of
string of node labels. We contribute to the understanding of their algorithmic
tractability, their properties, and their applications.
The thesis is divided into two parts. In the first part we focus on two
types of tree codes, namely Prüfer-like codes and Transformation codes. We
study optimal encoding and decoding algorithms, both in a sequential and in
a parallel setting. We propose a unified approach that works for all Prüferlike codes and a more generic scheme based on the transformation of a tree
into a functional digraph suitable for all bijective codes. Our results in this
area close a variety of open problems.
We also consider possible applications of tree encodings, discussing how
to exploit these codes in Genetic Algorithms and in the generation of random
trees. Moreover, we introduce a modified version of a known code that, in
Genetic Algorithms, outperform all the other known codes.
In the second part of the thesis we focus on two possible generalizations of
our work. We first take into account the classes of k-trees and k-arch graphs
I
II
ABSTRACT
(both superclasses of trees): we study bijective codes for this classes of graphs
and their algorithmic feasibility. Then, we shift our attention to Informative
Labeling Schemes. In this context labels are no longer considered as simple
unique node identifiers, they rather convey information useful to achieve
efficient computations on the tree. We exploit this idea to design a concurrent
data structure for the lowest common ancestor problem on dynamic trees.
We also present an experimental comparison between our labeling scheme
and the one proposed by Peleg for static trees.
Acknowledgments
I would like to express my gratitude to my advisor Rossella for all she did
for me in these years. She suggested me to enroll for a PhD position and
she helped me moving my first steps in algorithmic research. She always
supported, tolerated, and motivated me, believing in my ability even more
than I did sometimes. I am also very grateful to Irene and Tiziana for the
time they spent working with me, their advices, and their help, being always
kind and friendly.
Special thanks go to Prof. Narsingh Deo and Prof. András Frank. The
former gave me the chance to visit the Computer Science Department at
the University of Central Florida; the latter made it possible for me to
join EGRES group at the ELTE University in Budapest as a visiting researcher. Both these experiences have been fundamental parts of my professional growth.
Explicitly, I want to thank Prof. Alessandro Mei for being in my thesis
committee, and the external reviewers Prof. Luisa Gargano and Prof. Ömer
Eğecioğlu for their precious comments to this thesis. I would also thank
all my further coauthors for the interesting discussions and the work done
during these years: Prof. Stephan Olariu, Paulius Micikevičius, Guillaume
III
IV
ACKNOWLEDGMENTS
Fertin, and Emanuele G. Fusco.
I would also like to say thanks to all my friends that shared with me all
the joys and pains of being a student in Rome; Rosa who made the stay in
Florida better for me and my wife; and all the guys I knew in Budapest.
Finally, I give thanks to my family for the support they provided me
through my entire life and in particular, I must acknowledge my wife – without her love, encouragement and help, I would not have finished this thesis.
Contents
1 Introduction
1.1 Original Contributions of this Thesis . . . . . . . . . . . . . .
1
4
I
7
Tree Encodings
2 Prüfer-Like Codes
2.1 Prüfer Code . . . . . . . . .
2.1.1 Decoding . . . . . .
2.1.2 Rooted Trees . . . .
2.2 Neville’s Codes . . . . . . .
2.2.1 Second Neville Code
2.2.2 Third Neville Code .
2.3 Stack-Queue Code . . . . .
2.4 Concluding Remarks . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3 Algorithms for Prüfer-like Codes
3.1 Known Algorithms . . . . . . . . . . . . .
3.1.1 Prüfer Code . . . . . . . . . . . . .
3.1.2 Second Neville Code . . . . . . . .
3.1.3 Third Neville Code . . . . . . . . .
3.1.4 Stack-Queue Code . . . . . . . . .
3.2 A Unified Encoding Algorithm . . . . . . .
3.2.1 Coding by Sorting Pairs . . . . . .
3.2.2 Sequential Algorithm . . . . . . . .
3.3 A Unified Decoding Algorithm . . . . . . .
3.3.1 Decoding by Rightmost Occurrence
3.3.2 Sequential Algorithm . . . . . . . .
3.3.3 Second Neville Code . . . . . . . .
3.4 Unified Parallel Algorithms . . . . . . . .
3.4.1 Encoding . . . . . . . . . . . . . .
V
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
9
10
12
13
14
15
16
18
19
.
.
.
.
.
.
.
.
.
.
.
.
.
.
21
22
22
24
25
26
27
28
31
32
32
33
36
37
38
VI
CONTENTS
3.4.2 Decoding . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.5 Concluding Remarks . . . . . . . . . . . . . . . . . . . . . . . 43
4 Applications of Tree Encodings
4.1 Generating Random Trees . . . . . . . . .
4.1.1 Experimental Comparison . . . . .
4.1.2 Constrained Random Trees . . . .
4.1.3 Parallel Random Trees Generation
4.2 Genetic Algorithms . . . . . . . . . . . . .
4.3 Concluding Remarks . . . . . . . . . . . .
5 Transformation Codes
5.1 Preliminaries . . . . . . . . . . . .
5.2 Picciotto’s Codes . . . . . . . . . .
5.2.1 Blob Code . . . . . . . . . .
5.2.2 Happy Code . . . . . . . . .
5.2.3 Dandelion Code . . . . . . .
5.3 E-R Bijection . . . . . . . . . . . .
5.4 Functional Digraph Transformation
5.4.1 Blob Transformation . . . .
5.4.2 MHappy Transformation . .
5.4.3 Dandelion Transformation .
5.5 Comparing Transformation Codes .
5.6 Concluding Remarks . . . . . . . .
II
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
45
46
47
49
50
50
54
.
.
.
.
.
.
.
.
.
.
.
.
57
58
59
60
62
64
66
68
70
76
78
80
81
Generalizations
6 Encoding k-Trees
6.1 Preliminaries . . . . . . . .
6.2 Known Codes . . . . . . . .
6.3 Characteristic Tree . . . . .
6.4 Generalized Dandelion Code
6.5 A New Code for k-Trees . .
6.5.1 Encoding Algorithm
6.5.2 Decoding Algorithm
6.6 Compact Representation . .
6.7 Concluding Remarks . . . .
83
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
85
86
88
91
93
96
96
103
105
106
7 Counting k-Arch Graphs
107
7.1 Encoding k-Arch Graphs . . . . . . . . . . . . . . . . . . . . . 108
VII
CONTENTS
7.2 Decoding k-Arch Graphs . .
7.3 Enumerating k-Arch Graphs
7.3.1 Experimental Results
7.4 Concluding Remarks . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
8 ILS for LCA on Dynamic Trees
8.1 Preliminaries . . . . . . . . . . . . . .
8.1.1 The Concurrency Model . . . .
8.2 Peleg’s Labeling Scheme . . . . . . . .
8.3 A Dynamic Sequential Data Structure
8.3.1 Tree Decomposition . . . . . . .
8.3.2 The Data Structure . . . . . . .
8.3.3 Analysis . . . . . . . . . . . . .
8.4 A Concurrent Implementation . . . . .
8.4.1 Analysis . . . . . . . . . . . . .
8.5 Experimental Comparison . . . . . . .
8.5.1 Ingredients . . . . . . . . . . .
8.5.2 Experimental Framework . . . .
8.5.3 Experimental Results . . . . . .
8.6 Concluding Remarks . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
110
112
115
116
.
.
.
.
.
.
.
.
.
.
.
.
.
.
117
. 118
. 119
. 120
. 122
. 123
. 125
. 128
. 131
. 134
. 135
. 136
. 137
. 138
. 144
9 Conclusions and Future Work
145
Glossary
147
VIII
CONTENTS
Chapter 1
Introduction
Trees are probably the most studied class of graphs in Computer Science.
They are used in a large variety of domains, including computer networks,
computational biology, databases, pattern recognition, web mining. In almost all applications, tree nodes and edges are associated with labels, weights,
or costs. Examples range from XML data to tree-based dictionaries (heaps,
AVL, RB-trees), from phylogenetic trees to spanning trees of communication
networks, from indexes to tries (used in compression algorithms). Many data
structures can be used to represents trees: adjacency matrices, adjacency
lists, parent vectors, and balanced parentheses are just a few examples.
An interesting alternative to the usual representations of tree data structures in computer memories is based on coding labeled trees by means of
strings of node labels. String-based codes for labeled trees have many practical applications. For example, they are used in fault dictionary storage [12],
distributed spanning tree maintenance [48], generation of random trees [36],
Genetic Algorithms [75, 92].
There are codes that define bijections between the set of labeled trees and
a set of strings of node labels. In these one-to-one mappings, the length of
the string must be equal to n−2, since Cayley has proved that the number of
labeled trees on n nodes is nn−2 [26]1 , In his proof of Cayley’s theorem, Prüfer
1
For the sake of correctness we report that Borchardt [13] proved this result almost
30 years before Cayley, in 1860. Cayley independently rediscovered it in 1889. Nowadays
this result is universally known as Cayley’s Theorem.
1
2
CHAPTER 1. INTRODUCTION
provided the first bijective string-based coding for trees [90]. Over the years,
many other bijective codes have been introduced [29, 39, 43, 78, 79, 89].
Motivated by the importance of labeled trees, in this thesis we study algorithmic aspects related to bijective tree encodings. We contribute to the
understanding of bijective codes for labeled trees, their algorithmic tractability, their properties, and their applications. The thesis is divided into two
parts. In the first part we focus on two types of tree codes, named Prüfer-like
codes and Transformation codes. We study optimal algorithms for encoding
and decoding, both in a sequential and in a parallel setting. Our results in
this area close a variety of open problems. We also consider possible applications of tree encodings, discussing how to exploit these codes in Genetic
Algorithms and in the generation of random trees.
In the second part of the thesis we focus on two possible generalizations
of our work. We first take into account the class of k-trees [57] (a superclass of trees): we study bijective codes for this class of graphs and their
algorithmic feasibility for rooted, unrooted, and Rényi k-trees [94]. Then,
we shift our attention to Informative Labeling Schemes [87]. In this context
labels are no longer considered as simple unique node identifiers, they rather
convey information useful to achieve efficient computations on the tree. We
exploit this idea to design a concurrent data structure for the lowest common
ancestor problem on dynamic trees. In the literature other generalizations
and specializations of tree codes not considered in this thesis have been studied [44, 45, 83, 93].
Let us now detail the content of each chapter. We defer to Section 1.1 for
a description of all original contributions of this thesis. We refer the reader
to the Glossary at the end of this thesis for all notations and definitions not
explicitly introduced elsewhere.
Part I: Tree Encodings
In Chapter 2 we recall the Prüfer code as introduced in [90] and Prüfer-like
codes that hinge upon the same fundamental idea, i.e., recursive elimination
of leaves. Prüfer-like codes are due to Neville [79], Deo and Micikevičius [39].
We discuss how these codes can be used both for rooted and unrooted trees.
3
In Chapter 3 we initially survey known optimal algorithms for encoding
and decoding Prüfer-like codes. Then, we introduce a unified approach that
works for all of them [17, 20]. By means of our unified approach we completely
close the problem of encoding and decoding all these codes in a sequential
setting. We also provide efficient parallel algorithms that either match or
improve the performances of the best previous known results.
In Chapter 4 we describe two possible applications of tree encodings: random trees generation and Genetic Algorithms. The first application shows
how these combinatorial bijections can be fruitfully exploited to guarantee
that trees are generated uniformly at random, both in sequential and parallel
settings. We also present an experimental analysis showing that this method
is competitive with other known methods. Genetic Algorithms provide a
wider example of application of tree encodings. Many experimental comparisons have been presented in the literature, exploring several possible tree
encodings. Some of these experiments shifted our attention from Prüfer-like
code to Transformation codes.
In Chapter 5 we focus on bijective codes not belonging to the class of
Prüfer-like codes. We approaches proposed by Eğecioğlu and Remmel [43]
and by Picciotto [89], providing a general scheme based on the transformation
of a tree into a functional digraph (from which the name Transformation
codes). By means of our general scheme we are able to compare the codes
and provide theoretical reasons for their performances in Genetic Algorithm
implementations [24, 25].
Part II: Generalizations
In Chapter 6 we consider the class of k-trees, a natural generalization
of trees [57], and study bijective codes for labeled k-tree. We survey known
results and introduce a novel code together with encoding and decoding algorithms. The running time of our algorithms is linear with respect to the size
of the encoded k-tree. Our code can be easily adapted to rooted, unrooted,
and Rényi k-trees, preserving bijectivity [22, 23]. We conclude the chapter
with some considerations on the number of k-arch graphs (a superclass of ktrees): we consider enumerative results for this class given by Lamathe [72],
4
CHAPTER 1. INTRODUCTION
we prove that one such result is erroneous and provide a suitable correction [21].
In Chapter 8 we present the concept of Informative Labeling Scheme (ILS)
introduced by Peleg [87] and propose an ILS for Lowest Common Ancestor
on dynamic trees. We exploit it to obtain a concurrent data structure for
LCA of dynamic trees [18]. We also experimentally compare our scheme with
the one proposed by Peleg and show pros and cons of both schemes [19].
1.1
Original Contributions of this Thesis
A Unified Approach for Prüfer-like Codes. The unified approach presented in Chapter 3 makes it possible to encode and decode all Prüfer-like
codes introduced by Prüfer [90], Neville [79], Deo and Micikevičius [39]. The
unified encoding algorithm is based on the definition of pairs associated to
tree nodes according to criteria dependent on the specific code: the coding
problem is then reduced to the problem of sorting these pairs in lexicographic
order. The unified decoding algorithm hinges upon the computation of the
rightmost occurrence of each value in a codeword. By exploiting this approach, we obtain optimal linear time algorithms for encoding and decoding
all Prüfer-like codes presented in Chapter 2. We close the open problem of
finding a linear time decoding algorithm for the Second Neville code. We
also show how it is possible to parallelize our unified approach: our unified
√
algorithms either match or improve by a factor log n the performances of
the best ad hoc parallel algorithms known so far.
These results have been published on Theoretical Computer Science [20],
in the special issue devoted to the 6th Latin American Symposium on Theoretical Informatics (LATIN‘04) where a preliminary version of this work
appeared [17]. A part of these results also appeared in Congressus Numerantium [16].
A General Scheme for Transformation Codes. In Chapter 5 we introduce a general scheme for defining bijective codes based on the trans-
1.1. ORIGINAL CONTRIBUTIONS OF THIS THESIS
5
formation of a tree into a functional digraph. The class of Transformation
codes (i.e., those codes that can be defined with our general scheme) contains
each possible bijective code for labeled trees. The same is not true for other
classes, such as Prüfer-like codes. As examples, we show how it is possible to
map the codes by Eğecioğlu and Remmel [43] and by Picciotto [89] into our
scheme. This also gives us a better comprehension of how encoding preserves
the topology of the tree, and therefore helps to understand which code better
fits some desirable properties, such as locality and heritability.
These results have been published in Proceedings of the 11th International
Conference on Computing and Combinatorics (COCOON‘05) [25]. An extended version of this work has been submitted to SIAM Journal of Discrete
Mathematics [24]. A part of these results also appeared in Congressus Numerantium [16].
Optimal Algorithms for k-Trees Encoding. In Chapter 6 we introduce
a novel bijective code for rooted and unrooted k-trees (and also for Rényi
k-trees). We give a detailed description of linear time encoding and decoding
algorithms for our code. We also analyze, in Chapter 7, the result presented
by Lamathe [72] concerning the number of k-arch graphs. We point out an
error in his work: the closed formula he provided overestimates the cardinality of this class of graphs. We provide an exact counting result in terms of a
recursive function.
The results concerning k-trees encoding have been published in Proceedings of the International Symposium on Combinatorics, Algorithms, Probabilistic and Experimental Methodologies (ESCAPE‘07) [23]. An extended
version of this work has been submitted to Theory of Computing Systems [22].
The correct formula for the number of k-arch graphs appeared on Journal of
Integer Sequences [21].
Informative Labeling Schemes for LCA on Dynamic Trees. In Chapter 8 we exploit the idea of Informative Labeling Schemes to design concurrent data structures. The scenario we have considered is a multiprocessor
machine with asynchronous access to a shared memory. We focus on a data
6
CHAPTER 1. INTRODUCTION
structure for the Lowest Common Ancestor Problem on dynamic trees. We
propose a new Informative Labeling Scheme for Lowest Common Ancestor
that may be used for dynamic trees and show a detailed experimental comparison between our scheme and the one proposed by Peleg [87] for LCA on
static trees. These results have not yet been published [18, 19].
Part I
Tree Encodings
7
Chapter 2
Prüfer-Like Codes
We start this chapter recalling the well known Prüfer code [90], originally
introduced by the German mathematician in 1918 to provide an alternative
proof of Cayley’s theorem.
The Prüfer code deals with unrooted trees on n nodes labeled with distinct
values from a set L of cardinality n. Such trees are known as Cayley trees.
Without loss of generality we will assume that the labels are integer numbers
in the range from 1 to n, i.e., that L = [1, n]. Moreover, we will identify a
node with its label: the node set of a tree on n nodes will therefore be [1, n].
The set of Cayley trees on n nodes is denoted as Tn . It is well known that
|Tn | = nn−2 :
Theorem 2.1 (Cayley [26]). There exist nn−2 unrooted trees with n nodes
univocally labeled with n distinct labels.
Let us call Rn the class of rooted Cayley trees with n nodes. Since there
are n different possible ways to root a Cayley tree in Tn , it directly follows
that |Rn | = n|Tn |. More formally, from Theorem 2.1 we obtain:
Corollary 2.2. There exist nn−1 rooted trees with n nodes univocally labeled
with n distinct labels.
We continue the chapter surveying other codes that hinge upon the same
fundamental idea exploited by Prüfer: for this reason they are called Prüfer9
10
CHAPTER 2. PRÜFER-LIKE CODES
like codes. The codes we will study are due to Neville [79] and to Deo and
Micikevičius [39].
This chapter does not contain any original contribution but it is helpful to
understand the followings. It is organized as follows: in Section 2.1 we recall
the original encoding and decoding processes as introduced by Prüfer [90];
we also clarify how to apply it to rooted trees. In Section 2.2 and Section 2.3
we describe the Prüfer-like codes introduced by Neville [79] and by Deo and
Micikevičius [39], both for rooted and unrooted trees.
We will use adj(v) to refer to the set of all the nodes adjacent to any
node v. If adj(v) consists of a single node (i.e., v is a leaf), when there is no
ambiguity, we will use adj(v) to refer to the adjacent node, itself rather then
to a set of cardinality 1. Let T be a tree and v a leaf in T , we denote T v
the tree obtained form T by removing the node v and the edge incident on
node v.
2.1
Prüfer Code
The Prüfer code is a bijective association between trees in Tn and sequences
of n − 2 node labels. We will say that the sequence associated to a tree T is
the codeword for T . Codewords are strings of length n − 2 over the alphabet
[1, n] and thus belong to [1, n]n−2 . The operator :: will be used to denote the
concatenation of two strings.
The original definition of the Prüfer code was formulated in terms of a
recursive elimination of leaves. Given an unrooted tree T , recursively remove
the smallest leaf until a single node remains. Let ai be i-th removed leaf and
bi the node adjacent to ai when ai has been removed. The sequence:
a1 , a2 , . . . , an−1
b1 , b2 , . . . , bn−1
(2.1)
univocally describes T , since each edge of the tree appears as a pair (ai , bi )
for some i. This sequence of pairs is known as the Natural Code for T . Notice
11
2.1. PRÜFER CODE
that bn−1 is always n, since this node will never be selected as a smallest leaf
to be removed.
The Prüfer code for T is the string:
C = (b1 , b2 , . . . , bn−2 )
The following interesting property holds:
Property 2.3. Given an unrooted Cayley tree T , let C be its Prüfer code.
Each node v of T appears in C exactly deg(v) − 1 times.
Proof. Consider the Natural Code for T . Since Equation 2.1 is a list of all
edges in T , each node v appears in the Natural Code exactly deg(v) times.
Any node but n appears in the sequence a1 , . . . , an−1 exactly once, while
n does not appear in this sequence: n appears in b1 , . . . , bn exactly deg(n)
times and bn = n. This implies that each node appears in C = (b1 , . . . , bn−1 )
exactly deg(v) − 1 times.
Before showing how to invert this bijection (i.e., how to rebuild a tree
from its codeword), let us introduce a more formal definition for the code.
We will consider the encoding procedure of the Prüfer code as a recursive
function π:
π(T ) = adj(minT ) :: π(T minT )
(2.2)
where minT denotes the minimum leaf in T . If T has only 2 nodes π(T ) is
the empty string.
Example 1. In Figure 2.1 an example of coding is provided. At the beginning
the smallest leaf is a1 = 3, adj(3) = {2} and then b1 = 2. Once node 3 is
removed from the tree, node 2 the smallest leaf: a2 = 2 and its adjacent node
is b2 = 5, then node 2 is removed. At the next steps a3 = 4, b3 = 1, a4 = 6,
b4 = 1. As soon as node 6 is removed node 1 becomes a leaf and then a5 = 1
and b5 = 5. The process stops when the tree consists of a single edge (5, 7)
since a codeword of length n − 2 has been computed. The Prüfer code for
this tree is (2, 5, 1, 1, 5).
12
CHAPTER 2. PRÜFER-LIKE CODES
Figure 2.1: Step by step computation of Prüfer code. At each step the leaf with
smallest label is deleted. The resulting codeword is (2, 5, 1, 1, 5).
2.1.1
Decoding
We now show how it is possible to decode Prüfer code. Given any codeword
C in [1, n]n−2 , the decoding computes a tree T such that π(T ) = C.
From Property 2.3 we know that leaves in T are all nodes that do not
appear in C = (b1 , . . . , bn−2 ). We can therefore compute a1 as the smallest
number in [1, n] not in C. In order to compute a2 consider that (b2 , . . . , bn−2 )
is the codeword for T ′ = T {a1 }: indeed function π recursively computes
(b2 , . . . , bn−2 ) as π(T ′ ) (see Equation 2.2). Thus we can identify the smallest
leaf in T ′ as the smallest number in [1, n] {a1 } not in (b2 , . . . , bn−2 ). Analogously (b3 , . . . , bn−2 ) = π(T ′ {a2 }), and then a3 is the smallest number in
[1, n] {a1 , a2 } not in (b2 , . . . , bn−2 ). In general ai is the smallest number in
[1, n] {a1 , . . . , ai−1 } not in (bi , . . . , bn−2 ).
In order to complete the reconstruction of the Natural Code consider that
bn−1 is always n and an−1 should be the only number smaller than n not yet
used in (a1 , . . . , an−2 ). The tree is T = ([1, n], {(ai, bi ) : i ∈ [1, n − 1]}).
It is easy to see that the decoding procedure is the inverse function of π,
moreover π is injective and surjective: the Prüfer code is a bijection between
Tn and [1, n]n−2 . For formal proofs of these assertions we refer the interested
reader to [90]. In these thesis we rather focus on algorithmic implications.
2.1. PRÜFER CODE
13
Before showing how to extend the Prüfer code to rooted trees, let us show
an example of decoding.
Example 2. Given the codeword (2, 5, 1, 1, 5) we can easily deduce n = 7
since the codeword has length 5. The set of all leaves of the encoded tree T
is {3, 4, 6, 7}, i.e., all those numbers in [1, 7] not appearing the codeword. The
smallest leaf is a1 = 3. Leaves of T {3} are all those nodes in [1, 7]{3} not
appearing in the sequence (5, 1, 1, 5), i.e., {2, 4, 6, 7}. Then the leaf removed
at the second step of the encoding should be a2 = 2. The algorithm proceeds
by choosing a3 = min([1, 7]{2, 3}(1, 1, 5)) = 4, a4 = min([1, 7]{2, 3, 4}
(1, 5)) = 6, and a5 = min([1, 7] {2, 3, 4, 6} (5)) = 1. To complete the
reconstruction of the Natural Code for T the algorithm chooses b6 = 7 and
a6 = 5, i.e., the only number in [1, 6] not yet chosen as ai . The Natural Code
obtained univocally identifies the tree depicted in Figure 2.1:
3, 2, 4, 6, 1, 5
2, 5, 1, 1, 5, 7
2.1.2
Rooted Trees
The encoding procedure proposed by Prüfer can be applied to a rooted tree.
In this case the root is never considered as a leaf, even if it has degree 1,
and is never removed from the tree during the encoding. This implies that,
in the Natural Code, bn−1 is the tree root. Since this information changes
according to the encoded tree, it cannot be omitted. Thus, the codeword has
length n − 1 and the code a bijection between Rn and [1, n]n−1 .
We remark that Property 2.3 slightly changes when the code is applied
to rooted trees:
Property 2.4. Given a Cayley tree T rooted at r, let C be its Prüfer code.
The root r appears in C exactly deg(r) times and each other node v of T
appears in C exactly deg(v) − 1 times.
Example 3. In Figure 2.2 an example of encoding a rooted tree is presented.
At each step the smallest leaf is removed and its parent (i.e., its unique
adjacent node) is added to the codeword. We remember that the root is
14
CHAPTER 2. PRÜFER-LIKE CODES
Figure 2.2: Step by step computation of Prüfer code for a rooted tree. At
each step the leaf with smallest label is deleted. The resulting codeword is
(7, 4, 4, 3, 3, 3, 7).
never considered as a leaf, even if it has degree 1. The resulting n − 1 length
codeword is (7, 4, 4, 3, 3, 3, 7).
The decoding procedure simply deduces the sequence of the n−1 removed
leaves, as described for unrooted trees. Then, it reconstructs the Natural
Code, and returns the corresponding tree rooted in the last symbol of the
codeword.
We can also deal with Cayley trees rooted in a fixed node x (such as the
√
node 1, the node with maximum label, the node with label ⌈ n ⌉, etc.), let
us call Tnx the class of such trees. In this case the codeword length can be
reduced to n − 2: there is no need to maintain the last element bn−1 since it
is always x. The code is bijective because, for each x, |Tnx | = nn−2 .
It is worth noticing that the codeword that the Prüfer encoding procedure
associates to a tree T ∈ Tnn is exactly the same codeword obtained by the
original Prüfer code applied to T as an unrooted tree.
2.2
Neville’s Codes
In 1953, Neville [79] presented three different codes. The first one coincides
with Prüfer code, while the other two constitute novel bijections between
Cayley trees and strings of node labels. Remarkably, all of them have been
2.2. NEVILLE’S CODES
15
described in terms of recursive leaves elimination; each time a leaf is removed
from the tree, the unique node adjacent to the leaf is added to the codeword.
Because of this similarity with the Prüfer code these codes are called Prüferlike codes. However, each code has a specific criterion to determine the
sequence of leaves eliminated at each step.
In the following we will describe all codes as applied to rooted Cayley
trees. To apply these codes to unrooted trees, as stated in Section 2.1.2, it
is enough to root the tree in a fixed node (e.g., always n) and omit the last
symbol in the codeword. Indeed Neville’s codes have been originally introduced for trees rooted in the fixed node n, but have been later generalized by
Moon [78] to unrooted and arbitrarily rooted trees. All these codes, similarly
to the Prüfer code, satisfy Properties 2.3 and 2.4.
2.2.1
Second Neville Code
The Second Neville code, at each step, removes from the tree all the leaves
in increasing label order. The parent (the unique adjacent node) of each leaf
is added to the codeword.
Example 4. In Figure 2.3 an example of tree encoding with the Second Neville
code is shown. At the first step all leaves {8, 3, 5, 9, 4} are removed in increasing label order: (3, 4, 5, 8, 9). The first five symbols in the codeword
corresponds to (6, 10, 6, 1, 7), i.e., the labels of nodes adjacent to removed
leaves. Iterating this process until the tree consists of a single node a codeword of length n − 1 is obtained: (6, 10, 6, 1, 7, 2, 7, 7, 7).
The process of decoding the Second Neville code is analogous to the one
described in Section 2.1.1, except for the fact that, at each step, rather than
the smallest leaves we chose all leaves in increasing order. Let us show an
example of decoding the codeword C = (6, 10, 6, 1, 7, 2, 7, 7, 7) to obtain a
rooted tree.
Example 5. The codeword length is n − 1 then n = 10. The set of leaves of
the initial tree is {3, 4, 5, 8, 9}, i.e., all values not in C. These values, together
with the first 5 symbols in C, allow us to deduce the edges (3, 6), (4, 10), (5, 6),
(8, 1), and (9, 7). The remaining part of the codeword (2, 7, 7, 7) corresponds
16
CHAPTER 2. PRÜFER-LIKE CODES
Figure 2.3: Step by step computation of the Second Neville code. At each
step all leaves are deleted in increasing label order. The resulting codeword is
(6, 10, 6, 1, 7, 2, 7, 7, 7).
to the encoding of the subtree whose nodes are [1, 10]{3, 4, 5, 8, 9}. All these
nodes, but 2 and 7 that appear in the codeword, are leaves: {1, 6, 10}. The
edges identified are: (1, 2), (6, 7), and (10, 7). In the last step the codeword
consists of a single symbol (7) and the subtree represented by this codeword
has only two nodes: {2, 7}. Then the last edge is (2, 7).
We recall that, as noted in Section 2.1.2, the last symbol of the codeword
is the root of the tree. Moreover, each edge deduced during the decoding
process can be created as an oriented edge going from a node (the leaf) to
its parent (the node in the codeword).
We want to remark that when the Second Neville code is applied to
unrooted trees (as done by Moon in [78]) the last node remaining in the tree
after the encoding is the center of the tree. It corresponds to the last symbol
in the codeword (i.e., the one that should be omitted to obtain an n − 2
length codeword). If the tree has two centers the one with highest label will
be the last node.
2.2.2
Third Neville Code
The Third Neville code at the first step selects the smallest leaf and removes
it. In the subsequent steps, if the node adjacent to the last removed leaf
is now a leaf, it is selected and removed; otherwise the new smallest leaf is
2.2. NEVILLE’S CODES
17
Figure 2.4: Step by step computation of the Third Neville code. At each step
the pending chain containing the leaf with smallest label is deleted. The resulting
codeword is (8, 3, 4, 4, 3, 3, 7).
selected. In other words, nodes that are not leaves in the initial tree are
removed as soon as they become leaves, while all leaves of the initial tree are
selected in increasing order.
The example in Figure 2.4 helps us to clarify the criterion.
Example 6. Initially we select leaf 1: when it is removed, its adjacent node
8 becomes a leaf and then it is suddenly chosen and removed. Node 3 still
has other children, and thus we seek for a new smallest leaf: 2. The removal
of node 2 does not let its parent 4 becomes a leaf, and then the smallest
leaf 5 is chosen. Once 5 is removed 4 becomes a leaf and is removed. The
only remaining leaf is 6 whose removal lets 3 become a leaf. Finally 3 is
removed. As in Prüfer code, each time a leaf is removed, the label of its
adjacent node is added to the codeword, then the Third Neville code for this
tree is (8, 3, 4, 4, 3, 3, 7).
An alternative way to look at this code is as it works by deleting chains.
We call pending chain a path u1 , . . . , uk of maximal length such that the
starting point u1 is a leaf, and, for each i ∈ [1, k − 1], the deletion of ui
makes ui+1 a leaf: the code works by iteratively deleting the pending chain
containing the smallest leaf.
Moon in [78] applied the Third Neville code to unrooted trees. In this
case the last node remaining in the tree after the encoding is the leaf with
maximum label. Indeed, consider the set of all leaves of the initial tree in
18
CHAPTER 2. PRÜFER-LIKE CODES
Figure 2.5: Step by step computation of the Stack-Queue code. At the first step
all the leaves are deleted in increasing label order, other nodes are deleted in the order in which they become leaves. The resulting codeword is (6, 10, 6, 1, 7, 7, 7, 2, 2).
increasing order {l1 , l2 , . . . , lk }. After the removal of pending chains corresponding to l1 , l2 , . . . , lk−2 , the tree consists of a single chain joining lk−1 to
lk . This chain is removed starting from lk−1, then the last node must be lk .
2.3
Stack-Queue Code
Recently, Deo and Micikevičius [39] introduced a new Prüfer-like code called
Stack-Queue code. This code initially deletes all tree leaves in increasing
label order, as the Second Neville code. Then, it deletes all the internal
nodes in the order in which they become leaves. The original presentation
makes use of specific data structures. At the beginning of the encoding, a
FIFO queue Q is initialized with all tree leaves in increasing label order. At
each step the algorithm extracts a leaf from Q and removes it. Whenever a
node becomes a leaf, it is added to Q. We defer to Chapter 3 the explicit
presentation of their algorithm.
Example 7. Consider the tree in Figure 2.5. Let us explicitly report the
content of the queue Q at each step of the algorithm. It initially contains all leaves in increasing order: Q(0) = (3, 4, 5, 8, 9); in the following
steps the queue changes as follows: Q(1) = (4, 5, 8, 9), Q(2) = (5, 8, 9, 10),
Q(3) = (8, 9, 10, 6), Q(4) = (9, 10, 6, 1), Q(5) = (10, 6, 1), Q(6) = (6, 1),
Q(7) = (1, 7), Q(8) = (7), Q(9) = (). The leaves elimination order is given by
2.4. CONCLUDING REMARKS
19
the sequence of nodes extracted from the queue: 3, 4, 5, 8, 9, 10, 6, 1, 7. The
resulting codeword is (6, 10, 6, 1, 7, 7, 7, 2, 2).
The original decoding algorithm proceeds backwards and uses a LIFO
stack S. As a first step the codeword C is scanned right to left and each
value is pushed in S (avoiding duplicates). Then all leaves (values not in C)
are pushed in S in decreasing label order. This ensures that S contains all
nodes that have been pushed to Q during the encoding in the reverse order.
Therefore, popping values out of S, we have the exact leaves elimination
order realized by the encoding. This correctly reconstructs the tree.
Example 8. For example, decoding the codeword C = (6, 10, 6, 1, 7, 7, 7, 2, 2)
will make the content of the stack to be S = (2, 7, 1, 6, 10) after the right to
left scan of C. Then adding all values not in C in decreasing order we obtain
S = (2, 7, 1, 6, 10, 9, 8, 5, 4, 3). Popping n − 1 elements out of S we obtain the
same leaves elimination order realized by the encoding: 3, 4, 5, 8, 9, 10, 6, 1, 7.
Then we can rebuild the tree.
This code has been originally introduced for unrooted trees. In this case
the last node is the center of the tree (either of them if the tree has two
centers). This makes it possible to compute the tree diameter directly from
the codeword, as shown in [39]. The same property holds for the Second
Neville code.
2.4
Concluding Remarks
Up to now, we have recalled four well known Prüfer-like codes. All of them
are based on recursive leaves elimination and realize bijection between rooted
and unrooted Cayley trees an codewords of length n−2 and n−1, respectively.
In concluding this chapter, we want to highlight that the criterion used
by Prüfer to select the order in which tree nodes are removed (at each step
remove the smallest leaf) can be substituted by many other deterministic
criterions. The other codes we have seen in this chapter are just three possible
examples.
20
CHAPTER 2. PRÜFER-LIKE CODES
In general, any deterministic criterion can be used to generate a bijective
Prüfer-like code, provided that it select, at each step of the encoding, a (nonempty) sequence of the tree leaves exploiting only the current leaves set and
the sequences chosen in the previous steps. Indeed, the decoding scheme
proposed in Section 2.1.1, at each step, can identify the set of leaves removed
in the corresponding step of the encoding simply using the same deterministic
criterion.
Chapter 3
Algorithms for Prüfer-like
Codes
In this chapter we focus on algorithmic aspects related to the computation
of all Prüfer-like codes presented in Chapter 2. Initially, in Section 3.1, we
survey a series of ad hoc algorithms for Prüfer code, Neville’s codes, and
Stack-Queue code. All these algorithms strongly depend on the properties
of the code which has to be computed and thus are very different from each
other.
As a novel contribution of this thesis we present a unified approach that
makes it possible to encode and decode all Prüfer-like codes introduced so far.
It has been published in [20] (a preliminary version of this work appeared
in [17]). The unified encoding algorithm presented in Section 3.2 is
based on the definition of pairs associated to tree nodes according to criteria
dependent on the specific code: the coding problem is then reduced to the
problem of sorting these pairs in lexicographic order. The unified decoding algorithm presented in Section 3.3 hinges upon the computation of
the rightmost occurrence of each value in a codeword. With these unified
algorithms we obtain optimal linear time algorithms for encoding and decoding all Prüfer-like code seen in Chapter 2. It should be noted that we close
the open problem of finding a linear time decoding algorithms for the Second
Neville code.
Finally, in Section 3.4 we show how it is possible to parallelize our unified
21
22
CHAPTER 3. ALGORITHMS FOR PRÜFER-LIKE CODES
approach achieving very good results: our unified algorithms either match or
improve the performances of the best ad-hoc parallel algorithms known so far.
Namely we obtain parallel encoding algorithms that require O(n) operations
√
for the Prüfer code and the Third Neville code and O(n log n) for the Second
Neville code and the Stack-Queue code. Concerning decoding we match the
O(n log n) bound known for Prüfer code and, for the first time, we provide
parallel algorithms for the Second Neville code, Third Neville code, and the
√
Stack-Queue code: these algorithms require O(n log n) operations.
3.1
Known Algorithms
Here we recall known optimal sequential algorithms to encode and decode
Prüfer code, Second Neville code, Third Neville code, and Stack-Queue code.
We assume to deal with rooted trees.
3.1.1
Prüfer Code
A straightforward implementation of the idea described in Section 2.1 would
require to compute, at each of n − 1 steps, the minimum among a set. Even
using appropriate data structures, like a minimum heap, this would lead to
algorithms whose running time is O(n log n).
Since the introduction of Prüfer code in 1918, a linear time algorithm for
encoding a tree has been given for the first time only in the late 70’s. In
fact, it was left as an exercise both in [80] (exercise 46, page 293), and in [40]
(exercise 2, page 666). Maybe this is the reason why it has been rediscovered
several times, and still nowadays optimal algorithms for Prüfer code appear
to be not known by researchers (see for example [42, 51, 61]).
The linear time encoding algorithm that we propose here is the version
published in [17] and in [20] of an idea that, according to our knowledge, is
due to Klingsberg [68].
Let us assume that the tree is represented by means of adjacency lists and
that the degree of each node is known (otherwise, it can be easily computed
3.1. KNOWN ALGORITHMS
23
with a simple scan of the adjacency lists). The input is an unrooted tree
represented by adjacency lists. The Prüfer code of T can be computed as
follows:
1.
2.
3.
4.
5.
6.
7.
8.
for each node v = 1 to n do
if deg(v) = 1 and v = root then
let u be the unique node in adj(v)
append u to the code and decrease its degree by 1
while deg(u) = 1 and u < v and v = root do
let z be the unique node in adj(u)
append z to the code and decrease its degree by 1
u←z
The idea is to consider all nodes in increasing order (variable v in the
algorithm), once a leaf is encountered it is selected for removal. Each removal
may let at most one node becomes a leaf (variable u). If u becomes a leaf
and has a label smaller than v, it will certainly be the smallest leaf, than it is
selected for removal. Removing u may let another node becomes a leaf, thus
implying a cascading effect: the inner while loop ensures that this problem
is handled correctly.
The algorithm terminates when the codeword reaches the desired length
(n − 1 for rooted trees). In order to achieve O(n) running time the explicit
removal of nodes from the tree is avoided. We simply decrease the degree of
a node each time a leaf adjacent to it is selected for removal. This allows us
to avoid expensive changes in adjacency lists. In this case line 3 requires a
scan of the adjacency list of v to identify the unique node not yet removed
(removed nodes can be marked with a flag). Each adjacency list is scanned
at most once, then the overall running time is linear.
Concerning decoding, a similar idea can be exploited to reconstruct a tree
from a codeword. The linear time decoding algorithm due to Klingsberg has
been explicitly published in [40]. We present here a slightly modified version.
Initially T is a graph with n = |C| + 2 nodes and no edges. A symbol n is
added at the end of the codeword to ensure that all n − 1 edges are correctly
24
CHAPTER 3. ALGORITHMS FOR PRÜFER-LIKE CODES
computed. We preliminarily mark all nodes that do not appear in C: these
are candidate leaves. The algorithm works as follows:
1.
2.
3.
4.
5.
6.
7.
8.
for each v = 1 to n − 1 do
if v is marked then
u = pop(C) and add edge (v, u) to T
if u no longer appears in C then mark u
while u is marked and u < v do
z = pop(C) and add edge (u, z) to T
if z no longer appears in C then mark z
u←z
The operation pop(C) extracts the first symbol from the codeword. To
test if a certain node no longer appears in C in O(1) time we can precompute
the last occurrence of each value i ∈ [1, n] in C with a simple right to left
scan of the codeword. This is enough to conclude that the running time of
this algorithm is O(n).
3.1.2
Second Neville Code
A trivial implementation of the Second Neville code described in Section 2.2.1
would require to sort a set of integer numbers at each step. Using integer
sorting algorithms this requires O(n2 ) running time. In [38] an encoding
algorithm that uses a set of sorted lists is presented. Due to the use of sorted
lists, the running time decreases to O(n log n). The first linear time algorithm
for Second Neville code has been presented in [74] and is analogous to the
one obtained with the unified encoding algorithm described in Section 3.2.
There were no optimal algorithms for decoding the Second Neville code
known in the literature before the one obtained through our unified decoding
algorithm described in Section 3.3.
3.1. KNOWN ALGORITHMS
3.1.3
25
Third Neville Code
A linear time algorithm for computing the Third Neville code is not difficult to obtain. Each step corresponding to the elimination of an internal
node does not imply any global computation on the tree (such as identify
a minimum or sort a set of nodes), thus each step requires constant time.
To efficiently identify the new smallest leaf, when required, it is enough to
precompute a list of all leaves of the initial tree in increasing order, this may
be done in linear time with any integer sorting algorithm [35]. Analogous
considerations hold for decoding.
The
the one
u < v:
become
1.
2.
3.
4.
5.
6.
7.
8.
9.
Third Neville code can be computed with an algorithm similar to
presented for Prüfer code in Section 3.1.1, by just omitting the test
this guarantees that internal nodes are removed as soon as they
leaves.
for each node v = 1 to n do
if deg(v) = 1 and v = root then
let u be the unique node in adj(v)
append u to the code and decrease its degree by 1
while deg(u) = 1 and v = root do
let z be the unique node in adj(u)
append z to the code and decrease its degree by 1
deg(u) = 0
u←z
Line 8 avoids that a node u already used verifies the test in line 2 and
contributes to the codeword again.
The decoding algorithm can be obtained similarly from the Prüfer decoding algorithm by removing the test u < v. Also in this case, this implies
that labels appearing in the codeword are used as soon as they are marked
as candidate leaves. It is as follows:
26
1.
2.
3.
4.
5.
6.
7.
8.
9.
CHAPTER 3. ALGORITHMS FOR PRÜFER-LIKE CODES
for each v = 1 to n do
if v is marked then
u = pop(C) and add edge (v, u) in T
if u no longer appears in C then mark u
while u is marked do
z = pop(C) and add edge (u, z) in T
if z no longer appears in C then mark z
unmark u
u←z
3.1.4
Stack-Queue Code
As mentioned in Section 2.3 Deo and Micikevičius, in their original presentation of the Stack-Queue code, provided linear time algorithms. The encoding
algorithm uses a FIFO queue Q, while the decoding algorithm uses a LIFO
stack S.
The encoding algorithm is the following:
1.
2.
3.
4.
5.
6.
7.
for each node v = 1 to n except the root do
if deg(v) = 1 then enqueue(v, Q)
while Q is not empty do
v ← dequeue(Q)
let u be the parent of v
append u to the code and decrease its degree by 1
if deg(u) = 1 and u is not the root then enqueue(u, Q)
enqueue(v, Q) adds value v to the tail of queue Q, while dequeue(Q)
extracts a value from the head of Q.
The decoding algorithm is the following:
for each value v = 1 to n do
2.
used[i] = false
1.
3.2. A UNIFIED ENCODING ALGORITHM
3.
4.
5.
6.
7.
8.
9.
10.
27
for i = n − 1 to 1 do
if not used[C[i]] then
push(i, S)
used[C[i]] = true
push all unused values in S in increasing order
for i = 1 to n − 1 do
v ← pop(S)
add edge (C[i], v) in T
push(i, S) inserts value i into the to of stack S, while pop(S) extracts an
value from the top of S.
Both these algorithms require linear time [39].
3.2
A Unified Encoding Algorithm
As we said, sequential and parallel encoding and decoding algorithms have
been presented in the literature [27, 38, 39, 53, 54, 104], but all of them
strongly depend on the properties of the code which has to be computed and
thus are very different from each other.
In this section we show a unified approach that works for all Prüfer-like
codes introduced so far. Through this unified approach we obtain linear
time coding and decoding sequential algorithms. Moreover this approach
can be easily exploited to obtain parallel algorithms: in Section 3.4 we will
show how to do this for the EREW PRAM parallel model. Namely, we
associate each tree node with a pair of integer numbers according to criteria
dependent on the specific code. Then we sort nodes using such pairs as
keys; the lexicographic order is obtained with integer (radix) sorting [35].
The obtained ordering corresponds to the order in which nodes are deleted
from the tree and can thus be used to compute the code. We remark that
in [74] the idea of sorting pairs has been used to obtain an ad-hoc linear time
algorithm for the Second Neville code. In the rest of this section we show
how different pair choices yield Prüfer, Neville, and Deo and Micikevičius
codes, respectively.
28
CHAPTER 3. ALGORITHMS FOR PRÜFER-LIKE CODES
Code
Pair ( xv , yv )
Prüfer
( µ(v), d(µ(v), v) )
Second Neville
( l(v), v )
Third Neville
( λ(v), d(λ(v), v) )
Stack-Queue
( l(v), γ(v) )
Table 3.1: Pair (xv , yv ) associated to node v for different codes.
3.2.1
Coding by Sorting Pairs
Let T be a rooted Cayley tree with n nodes, and let u and v be any two
nodes of tree T . Let us call:
• d(u, v): distance between two nodes u and v in T , d(u, u) = 0;
• l(v): the (bottom-up) level of node v, i.e., the maximum distance of v
from a leaf in Tv ;
• µ(v): the maximum label among all nodes in Tv ;
• λ(v): the maximum label among all leaves in Tv ;
• γ(v): the maximum label among the leaves in Tv that have maximum
distance from v;
• (xv , yv ): a pair associated to node v according to the specific code as
shown in Table 3.1;
• P : the set of pairs (xv , yv ) for each v in T .
The following lemma establishes a correspondence between the set P of
pairs and the order in which nodes are deleted from the tree.
Lemma 3.1. For each code, the lexicographic ordering of the pairs (xv , yv )
in set P corresponds to the order in which nodes are deleted from tree T
according to the code definition.
3.2. A UNIFIED ENCODING ALGORITHM
29
Proof. We discuss each code separately:
Prüfer code: notice that before a node v can be selected as a leaf the entire
subtree Tv should have been deleted. Furthermore, according to the
definition of Prüfer code, when the node µ(v) is chosen for deletion, Tv
consists of a chain from µ(v) to v. All the nodes in such a chain have
label smaller than µ(v) and thus will be chosen in the steps immediately
following the deletion of µ(v). The tree is therefore partitioned into
chains containing nodes with the same µ value and the rank of each
node v in the chain is d(v, µ(v)). Prüfer code deletes all the chains, in
increasing order, with respect to µ(v).
Second Neville code: the code deletes at each iteration all the leaves of
T , and thus nodes are deleted starting from smaller to higher levels.
Nodes within the same level are deleted in increasing label order. Hence
the pair choice.
Third Neville code: it is sufficient to use the definition of pending chain
given in Section 2.2.2 and to observe that, for each node v, λ(v) is the
head of the unique pending chain containing v.
Stack-Queue code: similarly to the Second Neville code, this code deletes
nodes from smaller to higher levels. As proved in [37], nodes within
the same level ℓ are deleted in increasing order of their γ values. The
proof given by Deo and Micikevičius is by induction on ℓ.
Nodes within level 0 (i.e., the leaves of T ) are such that γ(v) = v and
are deleted by increasing label order. Let u and v be two arbitrary
nodes at level ℓ. According to the code definition, the order in which
u and v become leaves is strictly related to the deletion order of nodes
at level ℓ − 1. Let u′ and v ′ be the last deleted nodes of Tu and Tv
respectively. It is easy to see that l(u′ ) = l(v ′ ) = ℓ − 1. Furthermore,
by definition of γ, it holds γ(u′) = γ(u) and γ(v ′ ) = γ(v). Since by
inductive hypothesis u′ is deleted before v ′ if and only if γ(u′ ) < γ(v ′ ),
the same holds for nodes u and v.
30
CHAPTER 3. ALGORITHMS FOR PRÜFER-LIKE CODES
Prüfer code
Pairs: (3,0) (4,0) (5,0) (6,0) (8,0) (8,1) (8,2) (9,0) (9,1)
Nodes:
3
4
5
6
8
1
2
9
7
Code:
6
10
6
7
1
2
7
7
10
Second Neville code
Pairs: (0,3) (0,4) (0,5) (0,8) (0,9) (1,1) (1,6) (1,10) (2,2)
Nodes:
3
4
5
8
9
1
6
10
2
Code:
6
10
6
1
7
2
7
7
7
Third Neville code
Pairs: (3,0) (4,0) (4,1) (5,0) (5,1) (8,0) (8,1) (8,2) (8,3)
Nodes:
3
4
10
5
6
8
1
2
7
Code:
6
10
7
6
7
1
2
7
9
Stack-Queue code
Pairs: (0,3) (0,4) (0,5) (0,8) (0,9) (1,4) (1,5) (1,8) (2,9)
Nodes:
3
4
5
8
9
10
6
1
7
Code:
6
10
6
1
7
7
7
2
2
Figure 3.1: Examples of encoding Prüfer-like codes using pairs specified in Table 3.1. The pairs sorted in increasing order, the node corresponding to each pair,
and the resulting codeword are shown for each code. Bold edges in the trees related
to Prüfer code and Third Neville code indicate chains and pending chains, respectively; dashed lines in the trees related to Second Neville code and Stack-Queue
code separate nodes at different levels.
3.2. A UNIFIED ENCODING ALGORITHM
31
In Figure 3.1 the pairs relative to the four codes are presented. Bold
edges in the trees related to Prüfer code and Third Neville code indicate
chains and pending chains, respectively; dashed lines in the trees related to
Second Neville code and Stack-Queue code separate nodes at different levels.
In each figure the resulting codeword, the pairs sorted in increasing order,
and the node corresponding to each pair are also shown.
3.2.2
Sequential Algorithm
Our sequential coding algorithm works on rooted trees and hinges upon the
pairs defined in Section 3.2.1:
UNIFIED ENCODING ALGORITHM
1.
2.
3.
4.
5.
6.
for each node v do
compute the pair (xv , yv ) according to Table 3.1
sort the tree nodes according to pairs (xv , yv )
for i = 1 to n − 1 do
let v be the i-th node in the ordering
append parent(v) to the code
Theorem 3.2. The unified encoding algorithm correctly computes
Prüfer code, Second Neville code, Third Neville code, and Stack-Queue code
in O(n) running time.
Proof. The correctness of the unified encoding algorithm follows from
Lemma 3.1. For all codes the information used in pairs can be easily computed in O(n) time using a post-order traversal of the tree. To implement lin
2 notice that it is easy to sort the pairs (xv , yv ) used in the encoding scheme.
Indeed, independently by the specific code, each element in such pairs is in
the range [1, n]. A radix-sort like approach [35] is thus sufficient to sort them
according to yv first and xv later, exploiting a stable integer sorting (e.g.,
counting sort [35]). Then the overall running time is O(n).
32
CHAPTER 3. ALGORITHMS FOR PRÜFER-LIKE CODES
We remark that the unified encoding algorithm works on rooted
trees and generates codewords of length n − 1. According to Section 2.1.2 it
can be exploited to encode unrooted trees by simply rooting them in a fixed
node and omitting the last symbol of the codeword.
3.3
A Unified Decoding Algorithm
In this section we present a unified sequential algorithm for decoding Prüferlike codes, i.e., for building the tree T corresponding to a given codeword
C. As seen above, to reconstruct T , it is sufficient to compute the ordered
sequence of the removed leaves, let us call it S. For each i ∈ [1, n − 1], the
pair (C[i], S[i]) will thus be an edge in the tree (C[i] and S[i] represent the
i-th element in C and S respectively). The decoding scheme is based on the
computation of the rightmost occurrence of each value in the codeword.
3.3.1
Decoding by Rightmost Occurrence
Recall that leaves of T are exactly those nodes that do not appear in the
codeword and each internal node, say v, in general may appear in C more
than once; each appearance corresponds to the deletion of one of its children,
and therefore implies that the degree of v decreases by 1. After the rightmost
occurrence in the code, v is clearly a leaf and thus becomes a candidate for
being deleted. This implies that v should appear in S after its rightmost
occurrence. More formally:
∀ v = r,
∃ unique j > rm(v, C) such that S[j] = v
where r is the root of the tree (i.e., the last element in C) and rm(v, C)
denotes the index of the rightmost occurrence of node v in C. We assume
that rm(v, C) = 0 if v does not appear in C. It is easy to compute the
rightmost occurrence of each node with a simple right to left scan of C: this
requires O(n) time.
33
3.3. A UNIFIED DECODING ALGORITHM
Code
test(v)
position(v)
Prüfer
rm(v, C) > prev(v, C)
rm(v, C) + 1
Third Neville
rm(v, C) > 0
rm(v, C) + 1
Stack-Queue
rm(v, C) > 0
|leaves(T )| + σ(rm(v, C))
Table 3.2: Condition on node v that is checked in the unified decoding algorithm and position of v as a function of rm(v, C).
3.3.2
Sequential Algorithm
We now describe a decoding algorithm for Prüfer code, Third Neville code,
and Stack-Queue code that is based on the rightmost occurrences. Differently
from the other codes, for the Second Neville code the rightmost occurrence
of each node in C gives only partial information about sequence S. Thus, we
will discuss this code separately in Section 3.3.3.
We need the following notation. For each i ∈ [1, n − 1], let ρ(i) be 1 if i
is the rightmost occurrence of value C[i], and 0 otherwise. Let σ(i) be the
number of internal nodes whose rightmost occurrence is at most i, i.e.,
σ(i) =
ρ(j)
(3.1)
j≤i
Similarly to [104], let prev(v, C) denote the number of nodes with label
smaller than v that become leaves before v, i.e.,
prev(v, C) = |{u : u < v and rm(u, C) < rm(v, C)}|
The following lemma shows, for each code, how the position of a node
in the sequence S that we want to reconstruct can be expressed in terms of
rightmost occurrence of nodes.
Lemma 3.3. Let C be a codeword of n − 1 integers in [1, n]. Let test and
position be defined as in Table 3.2 for Prüfer code, Third Neville code, and
Stack-Queue code. Let S be the sequence of leaves deleted from the tree while
34
CHAPTER 3. ALGORITHMS FOR PRÜFER-LIKE CODES
building the codeword. The proper position in S of any node v that satisfies
test(v) is given by position(v).
Proof. We discuss each code separately, starting from the simplest one.
Third Neville code: each internal node v is deleted as soon as it becomes
a leaf. Thus, the position of v in sequence S is exactly rm(v, C) + 1.
Prüfer code: differently from the Third Neville code, in Prüfer code an
internal node v is deleted as soon as it becomes a leaf if and only
if there is no leaf with label smaller than v. In order to test this
condition we use information given by prev(v, C): the position of v in
S is rm(v, C) + 1 if and only if rm(v, C) ≥ prev(v, C).
Stack-Queue code: by the definition of this code, all the leaves of T , sorted
by increasing labels, are at the beginning of sequence S. Then, all the
internal nodes appear in the order in which they become leaves, i.e.,
sorted by increasing rightmost. Thus, the position of an internal node
v is given by |leaves(T )| + σ(rm(v, C)).
We remark that some entries of S may be still empty after positioning nodes
according to Lemma 3.3. The definitions of the various codes imply that
all the nodes not positioned by Lemma 3.3, except for the root, should be
assigned to the empty entries of S in increasing label order. In particular,
for Third Neville code and Stack-Queue code, only the leaves of T are not
positioned and, in the case of Stack-Queue code, all of them will appear at
the beginning of S. We are now ready to describe our unified decoding
algorithm:
UNIFIED DECODING ALGORITHM
1.
2.
3.
4.
5.
for each node v do
compute rm(v, C)
for each node v except for the root do
if test(v) = true then S[position(v)] ← v
let L be the ordered list of unused (non-root) nodes in S
3.3. A UNIFIED DECODING ALGORITHM
35
Figure 3.2: An example of execution of the unified decoding algorithm in
the case of Prüfer code: content of the main data structures and tree returned as
output.
let P be the list of empty positions in S
7. for each i = 1 to |L| do
8.
S[P [i]] ← L[i]
6.
where test(v) and position(v) are specified in Table 3.2. An example of
execution of the unified decoding algorithm in the case of Prüfer code
is shown in Figure 3.2.
For Third Neville and Stack-Queue code the unified decoding algorithm requires linear time, while a straightforward implementation for
Prüfer code would require O(n log n) time due to the computation of prev.
This can be reduced to O(n) time by adapting the unified decoding algorithm in such a way that the prev computation can be avoided. Namely,
lines 2–3 can be omitted (considering the test(v) as false for each node v),
and lines 6–7 can be replaced as follows:
for each i = 1 to |L| do
7.
position ← max{f irst empty pos(S), rm(L[i], C) + 1}
8.
S[position] ← L[i]
6.
where f irst empty pos(S) returns the smallest empty position in S. In this
implementation, nodes are considered in increasing label order: node v is
assigned to position rm(v) + 1 of S if this position is still empty, and to the
36
CHAPTER 3. ALGORITHMS FOR PRÜFER-LIKE CODES
leftmost empty position otherwise. In order to see that this is equivalent to
the unified decoding algorithm, observe that nodes for which rm(v) >
prev(v) (see the test in line 3) will always find the position rm(v) + 1 empty,
due to the definition of prev. Hence, they will be inserted in S exactly as in
line 3 of the unified decoding algorithm.
The performances of the unified decoding algorithm are summarized by the following theorem.
Theorem 3.4. The unified decoding algorithm computes the tree corresponding to a codeword C in O(n) sequential time.
3.3.3
Second Neville Code
As we said, for this code the rightmost occurrence of each node in the codeword C gives only partial information about sequence S. Here we show how
to efficiently extract from the codeword enough information to correctly decode C according with the Second Neville code. We remark that the problem
of finding an optimal sequential decoding algorithm for this code was open,
and our work close it.
We first observe that if all nodes were assigned with a level, sort nodes
according to pairs (l(v), v), as done by the encoding algorithm, would produce
the sequence S (see Section 3.2.1). We now show how to compute l(v) from
C.
Let x be the number of leaves of T : these nodes have both level and rm
equal to 0. Consider the first x elements of code C, say C[1], C[2], . . . , C[x].
For each i, 1 ≤ i ≤ x, such that i is the rightmost occurrence of C[i], we
know that node C[i] has level 1. The same reasoning can be applied to get
level-2 nodes from level-1 nodes, and so on. With respect to the running
time, a sequential scan of code C is sufficient to compute the level of each
node in linear time.
3.4. UNIFIED PARALLEL ALGORITHMS
3.4
37
Unified Parallel Algorithms
In this section we present a parallel version of the unified encoding algorithm proposed in Section 3.2 and of the unified decoding algorithm
proposed in Section 3.3. Our algorithms are described for the classical EREW
PRAM model and costs are expressed as the number of elementary operations
needed to perform a task.
We chosen the PRAM theoretical model because we do not need to address any specific hardware. In the last decade, PRAM model has been
deemed useless by many researchers because it is too abstract compared
with actual parallel architectures. It is worth noticing that this trend is
changing. At SPAA’07, Vishkin and Wen reported about the recent advancements achieved at the University of Meryland within the project PRAM-OnChip [105]. The XMT (eXplicit Multi-Threading) general-purpose computer
architecture is a promising parallel algorithmic architecture to implement
PRAM algorithms. They also developed a single-instruction multiple-data
(SIMD) multi-thread extension of C language with the intent of provide an
easy programing tool to implement PRAM algorithms. I has primitives like:
Prefix Sum, Join, Fetch and Increment, etc.
An optimal PRAM algorithm for encoding Prüfer codes, which improves
over a previous result due to Greenlaw and Petreschi [54], is given in [53].
A few simple changes make the algorithm works also for the third Neville
code. In [27] non optimal encoding algorithm for Prüfer has been presented,
it makes use of the idea of sorting pairs but requires O(n log n) operations.
Efficient, but not optimal, parallel encoding algorithms for the Second Neville
code and the Stack-Queue code have been presented in [37]. Our unified
√
algorithm either matches or improves by a factor O( log n) the performances
of the best ad-hoc approaches known so far.
Concerning decoding, Wang, Chen, and Liu [104] propose an O(log n)
time decoding algorithm for Prüfer code using O(n) processors on an EREW
PRAM. To the best of our knowledge, parallel decoding algorithms for the
other Prüfer-like codes were not known in the literature until our work.
Namely, we designed the first parallel decoding algorithm for Second Neville
38
CHAPTER 3. ALGORITHMS FOR PRÜFER-LIKE CODES
Code
Encoding
known
Prüfer
Second Neville
Third Neville
Stack-Queue
our result
Decoding
known
our result
O(n)[53]
O(n)
O(n log n)[104] O(n log n)
√
√
O(n log n)[37] O(n log n)
open
O(n log n)
√
O(n)[37, 53]
O(n)
open
O(n log n)
√
√
O(n log n)[37] O(n log n)
open
O(n log n)
Table 3.3: Summary of our results on the EREW PRAM model. Costs are
expressed in terms of number of operations.
code, Third Neville code, and Stack-Queue code: our unified algorithm works
√
on a n-processors EREW PRAM in O(log n) time with cost O(n log n). For
Prüfer code, the cost of our algorithm is O(n log n) and matches the best previous result [104]. Our parallel results both for encoding and for decoding
are summarized in Table 3.3.
3.4.1
Encoding
Before showing how to parallelize each step of the unified encoding algorithm, we want to remark that if the tree is unrooted, the Euler tour
technique makes it possible to root it in O(log n) time with cost O(n) [59].
We now discuss how to compute all information that constitutes the pair
components as described in Section 3.2.
Lemma 3.5. The pairs given in Table 3.1 can be computed on the EREW
PRAM model in O(log n) time with cost O(n).
Proof. We discuss separately the components of each pair.
µ(v): the maximum node in each subtree can be computed in O(log n) time
with cost O(n) using the Rake technique [59] as done in [54]. In order
to avoid concurrent reading during the Rake operation, the tree T must
3.4. UNIFIED PARALLEL ALGORITHMS
39
be preliminarily transformed into a binary tree TR as follows: for each
node v with k > 2 children, v is replaced by a complete binary tree
of height ⌈log k⌉ having v as root and v’s children as the k leftmost
leaves. This transformation can be also done in O(log n) time with
cost O(n) [53].
d(µ(v), v): we partition T into chains by marking each node v with the value
µ(v) and by deleting edges between nodes with different µ values. Now,
the rank of node v in its chain is exactly d(µ(v), v). In order to compute
the chains, each node links itself to its parent if µ(v) = µ(parent(v)). A
List Ranking then gives the position of each node in its chain in O(log n)
time with cost O(n) [59]. The use of the binary tree TR guarantees that
no concurrent read is necessary for accessing µ(parent(v)).
l(v): an Euler tour gives the distance d(v, r) of each node v from the root
r of tree T . Then, l(v) = d(f, r) − d(v, r), where f is a leaf of Tv at
maximum distance from r: f can be easily computed using the Rake
technique [59].
λ(v): the same techniques used for computing µ(v) can be adapted to obtain
the maximum leaf of each subtree with the same performances.
d(λ(v), v): analogous considerations as for computing d(µ(v), v) hold.
γ(v): given the distance of each node from the root, γ(v) is the node u ∈ Tv
such that (d(u, r), u) is maximum and can be computed with the Rake
technique.
The following theorem summarizes the performances of the unified encoding algorithm in a parallel setting.
Theorem 3.6. On the EREW PRAM model, the unified encoding algorithm computes Prüfer code and Third Neville code optimally (i.e., in
O(log n) time with cost O(n)) and Second Neville code and Stack-Queue code
√
in O(log n) time with cost O(n log n).
40
CHAPTER 3. ALGORITHMS FOR PRÜFER-LIKE CODES
Proof. By Lemma 3.5, line 1 of the unified encoding algorithm requires
O(log n) time with cost O(n). Line 3 can be trivially implemented in O(1)
time with cost O(n). The sorting in line 2 is thus the most expensive operation. Following a radix-sort like approach and using the stable integer-sorting
algorithm presented in [55] as a subroutine, line 2 would require O(log n) time
√
with cost O(n log n) on an EREW PRAM1 . This gives the stated running
time and cost for Second Neville code and Stack-Queue code. For Prüfer
code and Third Neville code we can further reduce the cost of our algorithm
to O(n) by using a more clever sorting procedure that benefits from the
partitioning of the tree into chains.
Let us consider Prüfer code first. As observed in [54], the final node
ordering can be obtained by sorting chains among each other and nodes
within each chain. In our framework, the chain ordering is given by the
value µ(v), and the position of each node within its chain by the distance
d(µ(v), v). Instead of using a black-box integer sorting procedure, we exploit
the fact that we can compute optimally the size of each chain, i.e., the number
of nodes with the same µ(v), by means of prefix sums. Another prefix sum
computation can then be used to obtain, for each chain head, the overall
number of nodes in the preceding chains, i.e., its final position. At last,
the position of the remaining nodes is univocally determined by summing
up the position of the chain head µ(v) with the value d(µ(v), v). Similar
considerations can be applied to the Third Neville code.
We remark that our algorithm solves within a unified framework the parallel encoding problem. With respect to Prüfer code and Third Neville code,
it matches the performances of the (optimal) algorithms known so far [37, 53].
With respect to Second Neville code and Stack-Queue code, it improves of
√
an O( log n) factor over the best approaches known in the literature [37].
1
The result on parallel integer sorting in [55] holds when the machine word length is
O(log n). Under the more restrictive hypothesis that the word length is O(log2 n), the
cost of sorting can be reduced to O(n), and so does the cost of our algorithm.
3.4. UNIFIED PARALLEL ALGORITHMS
3.4.2
41
Decoding
We now show how to parallelize the unified decoding algorithm; as
done in Section 3.3 we consider the Second Neville code separately.
The following lemma analyzes the rightmost computation in parallel.
Lemma 3.7. The rightmost occurrences of nodes in a codeword C of length
√
n − 1 can be computed in O(log n) time with cost O(n log n) on the EREW
PRAM model.
Proof. We reduce the rightmost occurrence computation to a pair sorting
problem: we sort in increasing order the pairs (C[i], i), for i ∈ [1, n − 1].
Indeed, in each sub-sequence of pairs with the same first element C[i], the
second element of the last pair is the index of the rightmost occurrence of
node C[i] in the code. Since each pair value is an integer in [1, n], we can
again use twice the stable integer-sorting algorithm described in [55]: this
√
requires O(log n) time and O(n log n) cost on an EREW PRAM. Then,
each processor pi in parallel compares the first element of the i-th pair in
the sorted sequence to the first element of the (i + 1)-th pair, determining
whether i corresponds to the end of a subsequence or not. This requires
additional O(1) time and linear cost with exclusive read and exclusive write
operations.
The performances of the unified decoding algorithm in a parallel
setting are described by the following theorem.
Theorem 3.8. The unified decoding algorithm computes the tree corresponding to a codeword C, on the EREW PRAM model, in O(log n) time
√
with cost O(n log n) for Prüfer code and O(n log n) for Third Neville code
and Stack-Queue code.
Proof. With respect to Prüfer code, the parallel version of the unified decoding algorithm yields essentially the same algorithm described in [104].
Its bottleneck is the prev computation that can be reduced to a dominance
counting problem and can be solved on the EREW PRAM in O(log n) time
42
CHAPTER 3. ALGORITHMS FOR PRÜFER-LIKE CODES
with cost O(n log n) [5, 32]: we refer to [53, 104] for a detailed analysis. For
the other codes, σ(i) (defined in Equation 3.1) can be computed for each i
using a prefix sum operation [59]. In order to compute list L in line 4, we can
mark each node not yet assigned to S and obtain its rank in L by computing
prefix sums. Similarly for list P in line 5. Hence, the most expensive step is
the rightmost computation, which requires integer sorting (Lemma 3.7).
Second Neville code
Unfortunately, the approach used in Section 3.3.3 is inherently sequential
and thus inefficient in parallel. We now discuss an alternative approach for
computing the level of each node v, from a codeword C. This approach can
be easily parallelized.
Lemma 3.9. Let C be the Second Neville code codeword for a tree T . The
level of each node in T can be computed from C on the EREW PRAM model
√
in O(log n) time with cost O(n log n).
Proof. Let T ′ be the tree obtained by decoding C with the Stack-Queue code:
although T and T ′ are different, the level of each node is the same both in
T and T ′ . Indeed, as shown in Table 3.1, for both Second Neville code and
Stack-Queue code the first element of the pair is xv = l(v).
Then, after T ′ is build using the unified decoding algorithm, we
compute node levels applying the Euler tour technique on it. We remark
that the Euler tour technique requires a particular data structure [59] that
can be built as described in [54]. The bottleneck of this procedure is sorting
pairs of integers in [1, n] and thus, once again, we can exploit the parallel
integer sorting presented in [55].
Given level information, the correct sequence S corresponding to tree T
can be easily obtained by sorting the pairs (l(v), v). We can summarize the
results concerning the Second Neville code as follows:
Theorem 3.10. The tree corresponding to a codeword C according to the
Second Neville code can be computed in O(n) sequential time and in O(log n)
√
time with cost O(n log n) on the EREW PRAM model.
3.5. CONCLUDING REMARKS
43
Proof. The correctness follows from the definition of the Second Neville code
and from Lemma 3.1. The running time is guaranteed by Lemma 3.9 and by
the bounds on integer sorting given in [55].
3.5
Concluding Remarks
In this chapter we have presented a unified approach for coding labeled
trees by means of strings of node labels and have applied it to four wellknown Prüfer-like codes due to Prüfer [90], Neville [79], and Deo and Micikevičius [39]. The encoding scheme hinges upon the definition of pairs
associated to the nodes of the tree according to criteria dependent on the
specific code: the coding problem is then reduced to the problem of sorting these pairs in lexicographic order. The decoding scheme is based on the
computation of the rightmost occurrence of each label in the code. In particular, we obtained the first linear time sequential decoding algorithm for
the Second Neville code.
We have also shown how it is possible to parallelize our unified encoding
and decoding algorithms. There where no decoding algorithms for the Second
Neville code, the Third Neville code, and Stack-Queue code before our work.
Concerning the encoding our results either improve or match the best results
known in the literature. Moreover, since integer sorting is the most expensive
operation in our parallel algorithms, any improvement on the computation
of integer sorting directly improves our results. The only exception is the
Prüfer decoding parallel algorithm: here the dominance counting problem
is the bottleneck. In [5] the lower bound Ω(n log n) has been shown for the
dominance counting problem in parallel. To the best of our knowledge, it is
an open question to understand if it is possible to overcome this bound when
the input is limited to n integer values in the range [1, n] or to avoid the prev
computation in the decoding algorithm for Prüfer code.
Algorithmic aspects related with encoding and decoding Prüfer-like codes
have been analyzed also in [38] where a different scheme relaying on lists has
been presented. Codes have been classified in function of used lists (FIFO,
44
CHAPTER 3. ALGORITHMS FOR PRÜFER-LIKE CODES
LIFO, or sorted lists), the ordering of initial leaves, and whether they require
a single list or multiple lists.
Chapter 4
Applications of Tree Encodings
Labeled trees are of interest in theoretical and practical areas of computer
science. They are used in a great variety of applications ranging from Phylogenetic Trees to data compression, from XML data representation and indexing to the computation of graph volumes. Tree encodings are used in
applications like Fault Dictionary Storage [12], Distributed Spanning Tree
Maintenance [48], etc.
In this chapter we focus on two applications of tree encodings: random
trees generation and Genetic Algorithms. The first application shows how
these combinatorial bijection can be fruitfully exploited to guarantee that
the trees are generated uniformly at random, both in sequential and parallel
settings. Genetic Algorithms provide a wider example of application of tree
encodings. In this context the choice of an appropriate representation for
trees is fundamental. Many experimental comparisons have been presented
in the literature in order to explore several possible tree encodings. Some of
these experiments driven our attention on certain bijective code not belonging
to the class of Prüfer-like code. These codes will be studied in Chapter 5.
This chapter is organized as follows: Section 4.1 describes the use of
Prüfer-like codes to generate trees uniformly at random, we also show experimental results proving that this method is competitive with other fast
methods to generate random trees. In Section 4.2 we give a brief introduction
to Genetic Algorithms and discuss known experimental results on trees.
45
46
4.1
CHAPTER 4. APPLICATIONS OF TREE ENCODINGS
Generating Random Trees
The problem of generating a random tree with certain properties is fundamental in Computer Science, especially in order to run experiments and
simulations. Its has been widely studied both in a sequential setting (see for
example [40, 70]) and in a parallel setting (see for example [36]). Roughly
speaking, this task can be performed in several ways: for example, by adding
random edges until the graph is completely connected, then breaking cycles
without disconnecting the graph. Another approach is the following: construct the tree by adding nodes at random, connecting them to nodes already
in the tree. Other methods are possible. Easy methods often require more
than O(n) time, while efficient ones may have the drawback that the random choice is not uniformly distributed among the set of all possible trees.
As shown by the following example, adding nodes at random, the star of
n nodes can be generated with a probability considerably higher than any
given n-nodes path.
Example 9. We want to generate random rooted Cayley trees by adding nodes
at random. At each step we select a random unused label v and a random
used one p an we add node v to the tree as a child of p. The first chosen
node will be the root. Now consider the probability that the star rooted at
node 1 is generated, call it S1 . At the first step we must chose label 1, the
probability that it happen is P r (1) [v = 1] = n1 . In the following steps we can
choose any label v but as p we have to choose always 1; the probability that
it happen depends on how many nodes are already been added to the tree.
At the second step P r (2) [p = 1] (the probability that we choose p = 1) is 1,
at the third step P r (3) [p = 1] = 12 , at the fourth step P r (4) [p = 1] = 31 , an so
1
. The overall probability
on. In general at step i we have P r (i) [p = 1] = i−1
for S1 to be generated is:
n
1
1 1
=
P r[S1 ] =
n i=2 i − 1
n!
Let us now compute the probability that a given path P is generated; w.l.o.g.
we assume that P is rooted at node 1 and the node sequence is 1, 2, 3, . . . , n
(any other path has the same probability). At the first step we have to choose
4.1. GENERATING RANDOM TREES
47
v = 1 as root: P r (1) [v = 1] = n1 . At the second step we must choose v = 2
1
and p = 1: P r (2) [v = 2] = n−1
and P r (2) [p = 1] = 1. At the third step we
1
and P r (3) [p = 2] = 21 . In
must choose v = 3 and p = 1: P r (3) [v = 3] = n−2
1
1
general at step i we have P r (i) [v = i] = n−i+1
and P r (i) [p = i − 1] = i−1
.
The overall probability for given path P to be generated is:
n
n
1
1
1
1
1
=
P r[P ] =
n i=2 n − i + 1 i=2 i − 1
n! (n − 1)!
Both S1 and P are Cayley trees in Rn but P r[S1] is considerably higher than
P r[P ].
Thus generate Cayley trees by adding nodes at random does not guarantee
uniform distribution (even for trees that are not stars nor paths).
Generating a random codeword of n − 1 integers in the range [1, n] and
applying a decoding algorithm is an easy and fast way to generate a random
tree. It also guarantees that, if each integer is chosen uniformly at random in
[1, n], each rooted Cayley tree will have the same probability to be generated.
Moreover, we have experimentally verified that this method is competitive,
in terms of running time, with the one based on random leaves addition.
4.1.1
Experimental Comparison
In order to verify if the generation of random trees based on Prüfer-like code
has good performances, we compared it with an effective implementation of
the add-leaves-at-random idea used in Example 9. In order to select, at each
step, a random leaf to add we precompute a permutation of [1, n]. It is well
known that the following algorithm ensures that the permutation is chosen
uniformly at random:
for i = 1 to n do
2.
perm[i] = i
3. for i = 1 to n do
4.
swap perm[i] and perm[Random(i,n)]
1.
48
CHAPTER 4. APPLICATIONS OF TREE ENCODINGS
Figure 4.1: Experimental comparison of Random Tree Generation algorithms,
from 1 to 10 million nodes. Y-axis report the time required to generate a single
tree in milliseconds. Decoding a random codeword with Third Neville code is 30%
faster than the Add Leaves based method.
The function Random(i,n) returns a random value in [i, n]. Then nodes are
added to the tree in the order given by vector perm. Each node v = perm[i]
is attached to one of those already added to the tree, i.e., all nodes between
perm[1] and perm[i − 1].
T = ([1, n], ∅)
2. for i = 1 to n do
3.
add the edge (perm[i], perm[Random(1,i − 1)])
1.
This algorithm has been compared against the decoding of a random
codeword. We choose the Third Neville code implementing of the unified
decoding algorithm presented in Section 3.3.
Both algorithms have been implemented in standard ansi C (C99 revised standard), compiled with gcc (version 3.3.5) with optimization flag O3.
4.1. GENERATING RANDOM TREES
49
Random values have been produced by the rand() pseudo-random source
of numbers provided by the ANSI C standard library. We used only odd
seeds to initialize the random generators and we randomly generated the
sequence of seeds used in each test starting from a base seed. Trees are implemented through adjacency lists. Our experiments have been carried out
on a workstation equipped with two Dual Core Opteron processors with 2.2
GHz clock rate, 6 GB RAM, 1 MB L2 cache, and 64 KB L1 data/instruction
cache. The workstation runs Linux Debian (kernel 2.6.8). The running time
of each experiment has been measured by means of the standard system call
getrusage().
Trees from 1 million to 10 million nodes have been generated. Results
(reported in Figure 4.1) clearly show that decoding a random codeword with
Third Neville code is 30% faster than the Add Leaves based method. Thus
we conclude that using tree encodings is definitely the best way to generate
random trees.
4.1.2
Constrained Random Trees
Using Prüfer-like codes certain topological properties of a tree are explicit in
its codeword. This it is possible to impose constrains on the random trees
generated maintaining linear running time.
Let C be the codeword for a tree T obtained with any Prüfer-like code:
since each node appears in C a number of time equal to the number of its
children, information concerning degrees, leaves, and root are explicit in C
(see Property 2.4). Thus we can state that:
• to generate a tree rooted in a desired node r it is enough to ensure that
C[n − 1] = r;
• to let a certain node v be a leaf, it does not have to appear in C at all;
• if the set of leaves must be {v1 , v2 , . . . , vk }, all nodes but these must
appear in C at least once;
50
CHAPTER 4. APPLICATIONS OF TREE ENCODINGS
• to guarantee that a node v gets degree d in T , it must appear exactly
d − 1 times in C (d times if v is the root).
All these observations can be combined together to generate random trees
satisfying several constraints. This same reasoning can be exploited to generate unrooted trees by decoding n − 2 length random codewords.
4.1.3
Parallel Random Trees Generation
Generating random trees in a parallel setting is an hard task. Add nodes
at random is an inherently sequential method, a straightforward use of this
idea would result in a misuse of the underling parallel architecture. On
the other hand, if each processor attaches a node to some other random
node, disregarding other processors activity, there is no guarantee that the
resulting graph is connected and acyclic; repair such a graph to obtain a tree
is expensive.
The idea of using bijective code to generate random tree is the easiest
one. Indeed in order to generate a codeword each processors can choose
a random number independently, then the tree is obtained directly with a
parallel decoding algorithm (see Section 3.4). This idea has been exploited
in [36] where a modified Prüfer code was used to obtain an architecturespecific (8192-processors MAS-Par MP-1) almost-constant-time algorithm to
generate random trees. This result hinges upon the fact that, when n <
8192, they have an almost-constant-time integer sorting algorithm for MASPar MP-1. The algorithms shown in Section 3.4 provides an architectureindependent solution for this problem. Random trees can be generated taking
advantage from the best integer sorting algorithm available on each specific
architecture.
4.2
Genetic Algorithms
Genetic Algorithms (GAs) are search heuristics that hinge upon the evolutionary ideas of natural selection and genetic. The basic concept of GAs is
4.2. GENETIC ALGORITHMS
51
to simulate natural processes necessary for evolution, specifically those that
follow the principles of survival of the fittest. They represent an intelligent
exploitation of a random search within a defined search space to solve a problem. We will focus on GA whose search space is the set of trees, e.g., GA for
finding the Minimum Spanning Tree of a graph with additional constrains:
minimum diameter, fixed number of leaves, bounded maximum degree, etc.
(see for example [41, 106].)
A GA starts with a population of a certain number of random candidate
solution, called individuals, that in our case are simply random trees. Each
individual is represented by its chromosome: a code (usually a string) that
identifies the individual. Individual are compared according to a fitness function and a set of good ones is selected. The fitness function strictly depends
on the problem we are dealing with, for example if we are looking for MST
with minimum diameter meaning full fitness functions should assign higher
fitness to trees with smaller diameter. There are many criteria to determine
how to choose good individuals given their fitness, a deep discussion of them
is outside the scope of this thesis (we refer the interested reader to [75, 92]).
We just mention that the easiest criterion is to determine whether to keep
or discard an individual at random using a probability proportional to its
fitness.
Then selected individuals are used to produce offsprings via genetic operators, in this way a new generation of individual is obtained. Several genetic
operators have been introduced in the literature along the years, the most
used are crossover and mutation. In a crossover, two chromosomes are mixed
together (according to several possible criteria) to obtain a new chromosome,
the underlying idea is that the offspring individual should inherit parent’s peculiarity and then it has chances to be better than both. In a mutation, the
chromosome of a single individual is slightly changed (at random), this may
or may not improve its fitness.
The GA repeats this selection/reproduction scheme until one among many
possible stop-criteria is reached:
52
CHAPTER 4. APPLICATIONS OF TREE ENCODINGS
• a solution “good enough” is generated;
• the improvement in the fitness of the best individual is negligible with
respect to the previous generations;
• the number of generations has reached a given bound;
• the computational time has reached a given bound.
An effective individual representation and meaningful fitness evaluation
are the keys of the success in GAs. We refer the interested reader to [75, 92]
for a more detailed description of the fundaments of GAs.
While the fitness function strictly depends on the specific problem we are
trying to solve, the individual representation by means of chromosome strings
only depends on the solution space. There exist several tree representations
suitable for GA, but not all of them achieve good results. There are certain
desirable properties for a code in GA. It should:
be injective: it should be able to represent each tree with a different codeword;
be unbiased: each tree should be represented by the same number of codewords;
be surjective: every codeword should represent a tree;
have high locality: small changes in the tree should correspond to small
changes in the codeword, and vice versa;
have high heritability: when a codeword is obtained by mixing two codewords (ancestors) each edge of the offspring tree should belong to either
of the ancestor trees;
be efficient: the encoding and decoding, should require small running time,
in order to efficiently compute the fitness.
Among the others [82, 96] Prüfer-like code look like an appealing choice
because they are bijective and linear time encoding and decoding algorithms
4.2. GENETIC ALGORITHMS
53
Figure 4.2: a) A tree T , and corresponding Prüfer code and naı̈ve code. b) T ′
obtained form T changing edge (2, 1) in (2, 7), together with corresponding Prüfer
code and naı̈ve code.
are known. Unfortunately it has been experimentally observed that Prüferlike codes preform poorly in GA because they have poor locality and heritability [51]. In all Prüfer-like codes the tree topology determines the elimination order of nodes, so a small change in the tree may cause a variation
of this order and thus a big change in the string (see Figure 4.2a and 4.2b).
This is the reason why Prüfer and Prüfer-like codes exhibit low locality and
heritability [51].
In order to better understand how a code can exhibit high locality and
heritability we now consider the naı̈ve code. This code represents a rooted
tree simply listing the parent of each node (see Figure 4.2a). Each edge
(v, p(v)) of a tree corresponds to the v-th element of the codeword, thus this
code has maximal locality: a single change in the tree corresponds to a single
change in its codeword, and vice versa (see 4.2b). Naı̈ve code also has high
heritability. Consider two trees T1 and T2 and their codewords C1 and C2 .
Let C be a string obtained by mixing C1 and C2 with a crossover and T the
corresponding tree. Since, for each i, either C[i] = C1 [i] or C[i] = C2 [i] we
deduce that each edge (v, p(v)) in T either comes from T1 or from T2 . Notice
that T1 and T2 must have the same root in order to avoid that C contains 0
or 2 entries identifying a root, for this purpose it is enough to re-root T2 in
the root of T1 (or vice versa) before the crossover.
Unfortunately, naı̈ve code is not bijective, certain codewords may rep-
54
CHAPTER 4. APPLICATIONS OF TREE ENCODINGS
resent graphs not necessarily connected or containing cycles or loops. This
implies that codewords obtained by crossover or mutation are not necessarily trees: more precisely, the probability of obtaining a tree is n1 . This is a
serious shortcoming for naı̈ve code to be used in GAs.
In [61], an experimental analysis shows that locality and heritability properties are satisfied by Blob code much better than by the Prüfer code. The
Blob code is a bijective code introduced by Picciotto [89]. Like Prüfer-like
codes, all of them are bijection between Cayley trees and codewords but they
do not belong to the class of Prüfer-like codes because they are not base on
recursive leave elimination. Blob code has also been shown to be competitive
against other well known tree representations in GAs [61, 62, 91].
Unfortunately these promising experimental results do not provide any
insight on the underlying reasons that make the Blob code better than the
Prüfer code in this field. For this reason we decide to study the three codes
presented by Picciotto: Blob code, Happy code, and Dandelion code. In the
next chapter, a deep discussion of these code is presented. Here it is enough
to say that our study clarified the reasons why Blob code has good locality
and heritability. Moreover, interpreting the three codes as transformation of
trees into functional digraphs, we pointed out that the Dandelion code approximate the desirable properties held by the naı̈ve code much better than
the Blob code. So, in a paper published in 2005 [25], we conjectured that
in GA Dandelion code (and a modified version of the Happy code we introduced) should outperform Blob code. This assertion has been experimentally
verified in 2006 by Paulden and Smith [84, 85, 101]: moreover the proved that
our Modified Happy code has similar locality properties and slightly better
heritability properties to the Dandelion code [85].
4.3
Concluding Remarks
In this chapter we have seen two among many possible applications of tree
encoding. We have shown that they lead to efficient and unbiased algorithms
to generate random trees, both in sequential and parallel settings.
4.3. CONCLUDING REMARKS
55
In the field of Genetic Algorithms, where the choice of a good encoding
play a crucial role, experimental analysis shown that Prüfer-like codes are
outperformed by other bijective codes. In order to understand the underlying
reasons behind these results we decided to better investigate other codes.
In the next chapter a deep discission about these codes is presented. Our
study brought us to the definition of a general encoding and decoding scheme
based on the transformation of a tree into a functional digraph. This makes
it possible for us to obtain linear time algorithm for encoding and decoding
all Picciotto’s codes.
56
CHAPTER 4. APPLICATIONS OF TREE ENCODINGS
Chapter 5
Transformation Codes
In Chapters 2 we have shown how Prüfer-like codes can be encoded and
decoded in optimal linear time. In Chapters 4 however we reported that
they lack other desirable properties. As observed in [51], Prüfer codes are a
poor tree representation for Genetic Algorithms, since they do not have good
locality and heritability (see Section 4.2). Experimental analysis [61] shown
that these properties are much better satisfied by the Blob code described
by Picciotto in her PhD thesis [89].
These experimental results do not provide any insight on the underlying
reasons that make the Blob code better than the Prüfer code in this field. So
our interest has been stimulated and therefore we decide to study, from an
algorithmic point of view, all the three codes described by Picciotto in [89]:
Blob code, Happy code and Dandelion code.
This chapter is organized as follows: initially we recall the original algorithms given by Picciotto for her codes. We also recall the E-R Bijection:
a code introduced by Eğecioğlu and Remmel [43] several years before Picciotto’s work. In Section 5.4, as a novel result of this thesis, we present a
general scheme for defining bijective codes based on the transformation of a
tree into a functional digraph. We show how it is possible to map Picciotto’s
codes to our scheme (for this reason we call them Transformation codes).
This gives us a better comprehension of how encoding preserves the topology of the tree, and therefore it helps to understand which code better fits
57
58
CHAPTER 5. TRANSFORMATION CODES
desirable properties, such as locality and heritability [24, 25].
It should be remarked that the Dandelion code is basically equivalent to
the E-R Bijection and that the work by Eğecioğlu and Remmel inspired our
general scheme. We also want to highlight that the general scheme based
on graph transformation introduced in this chapter is capable to describe
all possible bijective tree codes, while only a strict subset of them can be
described as a Prüfer-like code. In the literature other codes for Cayley tree
not considered in this thesis have been introduced (e.g., Chen [29], Palmer e
Kershenbaum [82]) as well as their generalizations and specializations. For
example there are code designed to describe trees that are spanning trees of
bipartite or multipartite graphs (e.g., Ωn bijection [44], Rainbow code [83]).
Let us now introduce a few preliminary definitions and notations.
5.1
Preliminaries
In order to keep our description coherent with the one given by Picciotto, in
this chapter we will deal with unrooted Cayley trees, labeled with integers in
[0, n − 1] rather than [1, n]. Moreover all trees will be considered as rooted
at node 0 with edges oriented upwards, from a node to its parent.
Definition 5.1. Given a function g : A → A, the functional digraph G =
(V, E) associated with g is a directed graph with V = A and E = {(v, g(v)) :
v ∈ V }.
It is well known that:
Lemma 5.2. A digraph G = (V, E) is a functional digraph if and only if the
out degree of each node is equal to 1.
Corollary 5.3. Each connected component of a functional digraph is composed of several trees, each of which is rooted in a node belonging to the core
of the component, which is either a cycle or a loop (see Figure 5.1a).
Functional digraphs are easily generalizable to represent functions undefined in some point: if g(x) is not defined, the node x in G does not have
5.2. PICCIOTTO’S CODES
59
Figure 5.1: a) A functional digraph associated with a fully defined function. b)
A functional digraph associated with a function undefined in 0, 8, and 9.
outgoing edges. The connected component of G containing an x, such that
g(x) is not defined, is a tree rooted at x without cycles (see Figure 5.1b). In
the following loops will always be considered as cycles of length 1.
Remark 5.4. Let T be a rooted tree and p(v) be the parent of v for each node
v in T . T is the functional digraph associated with the function p.
Using the notation pathset(u, v) we refer to the set of nodes in the directed
path, between u and v. For our purposes we will assume that u and v do not
belong to pathset(u, v). As an example, consider the digraph of Figure 5.1a,
pathset(3, 6) is {0, 2, 8}.
5.2
Picciotto’s Codes
In this section we recall Blob code, Happy code, and Dandelion code as
originally presented by Picciotto in her PhD thesis [89]. As she explicitly
remarks, all of them hinges upon previous works. The first one gives explicitly
a bijection that in the Orlin’s proof of Cayley’s theorem appears in implicit
form [81]. The Happy code is based on a proof by Knuth [69]. The last
one is an implementation of the Joyal’s pseudo-bijective proof of Cayley’s
theorem [60] and is equivalent to the code introduced in [43] by Eğecioğlu
and Remmel: the E-R Bijection. Picciotto initially studied these codes in
terms of matrix transformations then, by means of the Kirchhoff’s Matrix
60
CHAPTER 5. TRANSFORMATION CODES
Tree Theorem [102], she presented them as algorithms on trees. In this thesis
we focus only on algorithmic aspects related with these codes.
For all codes we recall only the encoding algorithm, since it is sufficient to
map these codes into our general scheme based on digraph transformation.
In this way we will obtain new linear time encoding algorithms; decoding
algorithms will be provided in terms of inverse transformations.
In this section we also explicitly recall the E-R Bijection, in order to show
that the Dandelion code is equivalent to this formerly introduced code.
5.2.1
Blob Code
Let us consider a tree with n nodes labeled with distinct integers in [0, n − 1]
rooted at node 0. The encoding algorithm for the Blob code consider all
nodes but 0 in decreasing label order. Each node is detached from its parent
and added to a macro node called blob. This macro node has a parent in
the tree (a conventional node) and it may contain many nodes; each node
included in blob maintains its own subtree, if any (the example in Figure 5.2
provide a clarifying image). Nodes whose ascending path intersect the blob,
once detached, force the blob to change its parent, others do not. The formers
add to the codeword the parent of blob before the induced change, while the
others simply add their parents.
Formally the encoding algorithm can be described as follows:
BLOB ENCODING ALGORITHM
1.
2.
3.
4.
5.
6.
7.
8.
9.
Root T in 0
Initialize C as an empty string of size n − 2
blob = {n − 1}
for v = n − 2 down to 1 do
if (pathset(v, 0) blob) = ∅ then
C[v − 1] = p(v)
delete (v, p(v))
insert v in blob
else
5.2. PICCIOTTO’S CODES
61
Figure 5.2: a) A sample tree T rooted at node 0; b) an intermediate step of the
execution of the blob encoding algorithm. The grey area identify the blob,
question marks in the codeword correspond to values that are still unassigned;
c) the resulting codeword at the end of the execution.
10.
11.
12.
13.
14.
C[v − 1] = p(blob)
delete (blob, p(blob))
add (blob, p(v))
delete (v, p(v))
insert v in blob
In Figure 5.2 an example of the execution of blob encoding algorithm is given.
The computational complexity of original Blob encoding and decoding
algorithms is quadratic in the number of nodes of the tree, due to the computation of pathset(v, 0) at each iteration (line 5).
Improvements
We now show how it is possible to improve the blob encoding algorithm
to obtain a linear time algorithm.
We will call stable all nodes satisfying the test in line 5 because they let
the blob parent unchanged. The value in the code corresponding with a stable
node v is simply p(v).
62
CHAPTER 5. TRANSFORMATION CODES
Analyzing this algorithm we can see that the condition in line 5 is not
strictly connected to the incremental construction of the blob, but it can be
computed apriori as Lemma 5.5 asserts:
Lemma 5.5. Stable nodes are all nodes v such that v < max(pathset(v, 0)).
Proof. When node v is considered by the encoding algorithm the set blob
contains all the nodes from v + 1 to n. Then the condition of line 5 holds if
and only if at least a node greater than v occurs in pathset(v, 0). Remember
that pathset(v, 0) does not include v and 0.
Lemma 5.6. For each unstable node v, let z be the smaller unstable node
greater than v, p(z) is the value inserted in the code when v is considered by
the encoding algorithm.
Proof. In line 10 of blob encoding algorithm the current parent of blob
defines the code value corresponding to an unstable node v. In subsequent
lines the blob becomes child of p(v). It implies that p(blob) is equal to the
parent of the smaller unstable node greater than v, i.e. p(z).
Our characterization of stable nodes decreases the complexity of blob
encoding algorithm to O(n).
5.2.2
Happy Code
The encoding algorithm for the Happy code focuses on the path from 1 to
0. Since the aim of the algorithm is to ensure the existence of edge (1, 0),
all nodes on the original path from 1 to 0 are sequentially moved in order
to form cycles. Let us call maximal each node v in pathset(1, 0) such that
v > max(pathset(1, v)). The first cycle is initialized with p(1) and each time
a maximal node is encountered a new cycle starts. Non maximal nodes are
inserted in the current cycle.
The encoding algorithm has been described in [89] as follows:
5.2. PICCIOTTO’S CODES
63
HAPPY ENCODING ALGORITHM
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
Root T in 0
Initialize J = p(1)
while p(1) = 0 do
// consider pathset(1, 0) until p(1) = 0
j = p(1)
delete (1, j)
// detach j
delete (j, p(j)) and add (1, p(j))
// attach 1 above j
if j > J then
// if j is maximal
J=j
// start a new cycle
add (J, J)
else
// insert j in the current cycle close to J
add (j, p(J))
delete (J, p(J))
add (J, j)
for v = 2 to n do
C[v − 2] = p(v)
// compute the codeword
Figures 5.3a and 5.3b show an example of the happy encoding algorithm applied to the tree T of Figure 5.2a. Notice that the algorithm inserts
a node j in a cycle immediately after J, the maximal node in the cycle. This
implies that nodes in a cycle of the resulting graph are in reversed order with
respect to their position in the original tree, e.g., in Figure 5.3b the edge
(5, 2) was (2, 5) in the original tree. With the intent of keep the digraph as
close as possible to the original tree, we now introduce a slightly modified
version of this code which avoids this inversion: j is attached immediately
before J instead of immediately after it. Let us call this modified version
of Happy code MHappy code. The resulting digraph obtained applying the
MHappy code to T is shown in Figure 5.3c.
The happy encoding algorithm works in O(n) running time since it
requires a number of steps equal to the length of the path between 1 and 0
in the tree.
64
CHAPTER 5. TRANSFORMATION CODES
Figure 5.3: a) An intermediate step of the execution of the happy encoding
algorithm on T of Figure 5.2a; b) the end of the execution and the resulting
codeword, maximal nodes are represented in grey. c) Graph and codeword obtained
with the MHappy code.
5.2.3
Dandelion Code
The Dandelion code is equivalent to the E-R Bijection introduced in [43] by
Eğecioğlu and Remmel, but is described by Picciotto in a totally different
way. It encodes the tree recursively attaching all nodes to node 1. During
this process labels on edges are introduced, these labels will be used to create
the code.
The encoding algorithm for Dandelion code, as presented in [89], is the
following:
DANDELION ENCODING ALGORITHM
1.
2.
3.
4.
5.
6.
7.
8.
9.
Root T in 0
for v = n down to 2 do
h = p(v)
k = p(1)
delete (v, h)
add (v, 1) with label label(v, 1) = h
if a cycle has been created then
delete (1, k)
add (1, h)
5.2. PICCIOTTO’S CODES
65
Figure 5.4: The tree T of Figure 5.2a after the execution of the dandelion
encoding algorithm.
label(v, 1) = k
11. for v = 2 to n do
12.
C[v − 2] = label(v, 1)
10.
The poetic name Dandelion derives from the fact that connecting all the
nodes to node 1, a graph which looks like a dandelion flower is created (see
Figure 5.4.
Testing if a cycle has been created (line 7) is the most expensive operation
required at each step. A straightforward implementation of this algorithms
requires O(n2) running time.
Improvements
Analogously to what has been done for the Blob code, we here give a characterization of all those node that satisfy the test in line 7, let us call them
flying nodes. This characterization allow us to precompute, for each node,
whether it satisfies the test or not, yielding a linear time algorithm.
Lemma 5.7. Flying nodes are all nodes v such that v ∈ pathset(1, 0) and
v > max(pathset(v, 0)).
Proof. The first condition trivially holds, otherwise cycles cannot be created.
66
CHAPTER 5. TRANSFORMATION CODES
Given v ∈ pathset(1, 0), let m = max(pathset(v, 0)). If m > v then m
is processed before v by the algorithm, m is directly connected to 1 and it
introduces a cycle containing v. When the cycle is broken (line 8 and 9),
all the nodes in the cycle are excluded by the resulting pathset(1, 0). This
implies that in successive steps v cannot be a flying node.
On the other hand, if v > m it will be in pathset(1, 0) when it is processed
by the algorithm and so it obviously introduces a cycle.
The condition stated in Lemma 5.7 allows us to easily precompute whether
a node satisfies the test in line 7 with a simple scan of the path from 1 to 0.
This decreases the running time of the dandelion encoding algorithm
to O(n).
5.3
E-R Bijection
In [43] Eğecioğlu and Remmel introduce a bijection, called θn , that associates
functions in [1, n−2] → [0, n−1] with Cayley trees (labeled in [0, n−1] rooted
in the fixed node n − 1)1 . This bijection can be straightforward interpreted
as a code for labeled trees. Indeed a function in [1, n − 2] → [0, n − 1] can
be written as a sequence of n − 2 numbers in [0, n − 1]. When it is used as a
code, θn is often called E-R Bijection.
Give a function f : [1, n − 2] → [0, n − 1], this bijection uses it to build a
graph G([0, n − 1], (i, j) : f (i) = j), i.e. the functional digraph associated to
g : [0, n − 1] → [0, n − 1] defined as follows:
⎧
⎨ undef ined if i = 0
undef ined if i = n − 1
g(i) =
⎩
f (i)
otherwise
G will necessarily contains two trees rooted at node 0 and n − 1, respectively. All other connected components in G have a cycle as core (see
Corollary 5.3).
According with the original definition of θn , the graph is drawn so that:
1
In the original presentation authors used labels in [1, n] and n as root.
67
5.3. E-R BIJECTION
a)
i:
f (i):
1
4
2
3
3
4
4
2
5
20
6
6
7
11
8
0
9
3
10
3
11
19
12
18
13
18
14
5
15
0
16
15
17
5
18
6
19
11
b)
c)
Figure 5.5: a) A function f : [1, 19] → [0, 20]; b) the digraph G corresponding to
f drawn according to rules 1-3; c) the tree T = θ21 (G).
1. the tree rooted at 0 and n−1 are drawn on the extreme left and extreme
right respectively;
2. cycles are drawn so that their edges form a directed path on the line
between 0 and n − 1 with one backedge above the line;
3. each cycle is arranged so that its smallest node is on the right and the
cycles are ordered from left to right by increasing smallest node.
In Figure 5.5a and Figure 5.5b a function and the corresponding digraph
are shown. The digraph is draw according to rules 1-3.
Now, in order to obtain a tree we must break all cycles and join all
connected components. Let us call ri and li the rightmost and the leftmost
node in each cycle according to the described drawing. θn deletes all backedge
(ri , li ) and inserts the edges (0, l1 ), (r1 , l2 ), (r2 , l3 ), . . . , (rk−1, lk ), (rk , n − 1),
where k is the number of cycles in G. In Figure 5.5c the obtained tree is
shown.
The inverse θn , (i.e., the tree encoding), can be obtained directly from
its definition: consider the path between 0 and n − 1, each node v <
68
CHAPTER 5. TRANSFORMATION CODES
min(pathset(v, n − 1)) will be a right node of a cycle. This is all we need to
correctly split the path between 0 and n−1 in cycles. Eğecioğlu and Remmel
also generalized their bijection so that any node can play the role of 0 and
n-1.
The idea of considering the codeword as the list of a function associated
to a functional digraph has inspired our general scheme described below.
5.4
Functional Digraph Transformation
The easiest method to associate a rooted Cayley tree with a string is to list,
for each node, its parent: this is the naı̈ve code (see Section 4.2). If the tree
is always rooted in a fixed node (0 in our case) the resulting codeword has
length n − 1 and then this method is not bijective.
In this section we present a general scheme for defining bijections between
the set of labeled trees with n nodes and the set of codewords of length n − 2.
Our idea is to modify the naı̈ve code to reduce the length of the codeword
that it yields.
If the tree is rooted in a fixed node x, and there exists another fixed node
y having x as parent, the length of the naı̈ve code can be reduced to n − 2
omitting the information related to both x and y. It is easy to root a given
unrooted tree at a fixed node x, while it is not clear how to guarantee the
existence of edge (x, y). For this reason our general scheme is parametrized
by a function ϕ that manipulates the tree in order to ensure the existence
of (x, y). Parameters ϕ, x, and y characterize each specific instance of our
general scheme.
In order to be suitable for our general scheme the function ϕ must satisfy
certain constraints. It must transform the tree into a graph such that:
1. x has no outgoing edges;
2. the only outgoing edge of node y is (x, y) ;
3. each other node has exactly one outgoing edge.
5.4. FUNCTIONAL DIGRAPH TRANSFORMATION
69
these constraints guarantee that a codeword of length n − 2 can be generated
listing the endpoint of the outgoing edge of each node but x and y.
The three constraints listed above imply that ϕ is a function that transforms a tree T into a functional digraph G. G has n−1 edges and corresponds
to a function g such that g(x) is undefined and g(y) = x. We will call the
class of such graphs Fnxy .
Lemma 5.8. |Fnxy | = nn−2 = |Tn |
Proof. Consider all functions h : [0, n − 1] {x, y} → [0, n − 1], there are
clearly nn−2 such functions. For each h we can derive a digraph in |Fnxy |
associated to a function g defined as follows:
⎧
⎨ undef ined if i = x
g(i) =
x
if i = y
⎩
h(i)
otherwise
Thus |Fnxy | ≥ nn−2 .
On the other hand, for each digraph in |Fnxy | associated to a function g,
we can univocally identify a function h such that h(i) = g(i) ∀i ∈ [0, n − 1]
{x, y}. This implies |Fnxy | ≤ nn−2 .
We now present the general encoding scheme when ϕ, x, and y are given:
GENERAL ENCODING SCHEME
1.
2.
3.
4.
5.
Root T in x
Construct G = ϕ(T )
for v = 0 to n − 1 do
if v = x and v = y then
append g(v) to C
In order to guarantee the bijectivity of the obtained encoding, the function
ϕ must be invertible, i.e. there must exist a function ϕ−1 that, given a
70
CHAPTER 5. TRANSFORMATION CODES
functional digraph G in Fnxy , is able to revert it to the tree T corresponding
to G, i.e., ϕ(T ) = G.
If ϕ−1 exists, it is possible to define the general decoding scheme:
GENERAL DECODING SCHEME
1.
2.
3.
4.
5.
6.
7.
8.
for v = 0 to n − 1 do
if v = x and v = y then
extract the first element u from C
g(v) = u
g(x) = undef
g(y) = x
Reconstruct the graph G from g
Compute T = ϕ−1 (G)
In the following we will show how to map Blob code, MHappy code and
Dandelion code into our scheme. For each code we will define a ϕ function
to compute it, we will provide an inverse function to decode it, and we will
discuss the running time required for both encoding and decoding. Linear
time algorithms for Happy and Dandelion code have been presented by Picciotto in terms of string permutations. We rather focus on graph algorithms
because it helps us to understand why these codes have good locality and
heritability.
5.4.1
Blob Transformation
In this section we introduce a function ϕb suitable to map the Blob code
presented in Section 5.2.1 into our general encodign scheme. We will
exploit the characterization of stable nodes given above. The function ϕb
constructs a graph G from a tree T in the following way: for each unstable
node v, remove edge (v, p(v)) and add edge (v, p(z)) where z = min{u : u >
v and u unstable}. If z does not exist (i.e., if v = n − 1), add edge (v, 0).
In Figure 5.6a all stable nodes (i.e., all v such that v < max(pathset(v, 0)))
of the tree T depicted in Figure 5.2a are marked in gray. Figure 5.6b shows
5.4. FUNCTIONAL DIGRAPH TRANSFORMATION
71
Figure 5.6: a) The tree T of Figure 5.2a with stable nodes identified in grey;
b) G = ϕb (T ) and the Blob code representing T .
the digraph obtained transforming T by means of ϕb .
Before proving that ϕb allows us to correctly compute the Blob code we
need to prove the following lemma:
Lemma 5.9. Each path in T from a stable node v to m = max(pathset(v, 0))
is preserved in G = ϕb (T ).
Proof. Let v be a stable node. Let us assume by contradiction that the
path from v to m = max(pathset(v, 0)) is in T but not in G = ϕb (T ).
This means that in the transformation from T to G at least one node w in
pathset(v, m) has changed its parent. Since ϕb changes only edges outgoing
from unstable nodes, w should be unstable and then w > max(pathset(w, 0)).
w ∈ pathset(v, m) implies m ∈ pathset(w, 0), then w should be greater than
m contradicting m = max(pathset(v, 0)).
Theorem 5.10. Blob code fits into the General Scheme when x = 0, y =
n − 1, and ϕ = ϕb .
Proof. It is easy to see that graph G = ϕb (T ) is a functional digraph, since
each node has outdegree equal to 1. Moreover the function g associated with
G is undefined in 0 and g(y) = 0, by definition of ϕb .
72
CHAPTER 5. TRANSFORMATION CODES
Figure 5.7: Nodes involved in the proof of Theorem 5.10 both in G and in T .
Stable nodes are represented in grey.
Lemmas 5.5 and 5.6 guarantee that the generated codeword C is equal
to the codeword computed by blob encoding algorithm.
Now we have to prove that ϕb is invertible, i.e. we have to show how to
rebuild T from G: all cycles in G must be broken, and stable and unstable
nodes recomputed.
Each cycle Γ is broken deleting the edge outgoing from γ, the maximum
label node in Γ. Lemma 5.9 implies that γ unstable in T , otherwise a path
from γ to max(pathset(γ, 0)) would have been preserved in G, then a node
greater than γ would appear in Γ. Notice that γ becomes the root of its
own connected component, while 0 is the root of the only connected component not containing cycles. The identification of γ is a step towards the
recomputation of stable and unstable nodes.
We call stable in G each node v such that v < max(pathset(v, γv ) {γv }),
where γv is the root of the connected component containing v. Lemma 5.9
guarantees that each node v stable in T , is also stable in G. Now we prove
that vice versa is also true.
Let us assume, by contradiction, that there exists a node v stable in G
but unstable in T . Let m = max(pathset(v, γv ) {γv }) in G. It holds v < m
and m unstable both in G and in T . In G node m is unstable because there
is no node greater than m in pathset(v, γv ) {γv }; in T node m cannot be
stable because, as noted before, each stable node in T remains stable in G.
W.l.o.g. let us assume that all nodes between v and m are stable both in
G and in T . Let w be the parent of v in G. By definition of ϕb there exists
5.4. FUNCTIONAL DIGRAPH TRANSFORMATION
73
a node u > v unstable in T such that p(u) = w in T . In Figure 5.7 v, m, u,
and w are depicted both in G and in T .
Since m is in the path from u to 0 in T and u is unstable in T , m must be
smaller than u. Then v < m < u and m is unstable in T , this is impossible
because ϕb chooses u as the smaller unstable node greater than v in T .
Once stable and unstable node are correctly identified, it is straightforward to rebuild the tree T = ϕ−1
b (G).
Linear running time algorithms for both encoding and decoding can be
obtained by fitting Blob code into our General Scheme. Indeed both ϕb and
ϕ−1
b can be implemented in O(n) sequential time: computation of the maximum node in the upper path (coding) and cycles identification (decoding)
can both be implemented by simple search techniques.
Let us now show a possible implementation:
function ComputeUpperMax(v)
if upperMax(p(v)) is undefined then
2.
ComputeUpperMax(p(v))
3. upperMax(v) = max{p(v), upperMax(p(v))}
1.
COMPUTE ϕb
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
upperMax(0) = 0
for v = 1 to n − 1 do
if upperMax(v) is undefined then
ComputeUpperMax(v)
prevNonStatic = n − 1
for v = n − 2 down to 1 do
if upperMax(v) > v then
g(v) = p(v)
else
g(v) = p(prevNonStatic)
prevNonStatic = v
// if v is a static node
// a non static node
74
CHAPTER 5. TRANSFORMATION CODES
g(n − 1) = 0
13. g(0) = undef ined
14. return G corresponding to g
12.
This algorithm begins with the computation of upperMax(v), i.e. for each
node v it computes max(pathset(v, 0)). Then this information is exploited
to distinguish static and non static nodes and to assign the correct value
to g(v). Using this algorithm to compute ϕb together with the general
encoding scheme introduced in Section 5.4, it is possible to compute the
Blob code for a tree in O(n) time.
The decoding process is a bit more difficult, but still requires linear
time. The hardest part in the computation of ϕ−1
is the computation of
b
max(pathset(v, γv ) {γv }) for each node v, let us call this information µ(v).
This can be done without explicitly identifying γv and then without breaking cycles. In order to avoid the risk of infinite recursion on cycles we will
associate to each node a variable status to distinguish whenever a node is
still being processed or not.
function ComputeMu(v)
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
if status(p(v)) = processed then
µ(v) = max{µ(p(v)), p(v)}
status(v) = processed
else
status(v) = inP rogress
if status(p(v)) = inP rogress then
µ(v) = MaxInCycle(v)
else
ComputeMu(p(v))
µ(v) = max{µ(p(v)), p(v)}
status(v) = processed
// no need to iterate
// iterate
// this is a cycle
// recursive call
Let us clarify the condition of line 6 in function ComputeMu. The status
of a node v is inP rogress only during a recursive call of ComputeMu on
5.4. FUNCTIONAL DIGRAPH TRANSFORMATION
75
the ascending path of v. If the condition of line 6 is true, a cycle has been
identified, i.e. following outgoing edges we move from v back to v itself. At
this point, in order to avoid infinite recursion on cycles, an auxiliary function
to compute the maximum in this cycle is called and the recursion terminates.
The auxiliary function MaxInCycle simply follows outgoing edges from v until
it comes back on v and returns the maximum label encountered.
COMPUTE ϕ−1
b
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
µ(0) = 0
status(0) = processed
for v = 1 to n − 1 do
if status(v) = processed then
ComputeMu(v)
prevNonStatic = n − 1
for v = n − 2 down to 1 do
if µ(v) > v then
p(v) = g(v)
else
p(prevNonStatic) = g(v)
prevNonStatic = v
p(prevNonStatic) = 0
p(0) = undef ined
return T corresponding to p
// if v is a static node
// a non static node
Since function µ(v) can be computed, for each v, in overall linear time
with function ComputeMu, ϕ−1
b requires O(n) running time.
The experimental analysis presented in [61] shows that locality and heritability are satisfied by the Blob code much better than by the Prüfer code.
The reasons behind this experimental result become clear when Blob code is
analyzed according to our method, which is quite different from Picciotto’s
description. The functional digraph generated by ϕb preserves an edge of
the original tree for each stable node, and for these nodes g(v) = p(v): this
partial similarity with naı̈ve code is the reason why Blob code exhibits good
76
CHAPTER 5. TRANSFORMATION CODES
locality and heritability. We recall that naı̈ve code has maximal locality and
heritability (see Section 4.2).
In the next two sections we will see how MHappy code and Dandelion
code preserve similarities with naı̈ve code more that Blob code.
5.4.2
MHappy Transformation
Here we show how to map our Modified Happy code introduced in Section 5.2.2 into our general scheme. The same result for the original Happy
code can be obtained analogously.
We define a function ϕm which, given a tree T , constructs a graph G
by considering only the path from 1 to 0. For each maximal node v in
pathset(1, 0) remove the edge incoming in v, and add an edge (z, v) where z
is a node in pathset(v, 0) such that p(z) is the smaller maximal node greater
than v. If z does not exist, use the child of 0; finally remove the edge incoming
in 0 and add the edge (1, 0).
Theorem 5.11. MHappy code fits into the General Scheme when x = 0,
y = 1, and ϕ = ϕm .
Proof. It is trivial to see that MHappy encoding transforms T into the same
functional digraph generated by ϕm : this corresponds to a function g undefined in 0 and is such that g(1) = 0.
To show that ϕm is invertible, first sort all cycles in G in increasing order
with respect to their maximum node γ, then break each cycle removing the
edge incoming in γ. Since the order of cycles obtained is the same as that in
which they were originally created, we rebuild the original tree inserting all
the nodes of each cycle in the path from 1 to 0 according to the order of the
cycles.
We now detail the implementation of ϕm and ϕ−1
m .
5.4. FUNCTIONAL DIGRAPH TRANSFORMATION
77
COMPUTE ϕm
1.
2.
3.
4.
5.
6.
7.
8.
g(v) = p(v) ∀v ∈ [0, n − 1]
identify all maximal nodes m1 , m2 , . . . , mk append mk+1 = 0
identify their predecessors pred(m1 ), . . . , pred(mk+1)
for i = 1 to k do
last(mi ) = pred(mi+1 )
// the last element of mi ’s cycle
g(last(mi )) = mi
// close each cycle
g(1) = 0
return G corresponding to g
Computations of lines 2 and 3 can be easily achieved with a simple scan
of the path from 1 to 0, then the algorithm requires linear time.
To compute ϕ−1
m we exploit the same µ(v) computed in Section 5.4.1,
indeed it is easy to see that µ(v) = v if and only if v is the maximum node
in a cycle.
COMPUTE ϕ−1
m
1.
2.
3.
4.
5.
6.
7.
8.
p(v) = g(v) ∀v ∈ [1, n − 1]
identify all nodes m1 , m2 , . . . , mk such that µ(mi ) = mi
identify their predecessors pred(m1 ), . . . , pred(mk ) in cycles
p(1) = m1
for i = 1 to k − 1 do
p(pred(mi )) = mi+1
// break each cycle
p(pred(mk )) = 0
return T corresponding to p
µ(v) can be computed, for each v, in linear time, as shown in Section 5.4.1
and line 3 requires nothing more than a scan of each cycle. Therefore encoding and decoding MHappy code requires O(n) running time; the same bound
holds for the original Happy code.
We underline that ϕm modifies only a subset of the edges on the path
between 1 and 0, so it preserves the topology of T much better than ϕb . For
78
CHAPTER 5. TRANSFORMATION CODES
Figure 5.8: The digraph GD = ϕd (T ), flying nodes are represented in grey.
this reason in [25] we claimed that MHappy code should satisfy locality and
heritability properties better than Blob code.
5.4.3
Dandelion Transformation
In this section we map the Dandelion code into our general scheme. Let us
consider the dandelion encoding algorithm and the characterization
of flying nodes given in Section 5.2.3.
We define a function ϕd that transforms T in G considering only flying
nodes in decreasing order. For each flying node v, ϕd swaps p(v) and p(1)
(see Figure 5.8).
Theorem 5.12. Dandelion code fits into the General Scheme when x = 0,
y = 1, and ϕ = ϕd .
Proof. G = ϕd (T ) is a functional digraph corresponding to a function g
undefined in 0 and such that g(1) = 0. The code generated using ϕd is the
same as that using dandelion encoding algorithm. Indeed when the
algorithm breaks a cycle in a flying node v, node 1 is connected to h (the
former parent of v) and the label of edge (v, 1) becomes k (the former parent
of 1). In code C, the position corresponding to v contains the value k. Nonflying nodes simply use their parents as edge labels. Thus, assigning p(v) = k
5.4. FUNCTIONAL DIGRAPH TRANSFORMATION
79
for flying nodes, it is possible to avoid edge labels. In this way, each nonflying node simply retrains its parent while each flying node exchanges its
parent h with the parent of node 1, i.e., k. Since the dandelion encoding
algorithm considers nodes in decreasing order, ϕd produces the same code.
To invert ϕd we again have to break cycles in the functional digraph
corresponding with a given code. Flying nodes are all and only maximal
nodes in cycles. Note that, in order to correctly rebuild the path from 1
to 0, cycles of G must be considered in increasing order of their maximum
node.
The algorithms obtained fitting Dandelion code into our general scheme
are linear. Indeed the computation of ϕd and ϕ−1
d requires the same operations used for ϕm and ϕ−1
m . Let us detail the implementation of both ϕd and
−1
ϕd :
COMPUTE ϕd
1.
2.
3.
4.
5.
g(v) = p(v) ∀v ∈ [0, n − 1]
identify all flying nodes f1 , f2 , . . . , fk in path from 1 to 0
for i = 1 to k do
swap g(1) and g(fi)
return G corresponding to g
Since line 2 requires a simple backward scan of the path from 1 to 0, the
algorithm requires O(n) time.
The implementation of ϕ−1
m relies on the fact that flying nodes are all and
only maximal nodes in cycles. We already know that for these nodes it holds
µ(v) = v.
COMPUTE ϕd−1
p(v) = g(v) ∀v ∈ [1, n − 1]
2. identify all nodes fi such that µ(fi ) = fi
3. sort f1 , f2 , . . . , fk in decreasing order
1.
80
CHAPTER 5. TRANSFORMATION CODES
for i = 1 to k do
5.
swap g(1) and g(fi )
6. return T corresponding to p
4.
In view of the considerations made in the previous sections regarding the
computation of µ, this algorithm runs in linear time.
5.5
Comparing Transformation Codes
Both MHappy code and Dandelion code modify only a subset of the edges
on the path between 1 and 0. Even the E-R Bijection focuses on a single
path of the tree and splits it into cycles. Moreover, if in the E-R Bijection
we use 1 instead of 0 and 0 instead of n − 1, the resulting encoding is almost
identical to the Dandelion code. The unique difference is that in the Dandelion code flying nodes are such that v > max(pathset(v, 0)), while in the
E-R Bijection rightmost nodes satisfy the inequality v < min(pathset(v, 0)).
Happy code and MHappy code, instead, focus on the pathset(1, v) so they
basically analyze the path in the reverse direction. Indeed, as Picciotto has
proved, given a tree T , if we obtain T ′ reverting the order of nodes in the
path between 1 and 0, the Happy code for T coincides with the Dandelion
code for T ′ and vice versa.
In a paper we published in 2005, we conjectured that MHappy code and
Dandelion code should have similar locality and heritability, and that both of
them are better than Blob code. Later then, in 2006, experimental comparisons of these codes in GA have been made by Paulden and Smith [85]. They
also tested all possible variations of codes that split a fixed path considering
foreward/backward minimum/maximum nodes in increasing/decreasing order. They called them Dandelion-like codes. Their experiments show that,
with respect to performances in GA, these codes are splitted in two groups.
Paulden and Smith concluded that: “The Group 2 codings (including the
MHappy Code) were found to have similar locality properties and slightly better heritability properties to the Group 1 codings (including the Dandelion
Code and Happy Code)” [85].
5.6. CONCLUDING REMARKS
81
Paulden and Smith also proved that Dandelion code has asymptotically
optimal locality [84].
5.6
Concluding Remarks
The General Scheme introduced in this section is suitable to interpreter
known codes as transformations between trees and functional digraphs. It
gives us a better comprehension of how encoding preserves the topology of
the tree, and therefore it helps to understand which code better fits desirable
properties, such as locality and heritability.
As we have seen, this General Scheme is suitable to code unrooted trees
considering them as rooted in a fixed node y. In order to code arbitrarily
rooted trees, it is enough to add the root label to an n − 2 length codeword (e.g., as a last symbol) obtained re-rooting the tree in y. In this way
the scheme encodes a rooted tree with n nodes with codewords of length
n − 1, that is still a bijective mapping. If both x and y are added to the
codeword, each instance of the General Scheme becomes an implementation
of the Joyal [60] bijection between vertebrates (doubly-rooted labeled trees)
and strings of length n.
The class of Transformation codes (i.e., those codes that can be mapped
into our general scheme) contains each possible bijective code for labeled
trees. This is not true for other classes such as Prüfer-like codes (which are
based on recursive leaves elimination). As an example consider the tree T of
Figure 5.2a. The Dandelion code for T is CD = (5, 6, 10, 2, 4, 2, 1, 0, 3, 9), it
is not possible to find a Prüfer-like code able to associate T with CD , indeed
at the first step, among all leaves, no one has 5 as parent.
On the other hand, it is possible to define a function ϕp that yields the
Prüfer code through the general encoding scheme for any chosen x
and y. Let CP be the Prüfer code associated to T , then functional digraph
GP = ϕp (T ) should have an edge (x, y) and no edges outgoing from y. All
other edges can be derived directly from CP : consider the set V − x − y in
increasing order, the i-th node vi gets the outgoing edge (vi , CP [i]). Figure 5.9
82
CHAPTER 5. TRANSFORMATION CODES
Figure 5.9: A sample tree T and its Prüfer code together with the digraph
GP = ϕp (T ); we used x = 0 and y = 6.
shows an example of applying ϕp , we have chosen x = 0 and y = n − 1.
In this example almost all edges have been changed, then the topology of
the functional digraph is completely different from the one of the tree, this
confirms the poor locality of Prüfer code.
This idea can be easily exploited to map any possible bijective code to our
General Scheme. In the second part of this thesis we will see how bijective
tree codes can be extended to the class of k-tree.
Part II
Generalizations
83
Chapter 6
Encoding k-Trees
In this chapter we consider bijective codes for the class of k-trees, i.e., one
of the most natural and interesting generalizations of trees (for a formal definition see Section 6.1). There is considerable interest in developing efficient
tools to manipulate this class of graphs. Indeed each graph with treewidth
k is a subgraph of a k-tree, and many NP-Complete Problems (e.g. Vertex
Cover, Graph k-Colorability, Independent Set, Hamiltonian Circuit, etc.)
have been shown to be polynomially solvable when restricted to graphs of
bounded treewidth [8, 9, 10]. Moreover each k-tree is also a minimal kconnected graph and thus a minimal k-fault tolerant network [56].
In 1970 Rényi and Rényi [94] generalized Prüfer’s bijective proof of Cayley’s theorem to code a subset of labeled k-trees (Rényi k-trees). They introduced a redundant Prüfer code for Rényi k-trees and then characterized the
valid codewords. Subsequently, non redundant codes that realize bijection
between k-trees (or Rényi k-trees) and a well defined set of codewords was
produced [28, 46]. Attempts have been made to obtain an algorithm with
linear running time for the redundant Prüfer code [73].
As an original contribution of this thesis we present a novel bijective code
for k-tree. We also give a detailed description of linear time encoding and
decoding algorithms for our code. It is worth mention that our code can be
easily adapted to rooted, unrooted, and Rényi k-trees, always preserving
bijectivity. To the best of our knowledge, no linear time algorithms for
85
86
CHAPTER 6. ENCODING K-TREES
encoding and decoding k-tree have been presented in the literature before
our work [22, 23].
The chapter is organized as follows: in Section 6.1 we provide definitions
and combinatorial results on k-trees. In Section 6.2, we survey known bijective codes for k-tree. In Section 6.3 and 6.4 we introduce two building
blocks of our coding technique (Characteristic Tree and Generalized Dandelion Code) while all details for the encoding and decoding algorithm are
given in Section 6.5. In Section 6.6 we discuss the physical representation of
our code. The chapter ends with some conclusions and future directions for
research in this topic.
6.1
Preliminaries
In this section we recall the concepts of k-trees (both rooted and unrooted)
and Rényi k-trees and highlight some properties related to these classes of
graphs.
Definition 6.1. [57] A k-tree is defined in the following recursive way:
1. A k-clique is a k-tree.
2. If Tk′ = (V, E) is a k-tree, K ⊆ V is a k-clique and v ∈
/ V,
then Tk = (V ∪ {v}, E ∪ {(v, x) | x ∈ K}) is a k-tree.
By construction, a k-tree with n nodes has k2 + k(n − k) edges, n − k cliques
on k + 1 nodes, and k(n − k) + 1 cliques on k nodes. Since every k-tree Tk
with k or k + 1 nodes is simply a clique, in the following we will assume
n ≥ k + 2.
In a k-tree, nodes of degree k are called k-leaves. Note that the neighborhood of each k-leaf forms a clique and then k-leaves are simplicial nodes.
A rooted k-tree is a k-tree with a distinguished k-clique R = {r1 , r2 , . . . , rk };
R is called the root of the rooted k-tree.
Remark 6.2. Each k-tree Tk with n ≥ k + 2 nodes contains at least two kleaves; when Tk is rooted at R at least one of those k-leaves does not belong
87
6.1. PRELIMINARIES
a)
b)
Figure 6.1: a) An unrooted 3-tree T3 with 11 nodes; b) T3 rooted in the clique
{2, 3, 9}.
to R (see [94]). Since k-trees are perfect elimination order graphs [95], when
a k-leaf is removed from a k-tree the resulting graph is still a k-tree. If
the resulting k-tree is not a single clique, at most one node adjacent to the
removed k-leaf may become a k-leaf.
In Figure 6.1(a) we give an example of a k-tree with k = 3 and 11 nodes
labeled with integers in [1, 11]. The same k-tree, rooted at R = {2, 3, 9}, is
given in Figure 6.1(b).
Let us call Tkn the set of k-trees with n nodes labeled with distinct labels.
The cardinality of Tkn is (see [6, 47, 77, 94]):
n
n
(k(n − k) + 1)n−k−2
|Tk | =
k
When k = 1 the set T1n is the set of Cayley’s trees and |T1n | = nn−2 , i.e., the
well known Cayley’s theorem (see Chapter 2).
Arbitrarily rooted k-trees with n nodes labeled with distinct labels can
be denoted as a pair (Tkn , R). Since each k-tree Tk contains k(n − k) + 1
cliques on k nodes, the number of arbitrarily rooted k-trees is:
n
n
(k(n − k) + 1)n−k−1
|Tk | · (k(n − k) + 1) =
k
Definition 6.3. [94] A Rényi k-tree Rk is a rooted k-tree with n nodes
labeled in [1, n] and root R = {n − k + 1, n − k + 2, . . . , n}.
88
CHAPTER 6. ENCODING K-TREES
({9, 10, 11}, {2, 10, 11}, {9, 10, 11}, {1, 5, 8}, {5, 8, 9}, {8, 9, 10})
Figure 6.2: A Rényi 3-tree R3 with 11 nodes and root {9, 10, 11} together with
its redundant Prüfer code.
It has been proven [77, 94] that:
|Rnk | = (k(n − k) + 1)n−k−1
where Rnk is the set of Rényi k-trees with n nodes.
Remark 6.4. The set of labeled trees rooted at n is equivalent to the set of
unrooted labeled trees. This equivalence cannot be transposed on k-trees
when k > 1. Indeed, not all k-trees contain the clique {n − k + 1, n − k +
2, . . . , n} and then not all k-trees are eligible to be considered a Rényi k-trees.
This implies Rnk ⊆ Tkn .
6.2
Known Codes
In 1970 Rényi and Rényi [94] generalized Prüfer’s bijective proof of Cayley’s
theorem to code Rényi k-trees. Their code recursively eliminate from the
k-tree the smallest k-leaf. Each time a k-leaf a is removed the set B of its
adjacent nodes (that form a k-clique) is added to the codeword as a single
symbol of the string. Nodes belonging to the root R = {n − k + 1, n − k +
2, . . . , n} are never considered as k-leaves. The procedure terminates when
the codeword reaches length n − k − 1.
Example 10. Consider the Rényi 3-tree in Figure 6.2. It has 3 k-leaves:
{3, 4, 7}. At the beginning a1 = 3 and B1 = {9, 10, 11}. At the second step
89
6.2. KNOWN CODES
a2 = 4 and B2 = {2, 10, 11}. When 4 is removed from the k-tree 2 becomes a
k-leaf and therefore a3 = 2 and B3 = {9, 10, 11}. The algorithm proceeds removing 7, 1, 5, 6. The resulting codeword is ({9, 10, 11}, {2, 10, 11}, {9, 10, 11},
{1, 5, 8}, {5, 8, 9}, {8, 9, 10}) Notice that there is no need to remove 8 and add
Bn−k = R to the codeword, indeed, the last symbol is always the fixed root
and then can be omitted (as it is for Cayley trees).
Notice that when this procedure is applied to Rényi 1-tree (i.e., simple
Cayley trees rooted at n) it yields exactly the original Prüfer code.
The decoding is analogous to the one given by Prüfer: the leaf ai removed
at the i-th step of the encoding is deduced as the smallest number not yet
used in a1 , . . . , ai−1 that does not appear in any subsequent symbol of the
codeword, i.e., Bi , . . . , Bn−k−1 :
ai = min a ∈ [1, n] {ah }h<i
Bj
j≥i
This code is not bijective, because each symbol of the codeword is a set
of k distinct elements in [1, n] and then each codeword belongs to the set:
n−k−1
[1, n]
k
In order to obtain a correct counting result on the cardinality of the
set of Rényi k-trees, in [94] the authors characterized all valid codewords.
Unfortunately, computationally check whether a given codeword is valid or
not is not immediate. From an algorithmic point of view, we can say that
the validity conditions given by Rényi and Rényi basically correspond to an
attempt of decode the codeword: if there is a step i where the algorithm
is unable to deduce ai the codeword is not valid, if the algorithm correctly
reaches its end the codeword is valid.
From our perspective this is a severe shortcoming of this code: it cannot
be used in those applications where a bijective code is required, like Random
k-tree Generation and Genetic Algorithms over k-tree (topics of Chapter 4
naturally generalize to k-tree).
90
CHAPTER 6. ENCODING K-TREES
Subsequently, non redundant codes that realize bijection between k-trees
(or Rényi k-trees) and a well defined set of codewords were produced by
Eğecioğlu and Shen [46] and by Chen [28]. The latter one deals only with
Rényi k-trees: the author motivates this choice saying that, as well as rooted
trees are more natural than unrooted trees, Rényi k-trees are more natural
than unrooted k-trees. We cannot agree with this claim, indeed, as discussed
above, when k > 1 Rényi k-trees are a strict subset of unrooted k-trees. Thus
we see this peculiarity of the Chen code as a limit. Moreover it does not seem
that this code can be extended to obtain bijective codes for unrooted and
arbitrarily rooted k-trees.
Eğecioğlu and Shen code
The work by Eğecioğlu and Shen is much more interesting than the one
by Chen. They noticed that, since a k-tree has K = k(n − k) + 1 cliques on
k nodes and K ′ = n − k cliques on k + 1 nodes, the number of k-tree can be
rewritten as:
|Tkn |
n
′
K K −2
=
k
Their work relay on a generalization of the E-R Bijection: they interpreted
K
as a function f mapping all (but two) (k−1)-cliques to k-cliques. Each
(k − 1)-cliques is divided into k + 1 “faces” (i.e., k-cliques), f describes how
these faces should be composed to obtain the k-tree. The encoding relies on
the existence of a complex orientation of all edges of the k-tree that induces
an order among the faces of a (k − 1)-clique and among the (k − 1)-cliques
themselves.
K ′ −2
This code does not seem to admits efficient implementation. For these
reason we devoted our efforts in designing a novel bijective code that admits
linear time encoding and decoding.
6.3. CHARACTERISTIC TREE
6.3
91
Characteristic Tree
In this section we introduce the characteristic tree of a rooted k-tree. This
is one of the building blocks of our code. We will use characteristic trees of
Rényi k-trees in our coding process.
Let us start by introducing the skeleton of a rooted k-tree.
Definition 6.5. Given a rooted k-tree Tk with root R, obtainable by Tk′
rooted at R by adding a new node v connected to a k-clique K (see Definition 6.1), the skeleton S(Tk , R) is obtained by adding to S(Tk′ , R) a new
node X = {v} ∪ K and a new edge (X, Y ). Y is the node of S(Tk′ , R) that
contains K at minimum distance from the root. If Tk is the single k-clique
R, its skeleton S(Tk , R) is a tree with a single node R.
The skeleton S(Tk , R) of a rooted k-tree Tk with root R is well defined:
indeed it is always possible to find a node Y containing K in Tk′ because K
is a clique in S(Tk′ , R). Moreover Y is unique: it is easy to verify that if
two nodes in S(Tk′ , R) contain a value v, their lowest common ancestor still
contains v. Since it holds for all v ∈ K, there always exists a unique node Y
containing K at minimum distance from the root.
Definition 6.6. The characteristic tree T (Tk , R) of a rooted k-tree Tk with
root R is obtained by labeling nodes and edges of S(Tk , R) as follows:
1. Node R is labeled 0 and each node {v} ∪ K is labeled v.
2. Each edge from node {v} ∪ K to its parent {v ′} ∪ K ′ is labeled with
the index of the node in K ′ (considered as an ordered set) that does
not appear in K. When the parent is R the edge is labeled ε.
The existence of a unique node in K ′ K is guaranteed by Definition 6.5.
Indeed, v ′ must appear in K, otherwise K ′ = K and the parent of {v ′ } ∪ K ′
contains K. This contradicts the fact that each node in S(Tk , R) is attached
at minimum distance from the root. Therefore at least one element of x ∈ K ′
does not appear in K. Moreover x is unique because |K ′ | = |K| and K =
K ′ {x} ∪ {v ′ }.
92
CHAPTER 6. ENCODING K-TREES
a)
b)
c)
Figure 6.3: a) A Rényi 3-tree R3 with 11 nodes and root {9, 10, 11}; b) the
skeleton of R3 , with nodes {v} ∪ K; c) the characteristic tree of R3 .
Remark 6.7. For each node {v} ∪ K of S(Tk , R), each w ∈ K R appears
as label of a node in the path from v to 0 in T (Tk , R).
As we mentioned before, in our code we will use the characteristic tree of a
Rényi k-trees Rk . As in Rényi k-trees the root is fixed, we omit the argument
R, referring the skeleton as S(Rk ) and the characteristic tree as T (Rk ).
In Figure 6.3 a Rényi 3-tree with 11 nodes, its skeleton and its characteristic tree are shown.
It is easy to see that, given a characteristic tree T , it is possible to reconstruct the corresponding Rényi k-tree: indeed the reconstruction of the
skeleton from T is straightforward, and the skeleton tells us, for each node,
which clique the node should be connected to.
We are interested in finding algorithms to compute T (Rk ) from Rk and
vice versa in linear time. In order to satisfy this constraint the algorithms
detailed in the following sections will avoid the explicit construction of the
skeleton. Moreover, we have to remark that, when restricted to Rényi ktrees, our characteristic tree coincides with the Doubly Labeled Tree defined
in a completely different way in [52] and used in [28]. Our new definition
gives us the right perspective to obtain linear time algorithms.
At the end of this section, let us consider the class of all characteristic trees
6.4. GENERALIZED DANDELION CODE
93
of Rényi k-trees: Zkn . More formally, Zkn is the set of all trees with n − k + 1
nodes labeled with distinct integers in [0, n − k] in which all edges incident
on 0 have label ε and all other edges have arbitrary labels in [1, k]. The
association between a Rényi k-tree and its characteristic tree is a bijection
between Rnk and Zkn . Indeed, for each Rényi k-tree its characteristic tree
belongs to Zkn , and this association is invertible. In Section 6.4 we will show
that |Zkn | = |Rnk |.
6.4
Generalized Dandelion Code
In the first part of this thesis, many bijective codes for labeled trees have
been presented. Here we show a generalization of the Dandelion code that
takes into account labels on edges and can be used to encode characteristic
trees of Rényi k-trees. We have arbitrarily chosen Dandelion code among
several possible others1 . We refer to Chapter 5 for a detailed description of
Dandelion code. The approach we follow here is the one obtained through
our general scheme (see Section 5.4.3).
The Generalized Dandelion Code takes as parameters r and x. It considers a tree T , with n nodes with distinct labels in [0, n − 1], and an edge
labeling function ℓ such that: each edge incident on r has label ε and all other
edges have label over a given alphabet Σ. At the beginning of the encoding procedure T is rooted at r, thus identifying, for each node v, its parent
p(v). Considering T as a digraph with labeled oriented edges, the code recursively breaks the path between x and r into cycles until x reaches r. This
is obtained by means of swap operations (see compute ϕd in Section 5.4.3).
We should specify what happen with edge labels when a swap takes place.
Our algorithm ensures that the edge labels remain associated to parent nodes.
More formally, when two nodes x and w swap their parents, the new edge
(x, p(w)) will have the label of the old edge (w, p(w)) and the new edge
(w, p(x)) will have the label of the old edge (x, p(x)).
1
As discussed at the end of Section 6.5.1 also E-R Bijection, Happy code, and MHappy
would have been valid choices.
94
CHAPTER 6. ENCODING K-TREES
The graph resulting from the encoding process satisfies the following invariants:
• node r has no outgoing edges;
• each node except r has exactly one outgoing edge;
• the outgoing edge of node x is (x, r);
• each edge incoming in r has label ε.
Exploiting the invariants, the resulting graph can be univocally represented by p(v) and ℓ(v, p(v)) for each v ∈ [0, n − 1] {r, x}. The sequence of
these n − 2 pairs constitutes the Generalized Dandelion Code of the original
tree T . The encoding algorithm is as follows:
GENERALIZED DANDELION ENCODING ALGORITHM
1.
2.
3.
4.
5.
6.
7.
identify all flying nodes f1 , f2 , . . . , fk in path from x to r
for i = 1 to k do
ℓ(fi , p(x)) = ℓ(x, p(x))
ℓ(x, p(fi )) = ℓ(fi , p(fi ))
swap p(x) and p(fi )
for v ∈ V (T ) {r, x} in increasing order do
append (p(v), ℓ(v, p(v))) to the code
As for the Dandelion code, the running time of the encoding algorithm is
O(n).
In Figure 6.4 an example of Generalized Dandelion Encoding, with parameters r = 0 and x = 1, is presented.
As a further example let us encode (with r = 0 and x = 1) the tree shown
in Figure 6.3(c). Here the only swap occurring is p(1) ↔ p(8). The codeword
obtained is: [(0, ε), (0, ε), (2, 1), (8, 3), (8, 2), (1, 3), (5, 3)].
Remark 6.8. The Dandelion code satisfies Property 2.3, it is easy to extend
this property to the Generalized Dandelion code. Consider the codeword
95
6.4. GENERALIZED DANDELION CODE
a)
b)
c)
d)
Figure 6.4: a) A tree T with 15 nodes labeled in [0, 14] and edge
labels in [1, 4], represented as rooted at 0; b) after the first swap
p(1) ↔ p(10), cycle {10, 9, 6} has been introduced; c) a loop 5 has been
introduced, after the second swap p(1) ↔ p(5); d) the tree T at the
end of the encoding, after the last swap p(1) ↔ p(3). The codeword is
[(3, 2), (2, 1), (6, 3), (5, 4), (10, 3), (1, 2), (10, 3), (6, 4), (9, 2), (1, 3), (8, 1), (3, 3),
(0, ε)].
associated to a tree T by the Generalized Dandelion code: all and only
internal nodes of T appear (as first element of a pair) in the codeword.
Let us now detail how to decode a codeword S. S is a sequence of n − 2
pairs, each pair is either (r, ε) or a pair in ([0, n − 1] {r}) × Σ.
Initially we construct a functional digraph G, whose node set is [0, n − 1],
in the following way: consider all nodes except r and x, in increasing order.
Let vi be the i-th node and let (pi , li ) be the i-th pair in S. Add to G the
oriented edges (vi , pi ) with label li , for each vi . At the end add the oriented
edge (x, r) with label ε.
The decoding algorithms detailed below proceeds (as for the Dandelion
code) breaking cycles in order to correctly reconstruct the path between x
and r.
96
CHAPTER 6. ENCODING K-TREES
GENERALIZED DANDELION DECODING ALGORITHM
1.
2.
3.
4.
5.
6.
Construct G from S
Identify all cycles in G and their maximal nodes
for each maximal node mi in increasing order do
ℓ(mi , p(x)) = ℓ(x, p(x))
ℓ(x, p(mi )) = ℓ(mi , p(mi ))
swap p(x) and p(mi )
The algorithm retrains O(n) running time. Indeed, managing edge labels
only requires O(1) extra operations at each step.
As mentioned at the end of the previous section, we now exploit the
Generalized Dandelion Code to show that |Zkn | = |Rnk |. Each tree in Zkn has
n−k +1 nodes and therefore is represented by a codeword of length n−k −1.
Each element of this string is either (0, ε) or a pair in [1, n − k] × [1, k].
Then there are exactly k(n − k) + 1 possible pairs. The number of possible
codewords is (k(n−k)+1)n−k−1 , and then |Zkn | = (k(n−k)+1)n−k−1 = |Rnk |.
6.5
A New Code for k-Trees
In this section we present a new bijective code for k-trees and we detail,
for this code, linear time encoding and decoding algorithms. To the best
of our knowledge, this work is the first one that explicitly provides efficient
algorithms to encode and decode k-trees.
6.5.1
Encoding Algorithm
Our algorithm initially transforms a k-tree into a Rényi k-tree: we root the
k-tree Tk at a particular clique Q and we perform a relabeling to obtain a
Rényi k-tree Rk . Exploiting the characteristic tree T (Rk ) and the Generalized
Dandelion Code, we bijectively encode Rk . The codeword for T (Rk ) is then
modified (according with information related to Q) to obtain a codeword for
Tk . The most demanding step of this process is the computation of T (Rk )
6.5. A NEW CODE FOR K-TREES
97
starting from Rk . We will show that even this step can be performed with a
running time linear in the size of the k-tree, i.e., O(nk).
As noted at the end of the previous section, using the Generalized Dandelion Code, we are able to associate elements in Rnk with codewords in:
Bkn = ({(0, ε)} ∪ ([1, n − k] × [1, k]))n−k−1
Since we want to encode all k-trees, rather than just Rényi k-trees, our
final code will consist of a substring of length n−k−2 of the Generalized Dandelion Code for T (Rk ), together with information describing the relabeling
used to transform Tk into Rk .
Our bijective code for k-trees associated elements in Tkn with elements in:
Ank
=
[1, n]
× ({(0, ε)} ∪ ([1, n − k] × [1, k]))n−k−2
k
Note that |Ank | = |Tkn |. In the next section we will describe a decoding
process that is able to associate each codeword in An,k to its corresponding
k-tree: this will prove that the obtained code is bijective.
The encoding algorithm takes as input a k-tree Tk with n nodes and
computes a code in An,k . It is summarized in the following 4 steps:
Encoding Algorithm
1. Identify Q, the k-clique adjacent to the leaf with maximum label lM in
Tk . By a relabeling process φ, transform Tk into a Rényi k-tree Rk .
2. Generate the characteristic tree T for Rk .
3. Compute the Generalized Dandelion Code for T with r = 0 and x =
φ(q), where q = min{v ∈
/ Q}. Remove from the obtained codeword S
the pair corresponding to φ(lM ).
4. Return the codeword (Q, S).
Assuming that the input k-tree is represented by adjacency lists adj, we now
detail how to implement the Encoding Algorithm in linear time.
98
CHAPTER 6. ENCODING K-TREES
Figure 6.5: Graphical representation of φ for 3-tree in Figure 6.1(a).
Step 1. Compute the degree d(v) of each node v and find lM , i.e. the
maximum v such that d(v) = k, then the node set Q is adj(lM ). In order
to obtain a Rényi k-tree, nodes in Q should get labels in {n − k + 1, n −
k + 2, . . . , n}. This can be achieved by a relabeling φ (i.e., a permutation of
labels) defined as follows:
1. if qi is the i-th smallest node in Q, assign φ(qi ) = n − k + i;
2. for each q ∈
/ Q ∪ {n − k + 1, . . . , n}, assign φ(q) = q;
3. unassigned values are used to close permutation cycles, formally: for
each q ∈ {n − k + 1, . . . , n} Q, φ(q) = i such that φj (i) = q and j is
maximized.
Figure 6.5 provides a graphical representation of the permutation φ corresponding to the 3-tree in Figure 6.1(a), where Q = {2, 3, 9}, obtained as
the neighborhood of lM = 10. Forward arrows correspond to values assigned
by rule 1, small loops are those derived from rule 2, while backward arrows
closing cycles are due to rule 3.
The Rényi k-tree Rk is obtained relabeling Tk according with φ. The final
operation of Step 1 consists in ordering the adjacency lists of Rk . The reason
for this operation will be clear in the next step.
Figure 6.3(a) gives the Rényi 3-tree R3 obtained by relabeling the T3 of
Figure 6.1(a) according with φ represented in Figure 6.5. The root of R3 is
{9, 10, 11}.
Let us now prove that the overall running time of Step 1 is O(nk). The
computation of d(v) for each node v can be implemented by scanning all
adjacency lists of Tk . Since a k-tree with n nodes has k2 + k(n − k) edges,
it requires O(nk) time.
6.5. A NEW CODE FOR K-TREES
99
The procedure to compute φ in O(n) time is:
function COMPUTE-PHI
1.
2.
3.
4.
5.
6.
7.
for qi ∈ Q in increasing order do
φ(qi ) = n − k + i
for i = 1 to n − k do
j=i
while φ(j) is assigned do
j = φ(j)
φ(j) = i
Let us show the correspondence between rules in the definition of the
function φ and lines of the algorithm: assignments of rule 1 are made by the
loop in Line 1. The loop in Line 3 implements rules 2 and 3 in linear time.
Indeed the while loop condition of Line 5 is always false for all those values
not belonging to Q ∪ {n − k + 1, . . . , n}. Moreover, for all other nodes the
inner while loop scans each permutation cycle only once, according to rule 3
of the definition of φ. Thus the program runs in O(n) time.
Relabeling all nodes of Tk to obtain Rk requires O(nk) operations, as well
as the standard procedure used to order its adjacency lists:
function ORDER-ADJACENCY-LISTS
for i = 1 to n do
2.
for each j ∈ adj(i) do
3.
append i to newadj(j)
4. return newadj
1.
Step 2. The goal of this step is to build the characteristic tree T of Rk . In
order to guarantee linear running time we avoid the explicit construction of
the skeleton S(Rk ). We build the node set and the edge set of T separately.
The node set is computed identifying all maximal cliques in Rk ; this can
be done by pruning Rk from k-leaves. The pruning proceeds by scanning the
100
CHAPTER 6. ENCODING K-TREES
adjacency lists in increasing order: whenever it finds a node v with degree
k, a node in T labeled by v, representing the maximal clique with node set
v ∪ adj(v), is created. Then v is removed from Rk and consequently the
degree of each of its adjacent nodes is decreased by one.
In a real implementation of the pruning process, in order to limit the
running time, the explicit removal of each node should be avoided. We keep
this information by marking removed nodes and by decreasing node degrees.
When v becomes a k-leaf, the node set identifying its maximal clique is given
by v union the nodes in the adjacency list of v that have not been marked
as removed yet. We will store this subset of the adjacency list of v as Kv : a
list of exactly k integers.
Note that, when v is removed, at most one of its adjacent nodes becomes
a k-leaf (see Remark 6.2). If this happens, the pruning process selects the
minimum between the new k-leaf and the next k-leaf in the adjacency list
scan.
At the end of this process, the original Rényi k-tree is reduced to its root
R = {n−k +1, . . . , n}. To represent this k-clique the node labeled 0 is added
to T (the algorithm also assigns K0 = R).
The algorithm to Prune Rk is detailed below. Its overall running time is
O(nk). Indeed, it removes n − k nodes and each removal requires O(k) time.
PRUNE Rk ALGORITHM
function remove(x)
1.
2.
3.
4.
5.
let Kx be adj(x) without all marked elements
create a new node in T with label x
mark x as removed
for each unmarked y ∈ adj(x) do
d(y) = d(y) − 1
main
for v = 1 to n − k do
2.
w=v
1.
6.5. A NEW CODE FOR K-TREES
3.
4.
5.
6.
7.
if d(w) = k then
remove(w)
while ∃ unmarked u ∈ adj(w) :
w=u
remove(w)
101
u < v and d(u) = k do
In order to build the edge set, let us consider for each node v the set of its
eligible parents, i.e. all w in Kv . Since all eligible parents must occur in the
ascending path from v to root 0 (see Remark 6.7), the correct parent is the
one at maximum distance from the root. This is the reason why we proceed
following the reversed pruning order.
The edge set is represented by a vector p identifying the parent of each
node. 0 is the parent of all those nodes such that Kv = R. The level of these
nodes is 1.
To keep track of the pruning order, nodes can be pushed into a stack
during the pruning process. Now, following the reversed pruning order, as
soon as a node v is popped from the stack, it is attached to the node in Kv
at maximum level. We assume that the level of nodes in R (which do not
belong to T ) is 0.
The pseudo-code of this part of Step 2 is:
function ADD-EDGES
1.
2.
3.
4.
5.
6.
7.
8.
for each v ∈ [1, n − k] in reversed pruning order do
if Kv = R then
p(v) = 0
level(v) = 1
else
choose w ∈ Kv whit maximum level(w)
p(v) = w
level(v) = level(w) + 1
This function requires O(nk) time. Indeed, it assigns the parent of n − k
nodes, each assignment involves the computation of a maximum (Line 6) and
102
CHAPTER 6. ENCODING K-TREES
requires k comparisons.
To complete Step 2, it remains to label each edge (v, p(v)). When p(v) =
0, the label is ε; in general, the label l(v, p(v)) must receive the index of the
only element in Kp(v) that does not belong to Kv . This information can be
computed in O(nk) by simply scanning lists Kv . Indeed, the execution of
ORDER-ADJACENCY-LISTS at the end of Step 1 ensures that elements in
all Kv appear in increasing order.
Figure 6.3(c) shows the characteristic tree computed for the Rényi 3-tree
of Figure 6.3(a).
Step 3. Applying the Generalized Dandelion Code with parameters r = 0
/ Q}, we obtain a codeword S consisting
and x = φ(q), where q = min{v ∈
in a list of n − k − 1 pairs. For each v ∈ {0, 1, 2, . . . , n − k} {0, x} there
is a pair (p(v), ℓ(v, p(v))) taken from the set {(0, ε)} ∪ ([1, n − k] × [1, k]).
As it is, the obtained codeword S is redundant because we already know,
from the relabeling process performed in Step 1, that the greatest leaf lM of
Tk corresponds to a child of the root in T . Therefore the pair associated to
φ(lM ) must be (0, ε) and can be omitted. The Generalized Dandelion Code
already omits the information (0, ε) associated with the node x, so, in order
to effectively reduce the codeword length, we must guarantee that φ(lM ) = x.
Lemma 6.9. Given a k-tree Tk with n nodes, let lM be the maximum leaf
of Tk and φ the permutation described in Step 1. Then, if x is chosen as
φ(min{v ∈
/ Q}), it holds φ(lM ) = x.
Proof. From Remark 6.2, we already know that a k-tree on n ≥ k + 2 nodes
has at least 2 k-leaves. Q cannot contain a k-leaf, since it is chosen as the
adjacent k-clique of the maximum leaf lM . So there exists at least a k-leaf
smaller than lM that does not belong to Q. q = min{v ∈
/ Q} will be less than
or equal to this k-leaf. Consequently lM = q and, since φ is a permutation,
φ(lM ) = φ(q) = x.
The removal of the redundant pair from the codeword S completes Step
3. Since the Generalized Dandelion Code can be computed in linear time,
6.5. A NEW CODE FOR K-TREES
103
the overall running time of the encoding algorithm is O(nk).
It should be now clear that we have chosen Dandelion Code because it
allows us to easily identify an information (the pair (0, ε) associated to φ(lM ))
that can be removed in order to reduce the codeword length from n−k −1 to
n − k − 2: this is crucial to obtain a bijective code for all k-trees. The same
could have been done with E-R Bijection, Happy code, and MHappy code
as well. Blob code and Prüfer-like codes, can be generalized to encode edge
labeled trees, obtaining bijection between Rényi k-trees and codewords in
Bn,k . However, with these codes, it is not clear how to identify a removable
redundant pair. This means that not any code for Rényi k-trees can be
directly exploited to obtain a code for k-trees.
The final codeword (Q, S) belongs to An,k , indeed Q ∈
string obtained by removing a pair from a string in Bn,k .
[1,n]
k
and S is a
The Generalized Dandelion Code obtained from the characteristic tree in
Figure 6.3(c), using as parameters r = 0 and x = 1, is: [(0, ε), (0, ε), (2, 1),
(8, 3), (8, 2), (1, 3), (5, 3)] ∈ B311 ; this is a code for the Rényi 3-tree in Figure 6.3(a). The 3-tree T3 in Figure 6.1(a) is encoded as: ({2, 3, 9}, [(0, ε), (2, 1),
(8, 3), (8, 2), (1, 3), (5, 3)]) ∈ A11
3 . We recall that in this example Q = {2, 3, 9},
lM = 10, q = 1, φ(lM ) = 3, and φ(q) = 1.
6.5.2
Decoding Algorithm
Any codeword (Q, S) ∈ An,k can be decoded to obtain a k-tree whose encoding is (Q, S). This process can be performed with the following algorithm:
Decoding Algorithm
1. Compute φ starting from Q and find lM and q.
2. Insert the pair (0, ε) corresponding to φ(lM ) in S and decode it to
obtain T .
3. Rebuild the Rényi k-tree Rk by visiting T .
4. Apply φ−1 to Rk to obtain Tk .
104
CHAPTER 6. ENCODING K-TREES
Let us detail the decoding algorithm. Since Q is known, it is straightforward to compute q = min{v ∈ [1, n] : v ∈
/ Q} and φ as described in Step 1 of
the encoding algorithm. Since all internal nodes of T explicitly appear
in S (see Remark 6.8), it is easy to derive the set L of all leaves of T by a
simple scan of S. Note that leaves in T coincide with k-leaves in Rk . Applying φ−1 to all elements in L we can deduce the set of all k-leaves of the
original Tk , and therefore find lM , the maximum leaf in Tk .
In order to decode S, a pair (0, ε) corresponding to φ(lM ) needs to be
added, and then the decoding phase of the Generalized Dandelion Code with
parameters 0 and φ(q) has to be applied. The obtained tree T is represented
by its parent vector.
The reconstruction of the Rényi k-tree Rk is now detailed. We assume
that each Kv is a list of k integers in increasing order.
REBUILD Rk ALGORITHM
1.
2.
3.
4.
5.
6.
7.
8.
9.
initialize Rk as the k-clique R on {n − k + 1, n − k + 2, . . . , n}
for each v in T in breadth first order do
if p(v) = 0 then
Kv = R
else
let w be the element of index l(v, p(v)) in Kp(v)
Kv = Kp(v) {w} ∪ {p(v)}
add v to Rk
add to Rk all edges (u, v) such that u ∈ Kv
The last step of the decoding process consists in applying φ−1 to Rk in
order to obtain Tk . The overall complexity of the decoding algorithm is
O(nk). The only step of deserves explanation is Line 7 of the rebuild Rk
algorithm. Assuming that Kp(v) is ordered, to create Kv in increasing
order, it is enough to scan Kp (v) omitting w and inserting p(v) in the correct
position. Since when Kv = R = {n − k + 1, . . . , n} it is trivially ordered, all
Kv can be easily produced as ordered lists.
105
6.6. COMPACT REPRESENTATION
6.6
Compact Representation
In this section we present some technical details about the physical representation of codewords in memory. A codeword in An,k can be represented
efficiently using roughly log(|An,k |) bits.
First we detail how to represent S, the sequence of pairs. Each pair
(p, ℓ) ∈ [1, n−k]×[1, k] can be easily represented in ⌈log(n−k)⌉+⌈log k⌉ bits.
In order to optimize the space requirement of a single pair, we can represent
it as the single integer (p − 1) · k + (ℓ − 1), thus using ⌈log((n − k)k)⌉ bits.
When ((n−k)k) is not a power of two, we can represent the special pair (0, ε)
with any bit sequence not corresponding to any other pair in [1, n−k]×[1, k].
Otherwise one more bit must be used. Hence (n−k−2)⌈log((n−k)k+1)⌉ bits
are required to represent the whole sequence S. Applying the same reasoning
we exploited on pairs we can represent S as a single integer, thus the total
number of bits can be further reduced to ⌈(n − k − 2) log((n − k)k + 1)⌉.
We now discuss several ways to represent Q ∈
n
k
.
The easiest form consists in a list of k values in [1, n]. This requires
k⌈log n⌉ bits. Even though nk has the same asymptotical order of nk , the
possibility to represent lists with repetitions is a drawback.
If k = Θ(n) we can consider to represent Q with its characteristic vector.
This requires exactly n bits but still allow us to represent values not in nk .
A non redundant representation of Q is given by its index in the lexicographically ordered list L of all X ∈ nk . In order to efficiently compute this
index id(Q), notice that the first n−1
elements in L contain 1, while the rek−1
n−1
maining k elements do not contain it. Exploiting this observation we can
compute id(Q) with the following recursive function as id(Q) = ρ(Q, 1, k, n),
where:
⎧
if k = 0,
⎨ 0
ρ(Q i, i + 1, k − 1, n − 1)
if i ∈ Q,
ρ(Q, i, k, n) =
⎩ n−1
+ ρ(Q, i + 1, k, n − 1)
otherwise.
k−1
This computation requires O(nk) time since all binomial coefficients can
106
CHAPTER 6. ENCODING K-TREES
be precomputed with dynamic programming techniques (or with more sophisticate approaches [103]) and each sum between n−1
and ρ(Q, i + 1, k, n − 1)
k−1
can be done in O(k) (these numbers are bigger that log n bits, then it is not
correct to assume that basic operations require constant time).
6.7
Concluding Remarks
In this chapter we have introduced a new bijective code for labeled k-trees,
together with coding and decoding algorithms whose running time is linear
with respect to the input size. To the best of our knowledge, no linear
time algorithms for encoding and decoding k-tree have been presented in the
literature before our work.
In order to develop our bijective code for k-trees we exploited a transformation of a k-tree in a Rényi k-tree and developed a new coding for Rényi
k-trees based on a generalization of the Dandelion code. The choose of
Dandelion code is motivated by the necessity to identify and discard some
redundant information. This is crucial to ensure the resulting code for k-trees
to be bijective.
It is worth to notice that our code can be exploited, with minor changes,
to bijectively encode Rényi k-trees and arbitrarily rooted k-trees as well. For
Rényi k-trees, it is enough to omit Step 1 of the coding process, and return the
codeword S produced by the Generalized Dandelion Code without removing
any redundant pair. The resulting codewords belong to the set Bkn . In the
case of arbitrarily rooted k-trees, it is enough to assign Q = R in Step 1,
without computing lM . This will have no drawback as we do not need to
remove any redundant pair from S in Step 3. The resulting codewords belong
to the set [1,n]
× ({(0, ε)} ∪ ([1, n − k] × [1, k]))n−k−1.
k
We think our work completely solves the problem of coding and decoding
k-trees efficiently. As a future direction for research in this topic, we propose
to work on bijective codes for the class of partial k-trees.
Chapter 7
Counting k-Arch Graphs
The class of k-trees studied in the previous chapter can be further generalized
by relaxing the second constraint of Definition 6.1 asking for the node set
K to be a clique. Graphs belonging to this class, introduced by Todd [100],
are known as the k-arch graphs. Formally a k-arch graph can be defined as
follows:
1. A complete graph on k nodes is a k-arch graph.
2. If A′k = (V, E) is a k-arch graph, K ⊆ V of cardinality k and v ∈
/ V,
then Ak = (V ∪ {v}, E ∪ {(v, x) | x ∈ K}) is also a k-arch graph.
An attempt to count the number of labeled k-arch graphs has been made
by Lamathe [72]. He used on k-arch graphs the very same generalization of
the Prüfer code given by Rényi and Rényi [94] for k-trees (see Section 6.2).
Thus each k-arch graph is associated with a strings over the alphabet Σ =
[1,n]
. He claimed that this correspondence is a bijection and that the number
k
of labeled k-arch graphs on n nodes is:
n−k−1
n
k
Unfortunately this result is not correct, as many codewords do not represent any k-arch graph. We prove the flaw in Lamathe’s formula by showing a
simple counterexample (in Section 7.2). As a novel contribution of this thesis
107
108
CHAPTER 7. COUNTING K-ARCH GRAPHS
we provide the characterization of valid codeword and exploit it to define a
recursive function that computes the number of labeled k-arch graphs of n
nodes, for any given n and k [21].
This chapter is organized as follows: in Section 7.1 we explicitly recall
the generalization of the Prüfer code used by Lamathe on k-arch graphs. In
Section 7.2 we discuss the decoding procedure and characterize the set of
valid codewords. The main counting result is given in Section 7.3.
7.1
Encoding k-Arch Graphs
Let Ank be the set of all k-arch graphs of n nodes and let Bkn be the set of
all possible strings of length n − k − 1 over the alphabet [1,n]
. We use the
k
notation adj(v) to identify the set of all nodes adjacent to a given node v,
and the term k-leaf to mean a node u such that |adj(u)| = k; any other node
v has |adj(v)| > k and is called internal.
Let us define the following function:
ρ(Ank )
=
ε,
if Ank is a single k + 1 clique;
adj(min{v ∈ Ank : |adj(v)| = k}) :: ρ(Ank {v}),
otherwise.
The function ρ is the injective function between Ank and Bkn used by
Lamathe [72], i.e., the generalization made by Rényi and Rényi [94] of the
Prüfer bijection applied to k-arch graphs. The recursion described by ρ
operates a pruning of the k-arch graph Ank that starts from the smallest kleaf v; as v is removed from Ank , its adjacent set constitutes the first symbol of
the codeword. This symbol is concatenated (by string concatenation operator
::) to the string obtained by recursively applying the function to the pruned
graph. The recursion terminates when the pruning gives a clique on k + 1
nodes, as ρ applied to a clique gives the empty string ε.
Note that, by definition of k-arch graphs, every subgraph produced during
the pruning process is a k-arch graph.
It is worth to notice that we are assuming n > k, analogously the Prüfer
code assumes the tree to have at least 2 nodes. When n = k the only
7.1. ENCODING K-ARCH GRAPHS
109
Figure 7.1: A labeled 3-arch graph on 10 nodes.
admissible k-arch graph is a single clique then |Akk | = 1, when n < k obviously
|Ank | = 0.
Let us show an example of the encoding process realized by the function
ρ. Starting from the 3-arch graph of Figure 7.1 we prune it by recursively
removing the smallest k-leaf. At each step the set of nodes adjacent to the
removed k-leaf is added to the codeword.
The smallest k-leaf of the initial graph is v1 = 2 and its adjacent nodes
are B1 = {1, 6, 9}. Then node 2 is removed from the graph and the smallest
k-leaf in the resulting graph is v2 = 3 implying B2 = {1, 5, 8}. Iterating this
procedure we obtain v3 = 6, v4 = 4, v5 = 7, v6 = 9 and B3 = {4, 8, 10}, B4 =
{1, 5, 9}, B5 = {5, 8, 10}, B6 = {1, 5, 8} respectively. The remaining graph
is a single clique of 4 nodes {1, 5, 8, 10}. Therefore the resulting codeword
is (B1 , B2 , B3 , B4 , B5 , B6 ) = ({1, 6, 9}, {1, 5, 8}, {4, 8, 10}, {1, 5, 9}, {5, 8, 10},
{1, 5, 8}).
For a given k-arch graph Ank , we say a node v ∈ V (Ank ) appears in ρ(Ank )
if there exists Bi ∈ ρ(Ank ) such that v ∈ Bi .
Lemma 7.1. v is an internal node in Ank if and only if it appears in ρ(Ank ).
Proof. Consider an internal node v in Ank : its initial degree is strictly greater
than k. The pruning process operated by ρ ends with a (k + 1)-clique, where
each node has degree k: either v has been eliminated in some step or it
belongs to the remaining clique; in both cases its degree must decrease to k.
110
CHAPTER 7. COUNTING K-ARCH GRAPHS
Since the degree of an internal node v can decrease only if in some step i a
k-leaf adjacent to v is removed, v must belong to Bi .
Let us now show that if an element appears in ρ(Ank ), then it is an internal
node. Consider a k-leaf v, and suppose by contradiction that there exists
some value i such that v ∈ Bi . This means that after removing a k-leaf
on step i, in the resulting graph node v has degree k − 1. This contradicts
the fact that each subgraph produced during the encoding process is k-arch
graph.
Lemma 7.2. Function ρ is injective.
Proof. We have to show that, given two k-arch graphs Ank ′ and Ank ′′ , if
ρ(Ank ′ ) = ρ(Ank ′′ ) = (B1 , . . . , Bn−k−1) then Ank ′ = Ank ′′ .
Let us proceed by induction on n − k. If n − k = 1, ρ(Ank ′ ) = ρ(Ank ′′ ) = ε,
then Ank ′ = Ank ′′ as the only k-arch graph on k + 1 nodes is a (k + 1)-clique.
For inductive hypothesis, assume the thesis holds when n − k < h. We
have to prove that it holds when n − k = h.
In order to have ρ(Ank ′ ) = ρ(Ank ′′ ), for Lemma 7.1, the sets of internal
nodes and the sets of k-leaves in Ank ′ and Ank ′′ must coincide. It follows that
the minimum k-leaf v1 in Ank ′ coincides with the minimum k-leaf in Ank ′′ and
both are adjacent to the same node set B1 . Moreover, the graphs obtained
by pruning v1 from Ank ′ and Ank ′′ , in order to produce the same substring
(B2 , . . . , Bn−k−1), have to be the same graph by inductive hypothesis. This
implies Ank ′ = Ank ′′ , as removing the same node and the same edge set from
them we obtain the same graph.
7.2
Decoding k-Arch Graphs
In this section we show how to revert function ρ in order to rebuild an encoded
k-arch graph.
Starting from a codeword (B1 , . . . , Bl ) that is the encoding of an unknown
k-arch graph Ank , initially we need to recover values n and k: k = |B1 | =
7.2. DECODING K-ARCH GRAPHS
111
|B2 | = · · · = |Bl | and, since l = n − k − 1, we can derive n = l + k + 1.
The node set of Ank is [1, n] so, to complete the decoding process, we need to
reconstruct its edge set.
In view of Lemma 7.1 it is easy to derive the set of all k-leaves of Ank
as [1, n] Bi . We can compute v1 (the first k-leaf removed during the
encoding process) as the minimum of this set. We also know adj(v1 ) = B1 .
Now, v2 is the smallest k-leaf of Ank {v1 } and we know both the node set
of this k-arch graph (i.e., [1, n] {v1 }) and its codeword (B2 , . . . , Bl ). Then
v2 = min{v ∈ [1, n] {v1 } li=2 Bi }.
Generalizing this idea it is possible to derive a formula analogous to the
one given by Prüfer for trees:
vi = min v ∈ [1, n] {vh }h<i
Bj
∀i ∈ [1, l]
(7.1)
j≥i
Knowing the k-leaf removed at each step of the encoding process it is easy
to rebuild the edge set of Ank . Indeed, all the k + 1 nodes not in {v1 , . . . , vl }
form a clique and each vi should be connected with all nodes in Bi . We will
refer to this decoding process as ρ−1 . It is easy to see that the codomain of
ρ−1 is Ank .
Not all strings in Bkn are eligible for this decoding procedure. Indeed,
ρ−1 implicitly requires that, at each step i, the set from which each vi is
chosen (Equation 7.1) must be not empty. We now show a simple string
not corresponding to the encoding of any k-arch graph: this is the easiest
counterexample that proves the incorrectness of Lamathe’s formula.
Consider the string ({1, 2}, {3, 4}, {5, 6}): in this case k = 2 and n =
3 + 2 + 1 = 6. Since the set [1, 6] ({1, 2} ∪ {3, 4} ∪ {5, 6}) is empty, there
is no value for v1 , so there can not exist any k-arch graph whose encoding is
({1, 2}, {3, 4}, {5, 6}).
It is quite easy to see, from definition of ρ−1 , that ρ−1 (ρ(Ank )) = Ank for
each k-arch graph Ank . We now characterize all those strings in Bkn resulting
by the encoding of some k-arch graph. Let us call the set of these strings
112
CHAPTER 7. COUNTING K-ARCH GRAPHS
Ckn ⊆ Bkn . Notice that Ckn is the image of Ank under function ρ, i.e., Ckn = ρ(Ank ).
Theorem 7.3. Given (B1 , . . . , Bl ) ∈ Bkn if ∃{v1 , . . . , vl } ∈
vi ∈
/ lj=i Bj then (B1 , . . . , Bl ) ∈ Ckn .
[1,n]
l
such that
Proof. The existence of such a sequence {v1 , . . . , vl } ∈ [1,n]
ensures that the
l
decoding process can be applied successfully, but this is not enough to ensure
(B1 , . . . , Bl ) ∈ Ckn . Indeed there is a reasonable doubt that the k-arch graph
Ank = ρ−1 (B1 , . . . , Bl ) obtained by decoding an arbitrary string in Bkn , can
produce a different string (B1′ , . . . , Bl′ ) = ρ(Ank ) when encoded, thus implying
(B1 , . . . , Bl ) ∈
/ Ckn . We will show this is not the case.
Without loss of generality assume that v1 , . . . , vl coincides with the sequence of nodes chosen by ρ−1 at each step during the decoding process.
Now, by induction on l, we prove that ρ(ρ−1 (B1 , . . . , Bl )) = (B1 , . . . , Bl ).
When l = 0, the string can only be ε, the resulting graph is a (k + 1)clique and its encoding is again ε. We assume, by inductive hypothesis, the
thesis holds for any string of length l < h and we prove it holds for strings of
length l = h. First note that if the string (B1 , . . . , Bl ) is decoded as the k-arch
graph Ank , then the substring B2 , . . . , Bl is decodable and results in the graph
An−1
= Ank {v1 }. By inductive hypothesis ρ(An−1
) = (B2 , . . . , Bl ) (here
k
k
the node set does not contain v1 ). The degree of v1 in Ank is |B1 | = k, so it is
a k-leaf. Any other node with label smaller than v1 appears in (B1 , . . . , Bl ),
as otherwise ρ−1 would have done a different choice for v1 . This implies
that v1 is the minimum k-leaf in Ank . Then ρ(Ank ) = adj(v1 ) :: ρ(An−1
) =
k
(B1 , . . . , Bl ).
Since in the proof of Theorem 7.3 we proved that ρ(ρ−1 (B1 , . . . , Bl )) =
(B1 , . . . , Bl ) for each codeword in Ckn , we can state that ρ−1 : Ckn → Ank is
exactly the inverse function of ρ : Ank → Ckn .
7.3
Enumerating k-Arch Graphs
We are interested in finding the number of k-arch graphs on n nodes, i.e.,
|Ank |. Since |Ank | = |Ckn |, in order to count labeled k-arch graphs we will count
7.3. ENUMERATING K-ARCH GRAPHS
113
Figure 7.2: Recursion tree for counting 3-arch graphs on 7 nodes.
valid codewords. The condition for a string (B1 , . . . , Bl ) to be a valid codeword of a k-arch graph (stated in Theorem 7.3) can be easily reformulated
as:
l
∀i : 1 ≤ i ≤ l, |
Bh | ≤ n − i
(7.2)
h=i
Exploiting Equation 7.2, it possible to define a recursive function to count
the number of labeled k-arch graphs on n nodes. Before providing this general
formula, let us show an example of our approach applied to |C37 |.
The basic idea is to simulate the generation of a valid codeword (B1 , B2 ,
B3 ), from right to left, and count how many choices we have at each step. The
choice for B3 gives 73 alternatives, as Equation 7.2 requires that no more
than 4 different numbers appear in substring (B3 ); this substring always
contains 3 distinct numbers, then the requirement is always satisfied.
Now consider B2 . Equation 7.2 requires at most 5 distinct numbers to
appear in substring (B2 , B3 ), thus imposing some limits on the choices for
B2 . In fact valid choices are those selecting 3, 2 or 1 numbers appearing
in B3 and respectively 0, 1 or 2 unused numbers, giving 33 40 , 32 41 and
3 4
distinct alternatives. Similar arguments hold for B1 and constraints
1 2
depend on how many distinct numbers appear in (B2 , B3 ). More explicitly,
since Equation 7.2 imposes to have at most 6 distinct numbers, if u distinct
numbers appear in (B2 , B3 ), then B1 can introduce up to min(3, 6−u) unused
numbers.
Figure 7.2 gives the complete recursion tree for the described process.
The root represents choices for B3 ; children of the root represent choices for
114
CHAPTER 7. COUNTING K-ARCH GRAPHS
B2 and leaves represent choices for B1 . For each level, on the left the bound
given by Equation 7.2 is reported; labels on edges represent how many new
numbers are introduced. |C37 | = 34405 is given by the sum of the products of
labels given by each leaf-to-root path in the tree:
7
3
3
3
4
0
3
2
4
1
3
1
4
2
3
3
4
0
+
3
2
4
1
4
3
3
0
+
4
2
3
1
5
3
2
0
+
5
2
2
1
3
1
4
2
+ 41
3
2
+
+
+
3
0
4
3
+
Now we introduce our main result on k-arch graphs.
Theorem 7.4. The number of labeled k-arch graphs on n > k + 1 nodes is
|Ank | = fkn (n − k − 1, 0, k) where fkn is the recursive function defined as:
⎧ n−u u
⎪
,
if i = 1;
⎪
j
k−j
⎪
⎨
min(k,n−(i−1)−(u+j))
fkn (i, u, j) =
⎪
n−u
u
⎪
fkn (i − 1, u + j, c), otherwise.
⎪
⎩ j k−j
c=0
When n = k or n = k + 1 we have |Ank | = 1; when n < k we have |Ank | = 0.
Proof. Given the string (B1 , . . . , Bl ) ∈ Ckn , we call characteristic of this string
the vector c = (c1 , . . . , cl−1 ) such that ci = |Bi j>i Bj |, i.e., the number
of elements in Bi that do not appear in the substring (Bi+1 , . . . , Bl ).
Consider the recursion tree generated by applying the function fkn to
(n − k − 1, 0, k). This tree is a generalization of the one presented in Fig. 7.2
for the special case n = 7 and k = 3: node labels correspond to the binomials product and edge labels correspond to the value of the variable c
discriminating recursive applications of function fkn .
Notice that, considering the edge labels in any leaf to root path of this
tree, we obtain a vector (c1 , . . . , cn−k−2) which represents the sequence of
newly inserted numbers (from right to left), and so it coincides with the
characteristic of some string in Ckn . It is also true that if c is the characteristic
7.3. ENUMERATING K-ARCH GRAPHS
115
of a string in Ckn , then a leaf to root path whose edge labels vector is c must
exist.
|Ckn | can be obtained by summing cardinalities of disjoint sets of strings
sharing the same characteristic. The size of any such set is given by the
product of node labels following the corresponding leaf to root path in the
recursion tree. By summing those products, we thus obtain |Ckn |, i.e., the
value computed by fkn (n − k − 1, 0, k).
7.3.1
Experimental Results
We implemented the recursive function to enumerate the labeled k-arch
graphs on n nodes using the open source algebraic system PARI/GP [31].
The code performing the counting is given in Figure 7.3.
f(n,k,i,u,j)={
local(s=0);
if (i==1,
binomial(n-u,j)*binomial(u,k-j),
for (c=0, min(k,n-(i-1)-(u+j)),
s+=f(n,k,i-1,u+j,c)
);
binomial(n-u,j)*binomial(u,k-j)*s
)
}
Figure 7.3: PARI/GP code implementing the recursive function fkn .
The size of the recursion tree is exponential in the order of (k + 1)n−k−2
so the value can only be computed if the difference between n and k is small.
As done by Lamathe we report values of |Ank | for n ∈ [1, 10] and k ∈ [1, 7] in
the following table:
116
k\n
1
2
3
4
5
6
7
CHAPTER 7. COUNTING K-ARCH GRAPHS
1
1
0
0
0
0
0
0
2
1
1
0
0
0
0
0
3
3
1
1
0
0
0
0
4
16
6
1
1
0
0
0
5
125
100
10
1
1
0
0
6
1296
3285
380
15
1
1
0
7
16807
177471
34405
1085
21
1
1
8
262144
14188888
5919536
216230
2576
28
1
9
4782969
1569185136
1709074584
92550276
982926
5376
36
10
100000000
229087571625
764754595200
74358276300
898027452
3568950
10200
The first row of this table gives exactly the well known Cayley’s formula, as
1-arch graphs are trees. Apart from this row (reported as Sequence A000272)
no other row of the table was listed in the on-line Encyclopedia of Integer
Sequences [98] before our work.
7.4
Concluding Remarks
In this chapter we have presented a recursive function that computes the
number of labeled k-arch graphs of n nodes, for any given n and k. In order
to obtain this function, we have used a code that maps labeled k-arch graphs
to strings and we have derived the counting function by characterizing valid
code strings. Moreover, we have proved the counting function for k-arch
graphs provided by Lamathe to be incorrect by showing a counterexample.
Unfortunately this result does not help us in developing a bijective code
for k-arch graphs. It remains an open problem to find, provided that it exists,
a closed formula for the number of k-arch graphs |Ank |, when k > 1. When
k = 1, from Cayley’s formula, we have |An1 | = nn−2 . Furthermore, it would
be interesting to investigate k-arch graphs with fixed or arbitrary root.
Chapter 8
Informative Labeling Scheme
for LCA on Dynamic Trees
In this chapter we abandon the idea of labels as simple unique identifiers of
nodes, as seen in all previous chapters. Here we investigate a richer concept
of label that makes it possible to perform computations directly from node
labels: Informative Labeling Scheme (ILS). We focus on ILS for trees. As
the idea of ILS naturally realizes a localization of the information required
to perform a computation, we decided to exploit this concept to design concurrent data structures. As a first example we focus on the lowest common
ancestor (lca) problem for dynamic trees.
Namely, our goal is to associate each node of a tree with a label such
that it will be possible to compute the lowest common ancestor of any two
nodes directly from their labels. Moreover it should be reasonably efficient
to update the labels in order to reflect changes in the tree. We will also
use locking system to allow multiple processors to access the data structure
simultaneously.
In the second part of this chapter we will present an experimental comparison between our data structure and an ILS for lowest common ancestor
queries introduced by Peleg for static trees [87].
This chapter is organized as follows: initially we recall the concept of Informative Labeling Scheme and describe the concurrency model we consider
117
118
CHAPTER 8. ILS FOR LCA ON DYNAMIC TREES
for our data structure. Then we recall the ILS introduced by Peleg for answering lca queries. In Section 8.3 we introduce our ILS for lca on dynamic
trees while in Section 8.4 we describe how to use it in a concurrent setting.
Finally, in Section 8.5, we present our experimental comparison.
8.1
Preliminaries
An Informative Labeling Scheme (ILS) [87, 88] for a target function f is
formally defined as a couple of algorithms (M, D). The Marker Algorithm
M, given a graph G, computes a label(v), for each node v in G. The Decoding
Algorithm D is then used to compute, for each pair of nodes u, v in G
the target function f (u, v) using only label(u) and label(v). In other words
D(label(u), label(v)) = f (u, v). The primary goal of a labeling scheme is to
minimize the maximum label length, while keeping queries fast.
Adjacency labeling schemes were first introduced by Breuer and Folkman
in [14, 15], and further studied in [63]. The interest in informative labeling schemes, however, was revived only more recently, after Peleg showed
the feasibility of the design of efficient labeling schemes capturing distance
information [86]. Since then, upper and lower bounds for labeling schemes
have been proved on a variety of graph families (including weighted trees,
bounded arboricity graphs, intersection-based and c-decomposable graphs)
and for a large variety of queries, including distance [2, 30, 49, 50, 64, 67, 99],
tree ancestry [1, 3], flow and connectivity [66].
We focus on labeling schemes for answering least common ancestor queries
in trees. We recall that the least common ancestor of any two tree nodes u
and v, denoted as lca(u, v), is the common ancestor of u and v having the
smallest distance to u (and to v). The least common ancestor problem has
been extensively studied over the last three decades in different models of
computation [4, 7, 33, 34, 58, 97]. Labeling schemes for least common ancestors are mainly useful in routing messages on tree networks: the ability to
compute the identifier of the least common ancestor of any two nodes u and
v turns out to be useful when a message has to be sent from u to v in the
network, because the message has to go through lca(u, v). Other applica-
8.1. PRELIMINARIES
119
tions are related to query processing in XML search engines and distributed
computing in peer-to-peer networks (see, e.g., [3, 11, 65]). In [87], Peleg has
proved that for trees there exists a labeling scheme for least common ancestors using Θ(log2 n)-bit labels, which is also shown to be asymptotically
optimal (as usual n represents the number of nodes in the tree).
In spite of a large body of theoretical works, to the best of our knowledge only few experimental investigations of the efficiency of compact labeling
schemes have been addressed in the literature [30, 65]. For this reason we decided to enrich our study with an experimental comparison (see Section 8.5).
As stated in the introduction of this chapter we exploit ILS to design a
concurrent data structure for lca on dynamic trees. The tree is dynamic in
the sense that we allow it to grow by insertion of new leaves.
Our data structure will implement two operations:
Query: given any two tree nodes u and v, compute their lowest common
ancestor lca(u, v);
Update: given a tree node p, add to the tree a new node v as a child of p.
8.1.1
The Concurrency Model
Throughout this chapter we focus on a multiprocessor system in which processors communicate by writing and reading shared variables in a common
memory address space. We assume that processors work asynchronously and
that each processor has its own local (i.e., non shared) memory. Moreover, in
order to use timestamps associated to shared variables, we assume that processors share a common clock: this is typically not a restrictive assumption
in a multiprocessor system.
We will use locking primitives to guarantee concurrent access to our data
structures. In particular, we will analyze our data structures under the concurrent read, exclusive write (CREW) model by using two different kinds of
locks: shared and exclusive. Several processes can hold shared locks on a
variable simultaneously, whereas if one process holds an exclusive lock, then
120
CHAPTER 8. ILS FOR LCA ON DYNAMIC TREES
no other process may hold any lock on that variable.
For more information about concurrent data structures, locking primitives
and related matters we refer the interested reader to [76].
We remark that concurrent data structures are much more difficult to
design and analyze than sequential ones: indeed, in the design of a concurrent data structure, the goal is to allow concurrent operations to proceed in
parallel when they do not access the same parts of the data structure. On
the other side, the presence of locks introduces a sequential bottleneck on the
execution of the operations, thus decreasing the speedup. This bottleneck
can be reduced by using a fine-grained locking scheme such that multiple
locks of small granularity can be introduced to protect different parts of the
data structure.
Correctness analysis. In order to prove that a data structure is correct
under concurrent operations, we will show that in every execution there exists
a total ordering of the operations with the following properties: (1) the
ordering is consistent with the desired insert/search semantics, and (2) if one
operation completes before another begins, then the first operation precedes
the second one in the ordering.
8.2
Peleg’s Labeling Scheme
In [87], Peleg has proved that for trees there exists a labeling scheme for least
common ancestors using Θ(log2 n)-bit labels. Peleg’s labeling scheme hinges
upon two main ingredients: a decomposition of the tree into paths, and a
suitable encoding of information related to such paths into the node labels.
Peleg’s data structure uses an ad hoc path decomposition as well as an ad
hoc label structure.
Let T be a tree with n nodes rooted at a given node r. As usual, for any
node u we denote its parent and its level in T by p(u) and l(u), respectively
(the root has level 0). The tree is decomposed into a set of node disjoint
paths, that we will call solid paths. For any solid path π, we denote by
8.2. PELEG’S LABELING SCHEME
121
head(π) the node of π with smallest level. We will also say that a solid path
π is an ancestral solid path of a node u if head(π) is an ancestor of u.
Decomposition by Large Child. This decomposition hinges upon the
distinction between small and large nodes: a nonroot node v with parent u
is called small if |Tv | ≤ |Tu |/2, i.e., if its subtree contains at most half the
number of nodes contained in its parent’s subtree. Otherwise, v is called
large. It is not difficult to see that any node has at most one large child: we
will consider the edge to that large child, if any, as a solid edge. Solid edges
induce a decomposition of the tree into solid paths: we remark that the head
of any solid path π is always a small node, while all the other nodes in π
must be large. Each node can have at most ⌈log n⌉ small ancestors, and thus
at most ⌈log n⌉ ancestral solid paths (unless otherwise stated, all logarithms
will be to the base 2).
The Marker Algorithm Mp . The Peleg labeling scheme is based on a
depth-first numbering of the tree T : as a preprocessing step, each node v
is assigned an interval Int(v) = [DF S(v); DF S(w)], where w is the last
descendent of v visited by the depth-first tour and DF S(x) denotes the
depth-first number of node x. The label of each node v of the tree is defined
as follows:
label(v) = < Int(v), list(v) >
where list(v) contains information related to all the heads (t1 , t2 , . . . , th ) of
solid paths from the root of T to v: for each head ti , list(v) contains a
quadruple (ti , l(ti ), p(ti ), succv (ti )), where succv (ti ) is the unique child of ti on
the path to node v. We remark that this is slightly different (and optimized)
with respect to the scheme originally proposed in [87].
The Decoder Algorithm Dp . We now describe how to query for a lowest
common ancestor: given two nodes u and v, the algorithm infers their least
common ancestor z = lca(u, v) using only information contained in label(u)
and label(v). By well-known properties of depth-first search, we have that
for every two nodes x and y of T , Int(x) ⊆ Int(y) if and only if x is a
descendent of y in T : this fact can be easily exploited to check whether
122
CHAPTER 8. ILS FOR LCA ON DYNAMIC TREES
the least common ancestor z coincides with any of the two input nodes u
and v. If this is not the case, let (u1 , u2 , . . . , uh ) and (v1 , v2 , . . . , vk ) be the
heads of solid paths from the root of T to u and v, respectively: information
about these heads is maintained in the node labels. The algorithm finds the
least common ancestor head h, which is identified by the maximum index i
such that ui = vi . If succu (h) = succv (h), then h must be the least common
ancestor. Otherwise, the algorithm takes the node of minimum level between
ui+1 and vi+1 , and returns its parent as the least common ancestor. We refer
to [87] for a formal proof of correctness. Here, we limit to remark that both
depth-first numbering and information about successors appear to be crucial
in this algorithm.
8.3
A Dynamic Sequential Data Structure
The Peleg’s labeling scheme is not suitable for dynamic trees, indeed it hinges
upon the depth-first numbering of the tree nodes. The insertion of a new node
can affect the depth-first numbering of many other nodes. This implies that
at each insertion a relevant part of the data structure needs to be recomputed.
Our data structure relays on the concept of ILS and, similarly to the
Peleg’s one, is based on a decomposition of the tree into solid path. The
decomposition we use is the same used in [71] and is described below. Moreover, in order to avoid depth-first numbering, we maintain slightly different
information in node labels.
We first present a sequential data structure for maintaining lowest common ancestors upon insertions of new tree nodes and we discuss its correctness. Although not optimal in a sequential setting, this data structure is
well suited for an efficient concurrent implementation, as we will show in
Section 8.4.
8.3. A DYNAMIC SEQUENTIAL DATA STRUCTURE
8.3.1
123
Tree Decomposition
For each edge (u, v), we call the edge solid if and only if ⌈log |Tu |⌉ = ⌈log |Tv |⌉.
We will also say that v is a solid child of u. It is not difficult to see that solid
edges decompose T into solid paths, also known as centroid paths [33]. We
will say that a solid path π has rank i if, for all nodes v belonging to π, the
size of the subtree rooted at v satisfies the following inequality:
2i ≤ |Tv | < 2i+1
(8.1)
solid paths univocally partition the tree into disjoint paths, as proved in the
following lemma:
Lemma 8.1. For any node u there exists at most one child v such that (u, v)
is solid.
Proof. Assume by contradiction that both (u, v1 ) and (u, v2) are solid, where
v1 and v2 are distinct children of u such that, without loss of generality,
|Tv2 | ≥ |Tv1 |. Then it must be ⌈log |Tv1 |⌉ = ⌈log |Tv2 |⌉. Since |Tu | ≥ |Tv1 | +
|Tv2 | ≥ 2|Tv1 |, we have ⌈log |Tu |⌉ ≥ ⌈log |Tv1 |⌉ + 1. This contradicts the
hypothesis that (u, v1 ) is solid.
We will refer to the partition induced by solid paths as solid path decomposition. For any solid path π, we denote by head(π) and tail(π) the
node of π with smallest and largest level, respectively. The root of the tree
is always head of a solid path. Notice that a solid path can have length 0
(i.e., it can consist of a single node) and that all the leaves are heads of solid
paths. We will say that a solid path π is an ancestral solid path of a node
u if head(π) is an ancestor of u, and that π is an ancestral solid path of a
path π ′ if head(π) is an ancestor of head(π ′ ). The following property easily
follows from Equation 8.1:
Property 8.2. Each node u has at most ⌈log n⌉ ancestral solid paths.
Similarly to [71], we now introduce relaxed solid paths: these paths will be
useful to deal with dynamic trees in order to avoid frequent recomputations
124
CHAPTER 8. ILS FOR LCA ON DYNAMIC TREES
of the decomposition upon insertions of new nodes. We allow a node v to
belong to a relaxed solid path of rank i, with i ≥ 2, if the size of the subtree
rooted at v satisfies the following inequality:
2i ≤ |Tv | < 2i+1 (1 + α)
(8.2)
where α is an arbitrary constant such that 0 < α < 1. We will call a node
stable (with respect to rank i) if it satisfies Inequality (8.1), and unstable (with
respect to rank i) if it satisfies Inequality (8.2) but not Inequality (8.1).
We remark that relaxed solid paths do not univocally induce a partition
of the tree. Indeed, it may be the case that an unstable node u in a path
π of rank i has two children that satisfy Inequality (8.2) with respect to i:
both of these children could therefore belong to π, that would be no longer
a path. We will break these ties by including in π at most one of these two
children, and by considering the other child as head of a different path π ′
also of rank i. Namely, if only one of the two children is stable, we choose
it as head of π ′ . Otherwise, we break the tie arbitrarily. We remark that u
might also have more than two children: however, since it belongs to a path
of rank i, at most two of its children can have size large enough to satisfy
Inequality (8.2).
This tie breaking approach, however, introduces a different problem: more
than one path of rank i may now appear in the path from the root to any
given node, thus invalidating Property 8.2. We will now show that this is
not a real problem, since the number of ancestral (relaxed) solid paths of any
node can at most double.
Lemma 8.3. Let u be a node in a relaxed solid path of rank i, and let v and
w be two children of u both satisfying Inequality (8.2) with respect to i. Then
u and at most one between v and w is unstable with respect to i.
Proof. We first notice that u satisfies Inequality (8.2) because it belongs to
a relaxed solid path of rank i. Since v and w satisfy Inequality (8.2) by
hypothesis, we have |Tu | > |Tv | + |Tw | ≥ 2i+1 . Hence, u is unstable. We now
consider v and w. If both of them are unstable, then their subtree sizes would
be larger than or equal to 2i+1 and it would be |Tu | > 2i+2 . This contradicts
the hypothesis that u belongs to a relaxed solid path of rank i.
8.3. A DYNAMIC SEQUENTIAL DATA STRUCTURE
125
Corollary 8.4. Let π and π ′ be any two solid paths of rank i such that π is
an ancestral path of π ′ . Then no other solid path can exist between π and π ′ ,
i.e., no other solid path π ′′ such that π is an ancestral path of π ′′ and π ′′ is
an ancestral path of π ′ can exist.
Proof. Let us assume by contradiction that there exists a path π ′′ such that
π is an ancestral path of π ′′ and π ′′ is an ancestral path of π ′ . Let v be
the head of π ′′ and let u be its parent. It is easy to see that it must be
rank(π) ≥ rank(π ′′ ) ≥ rank(π ′ ). This implies that rank(π ′′ ) = i and that u
belongs to a path of rank i. In the following, without loss of generality we
will assume that the solid path of node u coincides with π.
Since rank(π) = i = rank(π ′′ ), then π ′′ must be the result of an application of the tie breaking rule. By Lemma 8.3, u is unstable. Let w be the
child of u in π. Lemma 8.3 guarantees that, if w is unstable, then v must
be stable. If w is stable, then u cannot be unstable: if this is not the case,
then the tie breaking rule would have included v in π instead of w. In both
cases, v is stable. This implies that no tie breaking can take place below it,
contradicting the existence of π ′ .
By Corollary 8.4, we can have at most two consecutive paths with the
same rank. Hence, the tie breaking rule implies that the number of (relaxed)
ancestral solid paths of any node can at most double, i.e.:
Property 8.5. Each node u has at most 2⌈log n⌉ ancestral relaxed solid
paths.
We remark that in [71] the same problem related to relaxed solid paths
has been encountered and solved in a different way: our solution is simpler
and still allows us to design an ILS whose labels size is O(log2 n) bits.
8.3.2
The Data Structure
For each node v of a tree T , we maintain its parent, the list of all its children,
and a pointer to its solid child (if any). We also maintain a label defined as
126
CHAPTER 8. ILS FOR LCA ON DYNAMIC TREES
follows:
label(v) = < isHead(v), list(v) >
where isHead(v) is a Boolean value discriminating whether v is the head
of its relaxed solid path or not, and list(v) contains information related to
all the heads (t1 , t2 , . . . , th ) of relaxed solid paths from the root of T to v.
Namely, list(v) consists of a sequence of triples:
list(v) = [ (t1 , l(t1 ), p(t1 )), . . . , (th , l(th ), p(th )), (v, l(v), p(v)) ]
where t1 always coincides with the root of T . The sentinel triple (v, l(v), p(v))
is not necessary when v is head of its path, since in this case th = v.
For each v head of a relaxed solid path π, we also explicitly maintain
in our data structure the size of its subtree |Tv | and the rank of its path
rank(π).
We now describe the implementation of the update and query operations.
Update. Each time a new node v is added to T as a child of a node u,
besides updating the children list of u, we compute label(v) as follows:
< true, list(u) :: (v, l(u) + 1, u) >
if isHead(u)
label(v) =
< true, list(u) (u, l(u), p(u)) :: (v, l(u) + 1, u) > otherwise
where :: concatenates a new triple to a list and is used to remove the
sentinel triple of list(u) when u is not head of its solid path. We remark
that the new leaf v is always head of a solid path of rank 1, since the relaxed
Inequality (8.2) only applies to paths of rank at least 2.
Due to the addition of node v, the subtree size of its ancestors changes,
and some ancestor may no longer satisfy Inequality (8.2). This implies that
the decomposition into relaxed solid paths and the labels of some nodes may
need to be updated. We now describe how to tackle this problem.
After label(v) has been computed, the size |Tti | of the subtree rooted at
ti is incremented by one for each head ti in list(v). Let t be the topmost
head that no longer satisfies Inequality (8.2), if any. The idea is to move t
upward in the decomposition so that, after the update is completed, it will
8.3. A DYNAMIC SEQUENTIAL DATA STRUCTURE
127
belong to a relaxed solid path of rank i = ⌈log |Tt |⌉. At this aim, let π ′ be
the relaxed solid path above t: notice that p(t) belongs to π ′ . We can easily
find the head of π ′ at the end of list(t). By accessing the data associated to
head(π ′ ), we can also retrieve rank(π ′ ). Three cases may now arise:
1. If rank(π ′ ) > i, then t becomes the head of a new solid path of rank i.
2. If rank(π ′ ) = i and p(t) has no solid child, then t can join π ′ .
3. If rank(π ′ ) = i and p(t) already has a solid child, this prevents t from
joining π ′ and t will be the head of a new relaxed solid path of rank i.
We notice that the second case is a correct implementation of the tie breaking
rule described in Section 8.3.1: indeed, t is stable with respect to rank i =
⌈log |Tt |⌉.
After t has been correctly placed in a relaxed solid path, we recompute
from scratch all the information related to t and to its subtree: the labels,
the size of the subtree and the rank of each head in Tt , and the decomposition
into solid paths. While recomputing the decomposition, we force each node
to respect Inequality (8.1); this guarantees that all the unstable nodes that
were in the same path of node t before the update will be moved upward in
the decomposition together with t. We defer to Section 8.3.3 the proof of
correctness of this approach. We will also show that each update requires
O(log2 n) amortized running time.
We remark that, considering our data structure as an ILS, this update
procedure plays the role of the Marker algorithm. The main difference is
that a Marker Algorithm is ran only once, as a single precomputation step,
while the update takes place each time a new node is inserted in the tree.
Query. Our query algorithm computes the lowest common ancestor lca(u, v)
of any two nodes u and v as follows (tui and tvi denote the i-th head in list(u)
and list(v), respectively):
Lines 1 and 2 consider some trivial cases: either u = v or one of them
coincides with the root. In line 3 the algorithm finds the lowest head tk which
128
CHAPTER 8. ILS FOR LCA ON DYNAMIC TREES
Program 1 Query(u,v)
1. if u = v then return u
2. if u = r or v = r then return r
3.
Let k = max{i : tui = tvi } and call tk = tuk = tvk
4.
if u = tk or v = tk then return tk
5.
6.
if tuk+1 = u and not isHead(u) then cu = u
else cu = p(tuk+1 )
7.
8.
if tvk+1 = v and not isHead(v) then cv = v
else cv = p(tvk+1 )
9.
10.
if l(cu ) < l(cv ) then return cu
else return cv
is ancestor of both u and v: line 3 can be easily implemented by scanning
list(u) and list(v) together until the first different head is found. If neither
u nor v coincides with the last common head (trivial case handled by line 4),
we search lca(u, v) in the relaxed solid path π with head tk . Namely, the
algorithm identifies two candidates cu and cv and returns the highest of them.
Notice that node tuk+1 is either the sentinel of list(u) or the head following
tk in list(u): in the former case (line 5 and Figure 8.1a) the candidate cu is
u itself, while in the latter case (line 6 and Figure 8.1b) the candidate is the
parent of tuk+1 . The candidate cv is computed similarly and the algorithm
returns the highest level node among cu and cv . We defer to Section 8.3.3 a
formal proof of correctness.
We remark that all the information used by the algorithm are taken from
label(u) and label(v). Then the query procedure is indeed a Decoder algorithm for an ILS.
8.3.3
Analysis
We will now prove that each update maintains a correct decomposition into
relaxed solid paths in O(log2 n) amortized running time and each query correctly computes the lowest common ancestor of any two nodes in O(log n)
worst case running time.
8.3. A DYNAMIC SEQUENTIAL DATA STRUCTURE
129
Update. The insertion of a new node v as a child of a node u requires
adding v to T , computing label(v) and updating the decomposition into
relaxed solid paths. Adding v to T requires constant time in order to update
the children list of u. Computing label(v) requires O(log n) worst case time
since list(u) must be copied and slightly modified. We recall that (from
Property 8.5) list(u) contains O(log n) integer values.
The most demanding step is to deal with changes in the path decomposition. The algorithm first scans list(v) in order to identify all the heads ti
in the path from the root r down to v and to increment |Tti | by one: since
there are at most 2⌈log n⌉ such heads, this requires O(log n) time. Whenever
a head t that no longer satisfies Inequality (8.2) is found, t is moved upward
in the path decomposition and everything in Tt is recomputed: let us call
this operation a rebuild of Tt .
Retrieving the head of π ′ and its rank, discriminating which case applies,
and updating the path decomposition, require only constant time. The rebuild of Tt requires instead O(|Tt| log n) time: a traversal of Tt is performed
recomputing the size of each subtree and the label of each node from the
label of its parent (this requires O(log n) time by the upper bound on the
label length). Let us now discuss how to amortize this cost on each update.
Let π be any solid path of rank j that has been created during some
rebuild. We recall that the algorithm forces the head of π to satisfy Inequality 8.1 with respect to j. Hence, before a new rebuild takes place again at
head(π), at least α · 2j+1 new nodes must have been added to its subtree.
We amortize the cost of this new rebuild (i.e., O(2j+1(1 + α) log n)) over
these insertions as follows: each insertion pays O((1 + 1/α) log n) = O(log n)
credits for path π. Since, by Property 8.5, each node has O(log n) ancestral
relaxed paths, its insertion will pay in total O(log2 n) credits to amortize future rebuilds of all its ancestral paths. This proves that each update requires
O(log2 n) amortized running time.
Query. We first prove that the algorithm correctly finds the lowest common
ancestor of any two nodes u and v. Apart from trivial cases handled by
lines 1 and 2, in line 3 the algorithm identifies tk , which is the lowest head
130
CHAPTER 8. ILS FOR LCA ON DYNAMIC TREES
a)
b)
Figure 8.1: Two cases of query(u, v): tk is the lowest common head of u and v
and cv is the parent of tvk+1 . a) u ∈ π: there are no heads following tk in list(u)
(i.e., tuk+1 = u is the sentinel at the end of list(u) and it is not a head). In this
case cu = u; b) u ∈ π: in this case cu = p(tuk+1 ).
of a relaxed solid path appearing both in list(u) and in list(v). It is not
difficult to see that tk is a common ancestor of u and v, but not necessarily
the lowest one. Moreover, lca(u, v) must belong to π, the relaxed solid path
whose head is tk : otherwise, there should have been another head, lower than
tk , appearing both in list(u) and in list(v).
If either u or v coincides with tk , then it must be tk = lca(u, v): this case
is considered in line 4. Otherwise, the algorithm identifies two candidates cu
and cv , both in π, and returns the highest of them as the lowest common
ancestor. The test in line 5 succeeds if and only if u ∈ π (see Figure 8.1a):
this case can be identified by checking if there are no other heads of relaxed
solid paths between u and tk (tuk+1 = u, i.e., the scan has reached the sentinel
triple) and if u itself is not a head (otherwise it would not be in π). In this
case the candidate cu = u. If u ∈ π (see Figure 8.1b), line 6 chooses the
candidate cu as the parent of tuk+1 (the first head below tk in list(u)). This
candidate is the lowest node belonging to π in the ascending path from u to
tk . We compute cv analogously.
Once we have identified the two candidates cu and cv , we compare their
8.4. A CONCURRENT IMPLEMENTATION
131
levels in order to select the highest one. This node is ancestor of both u and
v. Let us assume without loss of generality that the selected candidate is
cv (the other case is symmetric). If cv = v, there cannot be any common
ancestor of u and v below v itself. If cv = v, then the node below cv in
the path to v (i.e., tvk+1 ) is not an ancestor of u because it does not appear
in list(u). This proves that the selected candidate is the lowest common
ancestor of u and v.
We now discuss the running time. Scanning the node lists (line 3) is the
most expensive operation in the query algorithm described in Section 8.3.2.
Since list lengths are bounded by 2⌈log n⌉ (Property 8.5), the query algorithm
requires O(log n) time in the worst case.
8.4
A Concurrent Implementation
In this section we describe how to exploit the data structure presented in
Section 8.3 in order to obtain a concurrent data structure for lowest common
ancestor in dynamic trees.
Each label label(v) has its own timestamp timestamp(v) that represents
last time the label has been computed or updated. Timestamps are especially
useful when answering queries in order to check whether the information
contained in the two node labels are reciprocally consistent.
Update. The update procedure used to add a new node v as a child of u is
detailed in Program 2. Writing locks are exclusive, all other locks are shared
to maximize concurrency. children(u) represents the children list of node u.
Initially, the new node is added to its parent children list and its label
is created starting from the label of its parent (lines 4 to 11). Subsequently,
subtree sizes are incremented for all head in list(v). Notice that t, tsv , and
tst are local (non shared) variables; also list(v) used in line 12 is a local copy
of the information contained in label(v). If condition in line 16 is true, this
means that a rebuild took place after v has been added. Then, everything
in this subtree has been recomputed. Otherwise, the size update continues
132
CHAPTER 8. ILS FOR LCA ON DYNAMIC TREES
Program 2 ConcurrentUpdate(v,u)
1. Acquire Exclusive Lock on children(u)
2. Add v to children(u)
3. Release Exclusive Lock on children(u)
4.
5.
6.
7.
8.
9.
10.
11.
Create a label for v
Acquire Exclusive Lock on label(v)
Acquire Shared Lock on label(u)
Compute label(v) from label(u)
timestamp(v) = timestamp(u)
Release Shared Lock on label(u)
Let tsv = timestamp(v)
Release Exclusive Lock on label(v)
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
for each t in list(v) do
Acquire Shared Lock on label(t)
Let tst = timestamp(t)
Release Shared Lock on label(t)
if tst > tsv then return
else
Acquire Exclusive Lock on |Tt |
|Tt | = |Tt | + 1
Release Exclusive Lock on |Tt |
if t changes rank then
rebuild(Tt )
return
until a node whose rank changes (that no longer satisfy Inequality 8.2) is
eventually encountered: here a rebuild takes place.
The function rebuild(Tt ) initially acquires an exclusive lock on label(t),
this will prevent any query to access information in Tt . Then, visiting the
subtree, the decomposition into solid paths is recomputed, as well as the
size of each subtree rooted in any head (proper locks are acquired on each
part of the data structure being read or wrote). Another visit of Tt is performed to recompute all node labels. Timestamps are updated to reflect
the rebuild, timestamp(t) will be the instant in which labels recomputation
started. Other node timestamps should satisfy the constraint that each node
must have a timestamp greater or equal to the one of its parent. At the
8.4. A CONCURRENT IMPLEMENTATION
133
end the exclusive lock on label(t) is released. Each time the label of a node
in Tt is written, an exclusive lock is acquired and then is released, to avoid
undesired readings in the meanwhile.
Query. In order to answer a query (see Program 3) we make a local copy
of u and v labels, we check their consistency by comparing their timestamps
with those of tui and tui+1 (and tvi and tvi+1 respectively). If we find an ancestor
whose timestamp is newer than the one of u (or v) this means that a rebuild
took place and a new label for u (or v) has been computed (or is going to be
computed). In this case the query fails; the process can try querying again
(possibly after a little random delay). Notice that there is no need to check
timestamps of nodes below tui+1 (or tvi+1 ) because, even if a rebuild took place
in one of these subtrees, this would not affect the query. If timestamps are
consistent the lca is computed as described in Section 8.3.2.
All locks are shared so that multiple queries can take place at the same
time.
Program 3 ConcurrentQuery(u,v)
1. Acquire Shared Lock on label(u) and label(v)
2. Make a local copy of label(u) and label(v)
3. Release Shared Lock on label(u) and label(v)
4.
5.
6.
7.
8.
9.
Scan list(u) and list(v) until tui = tvi
Let x be the last tui = tvi
Acquire Shared Lock on label(x)
Let tsx = timestamp(x)
Release Shared Lock on label(x)
if tsx > min{timestamp(u), timestamp(v)} then fail
10.
11.
12.
13.
14.
Let yu = tui+1 and yv = tvi+1
Acquire Shared Lock on label(yu ) and label(yv )
Let tsyu = timestamp(yu ) and tsyv = timestamp(yv )
Release Shared Lock on label(yu ) and label(yv )
if tsyu > timestamp(u) or tsyv > timestamp(v) then fail
15.
Compute cu and cv and return the one of minimum level
134
8.4.1
CHAPTER 8. ILS FOR LCA ON DYNAMIC TREES
Analysis
We now discuss the correctness of our concurrent implementation. First of
all, we remark that the data structure is deadlock free. Deadlocks can only
be caused by exclusive locks, therefore the query procedure cannot produce
any undesired situation. On the other hand, updates alway acquire locks in
a top down order, so it is not possible that two updates produce a deadlock.
If a rebuild on a certain node v is going on, then all updates that try to
acquire a lock on v will be suspended (waiting for the lock to be acquired)
until the end of the rebuild.
Two (or more) query operations can be executed concurrently without
any problem and in any order. Indeed, queries only acquire shared locks and
do not change the labels, therefore none of the two can affect the correctness
of the other. If a query (on u and v) is executed concurrently with an update, some interaction may happen. More specifically, if the update causes a
rebuild of a tree Tx containing the two nodes of the query, the labels of these
nodes can be not reciprocally consistent. Indeed, the lists of heads of ancestral solid paths are changing and it may be the case that one label reflects
this change while the other still does not. To prevent the query to produce a
wrong result, timestamps are checked (see Program 3), and in case of inconsistency, the query fails. Two updates can be executed concurrently, indeed
exclusive locks ensure that the operations are serialized. If two rebuilds (one
on Tx and the other on Ty , with Ty subtree of Tx ) are executed concurrently,
they are serialized. Indeed, the rebuild on Ty holds an exclusive lock on y.
When the rebuild of Tx will visit the tree, it will be not able to acquire a
lock on y until the other rebuild is running: rebuild of Tx will be suspended,
waiting for the lock on y to be released.
Let us now show the running time of the operations does not change.
Here we also discuss how many locks each operation requires. A query acquires O(log n) shared locks. The operations required to compute the lca are
exactly those used in Program 1: then the overall worst case running time
is O(log n). Same reasoning applies for the update procedure: it requires
O(log n). However, an update may imply a rebuild. As we have discussed
8.5. EXPERIMENTAL COMPARISON
135
in Section 8.4.1, the cost of each rebuild can be amortized resulting in an
O(log2 n) amortized running time of each update. The number of locks acquired by a rebuild operation, is linear in the size of the subtree being rebuilt.
Therefore, the number of locks can be amortized on update operations with
the very same argument used to the running time: each update requires
O(log2 n) locks (in an amortized sense).
8.5
Experimental Comparison
In this section we report details and results of an experimental analysis aimed
to compare the labeling scheme proposed by Peleg and the one due to us.
Peleg’s labeling scheme is intended only for static trees and is not designed
for a concurrent settings. For this reason we limited the analysis to static
trees in a sequential setting: the features of our data structure have been
only partially implemented in this investigation. In particular, we don’t
need to implement concurrency and all details related to our relaxed solid
path decomposition (needed to handle dynamic trees).
Namely, in this section we compare three different solid path decompositions and the two label structures due to Peleg and to us. Different
combinations of these two ingredients yield different labeling schemes: one
of them coincides with the tree labeling scheme for least common ancestors
originally proposed by Peleg. The main findings of our experiments can be
summarized as follows.
• Among different path decompositions, those generating the smallest
number of paths (with the largest average path length) appear to be
preferable in order to minimize the total size of the data structure.
• A variant of Peleg’s scheme proposed in [20] achieves the best performances in terms of space usage and construction time.
• Peleg’s scheme, used with a minor variant of the path decomposition
originally proposed in [87], exhibits the fastest query times.
136
CHAPTER 8. ILS FOR LCA ON DYNAMIC TREES
• All the data structures are very fast in practice. Although node labels
have size O(log2 n) bits, only a small fraction of the labels is considered
when answering random queries: typically, no more than a constant
number of words per query is read in all our experiments. However,
query times slightly increase with the instance size due to cache effects.
• Variants of the data structures carefully implemented with respect to
alignment issues save 20% up to 40% of the space, but increase the
query times approximately by a factor 1.3 on our data sets. The space
saving reduces as the instance size gets larger.
8.5.1
Ingredients
As mentioned, we will compare three different solid path decompositions and
two label structures. The first solid path decomposition is the one based on
Large Child used by Peleg (described in Section 8.2). The second one is a
minor variant of the first one, it is based on the concept of Maximum Child
and is described below. The third one is the one based on Rank (as described
in the first part of Section 8.3.1). There is no need to consider relaxed solid
paths because we are not going to deal with dynamic trees. We remark that
these decompositions have proven to be a useful tool for computing least
common ancestors in different models [33, 34, 71, 87].
Decomposition by Maximum Child. This is a minor variant of the
decomposition based on Large Child, that uses a relaxed definition of large
nodes: a nonroot node v with parent u is considered a maximum child of u if
|Tv | = maxw: (u,w)∈T |Tw |. If two or more children of u satisfy this condition,
ties are broken arbitrarily. The edge to the maximum child is considered
as a solid edge. We note that a large node is necessarily a maximum child;
however, a maximum child exists even when all the children v of a node u
are such that |Tv | ≤ |Tu |/2. All the basic properties of the decomposition by
large child remain valid in this variant.
We combine these three decompositions with the two label structures due
to Peleg (see Section 8.2) and to us (see Section 8.3.2) obtaining six different
8.5. EXPERIMENTAL COMPARISON
137
labeling schemes. Notice that, since each path decomposition ensures that
each node has O(log n) ancestral solid path, all the obtained labeling schemes
have maximum label size Θ(log2 n) bit.
From now on, we will refer to our label structure as CFP (from the name
of the authors: Caminiti, Finocchi, and Petreschi).
8.5.2
Experimental Framework
In this section we describe our experimental framework, discussing implementation details of the data structures being compared, the performance
indicators we consider, the problem instances generators, as well as our experimental setup. All implementations have been realized by the authors in
ANSI C.
Data Structure Implementation Issues
As stated in Section 8.5.1, we implemented six labeling schemes. Each scheme
comes in two variants, depending on alignment issues. In the word variant,
every piece of information maintained in the node labels is stored at wordaligned addresses: some bytes are therefore used just for padding purposes.
The actual sizes of node labels may be larger than the size predicted theoretically, but we expect computations on node labels to be fast. In the bit
variant, everything is 1-bit aligned: this variant guarantees a very compact
space usage, but requires operations for bit arithmetics that might have a
negative impact on the running times of operations.
Performance Indicators
Main objectives we considered to evaluate the data structures include space
usage, construction time, and query time. Space usage is strictly related to
the length of the lists in the node labels, i.e., to the number of entries in such
lists: besides the total size of the data structure (measured in MB, unless
otherwise stated), we have therefore taken into account also the average and
maximum list length. Other structural measures have been used to study the
138
CHAPTER 8. ILS FOR LCA ON DYNAMIC TREES
effect of the different path decompositions on the labeling schemes; among
them, we considered the number of paths in which the tree is decomposed,
the average and maximum length of paths, and the variance of path lengths.
Experimental Setup
Our experiments have been carried out on a workstation equipped with two
Dual Core Opteron processors with 2.2 GHz clock rate, 6 GB RAM, 1 MB
L2 cache, and 64 KB L1 data/instruction cache. The workstation runs Linux
Debian (Kernel 2.6.8). All programs have been compiled through the GNU
gcc compiler version 3.3.5 with optimization level O3, using the C99 revised
standard of the C programming language.
Random values have been produced by the rand() pseudo-random source
of numbers provided by the ANSI C standard library. We used only odd seeds
to initialize the random generators; we randomly generated the sequence of
seeds used in each test starting from a base odd seed.
In our experiments we averaged each data point on (at least) 1000 different instances. When computing running times of query operations, we averaged the time on (at least) 106 random queries. The running time of each
experiment was measured by means of the standard system call getrusage()
and, unless stated otherwise, is given in milliseconds.
8.5.3
Experimental Results
In this section we summarize our main experimental findings. We performed
experiments using a wide variety of parameter settings and instance families,
always observing the same relative performances of the data structures. For
this reason we report the results of our experiments on trees generated uniformly at random. We remark that random trees are generated with the fast
method based on tree encoding deeply described in Chapter 4 of this thesis.
139
8.5. EXPERIMENTAL COMPARISON
Number of paths
Average path length
Variance of path length
Maximum path length
Average list length (Peleg)
Variance of list length (Peleg)
Maximum list length (Peleg)
Data structure size (Peleg)
Average list length (CFP)
Variance of list length (CFP)
Maximum list length (CFP)
Data structure size (CFP)
maxChild
3678739
2.72
73.7
15352
5.72
2.16
15
1179
6.36
2.06
15
1033
largeChild
4172966
2.4
61.2
15351
5.89
2.32
15
1203
6.47
2.18
15
1045
rank
6803270
1.47
7.9
6346
12.40
10.85
24
2199
12.72
10.44
24
1761
Table 8.1: Comparison of path decompositions. The results of this experiment
are averaged over 250 random trees with n = 107 nodes. Only the word variant of
the data structures is considered.
Path Decomposition
Our first aim was to analyze the effects of different path decomposition strategies on the size of node labels. A typical outcome of our experiments is exemplified in Table 8.1. With respect to all measures, maxChild (the decomposition by Maximum Child) appears to be slightly preferable than largeChild
(the decomposition by Large Child) and considerably better than rank (the
decomposition by Rank). Consider first the structural measures: among
the three decompositions, maxChild generates the smallest number of solid
paths. Paths are therefore longer on the average, and their lengths exhibit
a higher variance. On the opposite side, the number of paths generated by
rank is almost twice as large, and their length is almost twice as small.
The number of paths is strictly related to the number of solid heads
in the lists associated to tree nodes, i.e., to the list lengths. Indeed, this
is the case both for Peleg and CFP labels, not only when considering the
maximum list length, but also on the average. In particular, Table 8.1 shows
that the average list length appears to be inversely proportional to the path
length: this can be easily explained by observing that if paths are longer on
140
CHAPTER 8. ILS FOR LCA ON DYNAMIC TREES
1200
7
6.5
Peleg-Word
CFP
CFP-Word
1000
6
Peleg
Peleg-Bit
800
5
size
average list length
CFP-Bit
5.5
4.5
600
4
400
3.5
3
200
2.5
2
1000
0
10000
100000
n
a)
1000000
10000000
0
2000000
4000000
6000000
8000000
10000000
n
b)
Figure 8.2: Size comparison for Peleg’s and CFP’s schemes: a) average list length;
b) total size, measured in MB.
the average, the number of paths in any root-to-leaf path is expected to be
smaller, and so is the number of heads in the node labels.
We remark that for all the decompositions the average and the maximum
list length for Peleg and CFP are very similar: the list length in the case
of Peleg’s scheme appears to be only slightly smaller, due to the presence
of sentinel triples in CFP. Instead, the total size of the data structure is
considerably smaller in the case of CFP: we will further discuss this aspect in
the next section.
Size Comparison
Our next aim is to evaluate the requirements of Peleg’s and CFP’s schemes
with respect to the space usage. We will limit to use maxChild as a common
path decomposition for both labeling schemes, since it proved to be consistently better than the other two decompositions in all the experiments we
performed. To compare the size of the data structures, we measured both
the average list length and the total data structure size on random trees with
an increasing number of nodes. Figure 8.2 illustrates one such experiment,
where the number n of nodes ranges from 103 to 107 .
As already observed in Table 8.1, lists in Peleg’s scheme are shorter, but
the total size of the data structure is larger than in CFP: this is because the
8.5. EXPERIMENTAL COMPARISON
141
lists are made of triples, instead of quadruples, and the smaller list length
in Peleg’s scheme is not sufficient to compensate for the presence of one
more information in each element of the lists. We remark that lists are very
short in practice for both schemes: they contain on the average 3 up to 6
elements for the data sets considered in this experiment. This value is very
close to log10 n, showing that the constant factors hidden by the asymptotic
notation in the theoretical analysis are very small for the maxChild path
decomposition. In Figure 8.2(b) we also distinguish between the bit and
word versions of the data structures (there is no such difference with respect
to the average list length): as expected, for both schemes the bit versions
can considerably reduce the space usage. We will analyze further these data
below.
Running Times
According to the theoretical analysis, the construction times and the query
times for the different labeling schemes are asymptotically the same. A
natural question is whether this is the case also in practice. Our experiments
confirmed the theoretical prediction only in part, showing that the constant
factors hidden by the asymptotic notation can be rather different for Peleg’s
and CFP’s schemes. The charts in Figure 8.3, for instance, have been obtained
on the same data sets used for the experiment reported in Figure 8.2: these
charts show that Peleg is slower than CFP when considering initialization
time, but faster when considering query times. The bit versions of the data
structures are always slower than the corresponding word versions.
In order to explain the larger construction time of Peleg’s scheme, notice
that Peleg makes use of a depth-first traversal of the tree, that is instead
avoided by CFP: all the other operations performed by the initialization algorithms (i.e., path decomposition and list construction) are instead very
similar. We also recall that Peleg’s data structure is larger than CFP, and
the size of a data structure is clearly a lower bound on its construction time.
However, the larger amount of information maintained by Peleg in the node
lists is efficiently exploited in order to get faster query times: as an example,
if one of the two input nodes is ancestor of the other, the query algorithm
142
CHAPTER 8. ILS FOR LCA ON DYNAMIC TREES
1200
12000
Peleg-Word
10000
Peleg-Word
1000
CFP-Word
800
CFP-Bit
Query time
Construction time
8000
6000
400
2000
200
0
2000000
4000000
6000000
8000000
0
1000
10000000
CFP-Bit
600
4000
0
CFP-Word
Peleg-Bit
Peleg-Bit
10000
100000
1000000
10000000
n
n
b)
a)
Figure 8.3: Running time comparison for Peleg’s and CFP’s schemes: a) construction time; b) average query time.
2.2
2.E+09
2.E+09
L2 cache references and misses
# scanned list elements
2.15
2.1
2.05
2
CFP
1.95
Peleg
1.9
1.85
1000
1.E+09
1.E+09
Total cache refs
Read refs
1.E+09
8.E+08
Write refs
Total cache misses
Read misses
6.E+08
Write misses
4.E+08
2.E+08
10000
100000
1000000
10000000
0.E+00
1000
10000
a)
100000
n
n
b)
Figure 8.4: a) Average number of list elements scanned by the query algorithms;
b) number of references to L2 cache and number of cache misses.
used by CFP needs to scan the beginning of the node lists, while the depth-first
intervals directly provide the answer in the case of Peleg’s data structure.
To get a deeper understanding of the query times, we also measured the
average number of list elements scanned by the query algorithms during a
sequence of operations. This number turns out to be very small both for
Peleg and for CFP, as shown by the charts reported in Figure 8.4(a): on
the average, slightly more than 2 elements are considered in each query even
on the largest instances. Peleg considers less elements than CFP, especially
for small values of n: on small trees, two nodes taken uniformly at random
have indeed a higher probability to be one ancestor of the other, and for all
143
8.5. EXPERIMENTAL COMPARISON
4
4
InitTime CFP-Bit / InitTime CFP-Word
3.5
InitTime Peleg-Bit/InitTime Peleg-Word
3.5
Size Peleg-Bit / Size Peleg-Word
Size CFP-Bit / Size CFP-Word
3
QueryTime Peleg-Bit / QueryTime Peleg-Word
QueryTime CFP-Bit / QueryTime CFP-Word
Peleg bit / word ratio
CFP bit / word ratio
3
2.5
2
1.5
2.5
2
1.5
1
1
0.5
0.5
0
1000
0
10000
100000
1000000
n
a)
10000000
1000
10000
100000
1000000
10000000
n
b)
Figure 8.5: Space saved by the bit versions and time saved by the word versions:
a) CFP; b) Peleg.
these queries Peleg can avoid to scan the list at all, as we observed above.
Quite surprisingly, however, for the largest values of n the number of scanned
list elements remains almost constant for both data structures: this seems
to be in contrast to the fact that the query times increase (see Figure 8.3),
and suggests that the larger running times may be mainly due to cache
effects. To investigate this issue, we conducted a preliminary experimental
analysis of the number of cache misses incurred by the data structures using
the valgrind profiler: the outcome of one such experiment is reported in
Figure 8.4(b) and confirms that the number of L2 cache read misses increases
with n, even if the number of cache references does not increase substantially.
Trading Space for Time
The experimental results discussed up to this point show that the bit versions of the data structures require more space than the corresponding word
versions, but have larger construction and query times. In Figure 8.5 we
summarize the space-time tradeoffs, both for Peleg and for CFP. The charts
show the differences between bit and word versions tend to decrease for all
measures as the instance size increases: this depends on the fact that, as n
increases, the value log n becomes progressively closer to the word size specific of the architecture, and therefore the number of bits wasted by the word
versions becomes smaller. The size of the bit versions ranges approximately
from 60% up to 80% of the size of the word versions on our data sets. On
144
CHAPTER 8. ILS FOR LCA ON DYNAMIC TREES
the other side, construction and query times of the bit versions are approximately 1.3 times higher than the word versions for the largest values of n
(for small values of n the ratio is even larger).
8.6
Concluding Remarks
In this chapter we have exploited the concept of Informative Labeling Scheme
to devise a concurrent data structure that allow fast computation of the
lowest common ancestor on dynamic trees. We want to explicitly remark
how our data structure can be easily adapted to answer distance queries
on trees. Given two nodes u and v, their levels (lu and lv ) are explicitly
maintained in the data structure. When we compute the lowest common
ancestor w of u and v, we also explicitly obtain its level lw . The distance
between u and v is simply d(u, v) = (lu − lw ) + (lv − lw ).
In future we plan to extend this data structure to handle dynamic trees
that admit deletion of leaves and insertion of internal nodes (edge splitting).
Moreover, the idea of using ILS to design concurrent data structure seems
to be promising and deserves to be better investigated. Problems other than
lowest common ancestor can be considered both on trees and other classes
of graphs.
It would be finally interesting to run an experimental analysis of the
labeling scheme proposed in this chapter on a real concurrent architecture.
Chapter 9
Conclusions and Future Work
In this thesis we studied algorithmic aspects related to bijective tree encodings and their generalizations. We proposed a unified approach that works for
all Prüfer-like codes and a more generic scheme based on the transformation
of a tree into a functional digraph suitable all bijective codes. By means of
these ideas we devised optimal encoding and decoding sequential algorithms
for many known codes as well as for interesting variants of known codes. We
considered parallel algorithms for Prüfer-like codes on the EREW PRAM
model. In all cases our results either match or improve the performances of
the best previous known algorithms. We also described possible applications
of these codes to Genetic Algorithms and to random tree generation. Specifically, in the field of Genetic Algorithms, our modified version of the Happy
code has been experimentally proven to be the best known code for trees by
Paulden and Smith [85].
We believe that, at the moment, this field does not offer further interesting
algorithmic problems to investigate. More stimuli may eventually come from
other applications requiring codes to satisfy new unstudied properties.
Shifting our attention from trees to their superclasses, we proposed a new
bijective encode for k-trees. For the best of our knowledge, our work is the
first one that presents linear time encoding and decoding algorithms. Our
idea can be adapted to obtain bijective codes for both rooted and unrooted
k-trees as well as for Rényi k-trees.
145
146
CHAPTER 9. CONCLUSIONS AND FUTURE WORK
Looking at k-arch graphs (a superclass of k-trees), we discovered that
the known result concerning the number of such graphs was wrong [72]. We
corrected this result providing a recursive formula to compute the number of
labeled k-arch graphs on n nodes. Unfortunately, this result does not help
us in defining a bijective code for this class of graphs. This work deserves
to be continued by investigating whether or not it is possible to obtain a
closed formula for counting k-arch graphs. If this question can be settle
affirmatively, an attempt to devise a bijective code can be made.
In the last chapter we focused on Informative Labeling Schemes: a way
to associate to each node a rich label, so that it is possible to perform computations directly from labels. Since Informative Labeling Schemes naturally
realize a localization of the information required to perform a computation,
we decided to exploit them to design concurrent data structures. As a first
example, we presented a concurrent data structure that allow fast computation of lowest common ancestors and distances on dynamic trees (leaves
insertion only).
The results obtained in this initial investigation reveal that this is a
promising idea. There are many possible directions for future work:
• we plan to extend our data structure to handle dynamic trees that
admit deletion of leaves and insertion of internal nodes (edge splitting);
• this approach can be applied to other classes of graphs (e.g., k-trees,
weighted trees, direct acyclic graphs, etc.);
• problems other than lowest common ancestor can be considered (e.g.,
routing, distance, flow, connectivity, etc.);
Finally, it would be interesting to run experimental analysis on a real
concurrent architecture comparing classical concurrent data structures with
those obtained by means of Informative Labeling Schemes.
Glossary
::
: with symbol “::” we denote the concatenation of two strings.
[a, b]: the interval of integers from a to b (a and b included).
A B: if B is a set this is the difference among sets: A B = {x ∈ A : x ∈
/
B}; if B = (b1 , b2 , . . . , b|B| ) is a string this is the difference among a set
and a string A B = {x ∈ A : x ∈
/ {b1 , b2 , . . . , b|B| }}.
A
k
: be the set of all k-subset of the set A. |
A
k
|=
|A|
k
.
adj(v): the set of nodes adjacent to v in a graph, i.e., {u : d(u, v) = 1}. If v
has only one adjacent node u, adj(v) may be used to refer to u itself
rather than to the set {u}.
d(u, v): the distance between nodes u and v, i.e., the number of edges in the
shortest path between u and v. The distance of a node from itself is 0,
i.e., d(v, v) = 0.
deg(v): the degree of node v, i.e., |adj(v)|.
l(v) : bottom up level of node v, i.e., l(v) = max{d(v, u) : u is a leaf in Tv }.
Each leaf has level 0.
Tv : the subtree of T rooted at node v.
T v: the tree obtained by removing a leaf v from a tree T .
k-Arch Graphs: a generalization of k-trees, see Chapter 7.
Cayely tree: a tree on n nodes labeled with distinct integers in [1, n].
147
148
GLOSSARY
Center of a graph: a node minimizing the maximum distance from all
other nodes. A tree has at least one and at most two centers.
k-Clique: a complete graph on k nodes.
Digraph: a directed graph G = (V, E). Each edge (u, v) ∈ E is oriented
from u towards v.
EREW: Exclusive Read Exclusive Write.
Euler Tour: of a connected, directed graph G = (V, E) is a cycle that
traverses each edge of graph G exactly once. The Euler Tour of a tree
can be efficiently computed on a PRAM [59].
FIFO: First In First Out.
Functional Digraph: a graph G = (V, E) is a functional digraph for a
function g if and only if g : V → V and E = {(v, g(v)) : v ∈ V }.
Genetic Algorithm: a search heuristic that hinge upon the evolutionary
ideas of natural selection and genetic (see Section 4.2).
LIFO: Last In First Out.
PRAM: Parallel Random Access Machine [59].
Rényi k-Tree: a k-tree on n nodes rooted in {n − k + 1, . . . , n}, see Chapter 6.
Star: a graph G = (V, E) having a single node c (the center) connected with
all the others nodes and no other edges, i.e., E = {(c, v) : v ∈ V {c}}.
Analogously, a tree of height 2. Analogously, a connected bipartite
graph G = (V ′ , V ′′ , E) with |V ′ | = 1.
String: a string over an alphabet Σ is a sequence S = (s1 , s2 , . . . , sk ) where
each symbol si ∈ Σ. The length of the string is k and S ∈ Σk .
k-Trees: a generalization of trees, see Chapter 6.
Bibliography
[1] S. Abiteboul, S. Alstrup, H. Kaplan, T. Milo, and T. Rauhe. Compact
Labeling Scheme for Ancestor Queries. SIAM Journal on Computing,
35(6):1295–1309, 2006.
[2] S. Alstrup, P. Bille, and T. Rauhe. Labeling Schemes for Small Distances in Trees. SIAM Journal on Discrete Mathematics, 19(2):448–
462, 2005.
[3] S. Alstrup, C. Gavoille, H. Kaplan, and T. Rauhe. Nearest Common
Ancestors: a Survey and a New Distributed Algorithm. In Proceedings
of the 14th ACM Symposium on Parallel Algorithms and Architectures
(SPAA’02), pages 258–264, 2002.
[4] S. Alstrup and M. Thorup. Optimal Pointer Algorithms for Finding
Nearest Common Ancestors in Dynamic Trees. Journal of Algorithms,
35(2):169–188, 2000.
[5] M.J. Atallah, R. Cole, and M.T. Goodrich. Cascading Divide-andConquer: A Technique for Designing Parallel Algorithms. SIAM Journal on Computing, 18(3):499–532, 1989.
[6] L.W. Beineke and R.E. Pippert. On the Number of k-Dimensional
Trees. Journal of Combinatorial Theory, 6:200–205, 1969.
[7] M.A. Bender, M. Farach-Colton, G. Pemmasani, S. Skiena, and
P. Sumazin. Lowest Common Ancestors in Trees and Directed Acyclic
Graphs. Journal of Algorithms, 57(2):75–94, 2005.
149
150
BIBLIOGRAPHY
[8] H.L. Bodlaender. A Tourist Guide Through Treewidth. Acta Cybernetica, 11:1–21, 1993.
[9] H.L. Bodlaender. A Linear Time Algorithm for Finding TreeDecompositions of Small Treewidth. SIAM Journal on Computing,
25:1305–1317, 1996.
[10] H.L. Bodlaender. A Partial k-Arboretum of Graphs with Bounded
Treewidth. Theoretical Computer Science, 209:1–45, 1998.
[11] N. Bonichon, C. Gavoille, and A. Labourel. Short Labels by Traversal and Jumping. In Proceedings of the 13th International Colloquium on Structural Information and Communication Complexity
(SIROCCO’06), pages 143–156, 2006.
[12] V. Boppana, I. Hartanto, and W.K. Fuchs. Full Fault Dictionary Storage Based on Labeled Tree Encoding. In Proceedings of the 14th IEEE
VLSI Test Symposium, pages 174–179, 1996.
[13] C.W. Borchardt. Uber eine der Interpolation Entsprechende Darstellung der Eliminations-Resultante. Journal für die Reine und Angewandte Mathematik, 57:111–121, 1860.
[14] M.A. Breuer. Coding the Vertexes of a Graph. IEEE Transactions on
Information Theory, 12:148–153, 1966.
[15] M.A. Breuer and J. Folkman. An Unexpected Result on Coding the
Vertices of a Graph. Journal of Mathematical Analysis and Applications, 20:583–600, 1967.
[16] S. Caminiti, N. Deo, and P. Micikevičius. Linear-time Algorithms for
Encoding Trees as Sequences of Node Labels. To appear on Congressus
Numerantium, 2007.
[17] S. Caminiti, I. Finocchi, and R. Petreschi. A Unified Approach to Coding Labeled Trees. In Proceedings of the 6th Latin American Symposium
on Theoretical Informatics (LATIN’04), pages 339–348, 2004.
BIBLIOGRAPHY
151
[18] S. Caminiti, I. Finocchi, and R. Petreschi. Concurrent Data Structures
for Lowest Common Ancestors. Manuscript, July 2007.
[19] S. Caminiti, I. Finocchi, and R. Petreschi. Engineering Tree Labeling
Schemes: a Case Study on Least Common Ancestors. Manuscript,
November 2007.
[20] S. Caminiti, I. Finocchi, and R. Petreschi. On Coding Labeled Trees.
Theoretical Computer Science, 382(2):97–108, 2007.
[21] S. Caminiti and E.G. Fusco. On the Number of Labeled k-Arch Graphs.
Journal of Integer Sequences, 10(7), 2007.
[22] S. Caminiti, E.G. Fusco, and R. Petreschi. Bijective Linear Time Coding and Decoding for k-Trees. Submitted to Theory of Computing Systems.
[23] S. Caminiti, E.G. Fusco, and R. Petreschi. A Bijective Code for kTrees with Linear Time Encoding and Decoding. In Proceedings of the
Proceedings of the IntErnational Symposium on Combinatorics, Algorithms, Probabilistic and Experimental Methodologies (ESCAPE’07),
2007.
[24] S. Caminiti and R. Petreschi. A General Scheme for Coding and Decoding Trees with Locality and Heritability. Submitted to SIAM Journal
of Discrete Mathematics.
[25] S. Caminiti and R. Petreschi. String Coding of Trees with Locality and
Heritability. In Proceedings of the 11th International Conference on
Computing and Combinatorics (COCOON’05), pages 251–262, 2005.
[26] A. Cayley. A Theorem on Trees. Quarterly Journal of Mathematics,
23:376–378, 1889.
[27] H.C. Chen and Y.L. Wang. An Efficient Algorithm for Generating
Prüfer Codes from Labelled Trees. Theory of Computing Systems,
33:97–105, 2000.
152
BIBLIOGRAPHY
[28] W.Y.C. Chen. A Coding Algorithm for Rényi Trees. Journal of Combinatorial Theory, 63A:11–25, 1993.
[29] W.Y.C. Chen. A General Bijective Algorithm for Increasing Trees.
Systems Science and Mathematical Sciences, 12:194–203, 1999.
[30] E. Cohen, E. Halperin, H. Kaplan, and U. Zwick. Reachability and
Distance Queries via 2-hop Labels. In Proceedings of the 13th ACMSIAM Symposium on Discrete Algorithms (SODA’02), pages 937–946,
2002.
[31] H. Cohen.
PARI/GP Development Headquarter.
http://pari.math.u-bordeaux.fr/.
Website:
[32] R. Cole. Parallel merge sort. SIAM Journal on Computing, 17(4):770–
785, 1988.
[33] R. Cole and R. Hariharan. Dynamic LCA Queries on Trees. In Proceedings of the 10th ACM-SIAM Symposium on Discrete Algorithms
(SODA’99), pages 235–244, 1999.
[34] R. Cole and R. Hariharan. Dynamic LCA Queries on Trees. SIAM
Journal on Computing, 34(4):894–923, 2005.
[35] T.H. Cormen, C.E. Leiserson, R.L. Rivest, and C. Stein. Introduction
to Algorithms. McGraw-Hill, 2001.
[36] N. Deo, N. Kumar, and V. Kumar. Parallel Generation of Random
Trees and Connected Graphs. Congressus Numerantium, 130:7–18,
1998.
[37] N. Deo and P. Micikevičius. Parallel Algorithms for Computing PrüferLike Codes of Labeled Trees. Technical report, CS-TR-01-06, Department of Computer Science, University of Central Florida, Orlando,
2001.
[38] N. Deo and P. Micikevičius. Prüfer-like codes for labeled trees. Congressus Numerantium, 151:65–73, 2001.
BIBLIOGRAPHY
153
[39] N. Deo and P. Micikevičius. A New Encoding for Labeled Trees Employing a Stack and a Queue. Bulletin of the Institute of Combinatorics
and its Applications (ICA), 34:77–85, 2002.
[40] L. Devroye. Non-Uniform Random Variate Generation. SpringerVerlag, New York, 1986.
[41] W. Edelson and M.L. Gargano. Feasible Encodings For GA Solutions
of Constrained Minimal Spanning Tree Problems. In Proceedings of
the Genetic and Evolutionary Computation Conference (GECCO’00),
page 754, Las Vegas, Nevada, USA, 2000.
[42] W. Edelson and M.L. Gargano. Modified Prüfer code: O(n) implementation. Graph Theory Notes of New York, 40:37–39, 2001.
[43] Ö. Eğecioğlu and J.B. Remmel. Bijections for Cayley Trees, Spanning Trees, and Their q-Analogues. Journal of Combinatorial Theory,
42A(1):15–30, 1986.
[44] Ö. Eğecioğlu and J.B. Remmel. A Bijection for Spanning Trees of
Complete Multipartite Graphs. Congressus Numerantium, 100:225–
243, 1994.
[45] Ö. Eğecioğlu, J.B. Remmel, and G. Williamson. A Class of Graphs
which has Efficient Ranking and Unranking Algorithms for Spanning
Trees and Forests. International Journal of Foundations of Computer
Science, 15(4):619–648, 2004.
[46] Ö. Eğecioğlu and L.P. Shen. A Bijective Proof for the Number of
Labeled q-Trees. Ars Combinatoria, 25B:3–30, 1988.
[47] D. Foata. Enumerating k-Trees. Discrete Mathematics, 1(2):181–186,
1971.
[48] V.K. Garg and A. Agarwal. Distributed Maintenance of a Spanning
Tree Using Labeled Tree Encoding. In Proceedings of 11th International
Euro-Par Conference, pages 606–616, 2005.
154
BIBLIOGRAPHY
[49] C. Gavoille, M. Katz, N.A. Katz, C. Paul, and D. Peleg. Approximate Distance Labeling Schemes. In Proceedings of the 9th European
Symposium on Algorithms (ESA’01), pages 476–487, 2001.
[50] C. Gavoille, D. Peleg, S. Perennes, and R. Raz. Distance Labeling in
Graphs. In Proceedings of the 12th ACM-SIAM Symposium on Discrete
Algorithms (SODA’01), pages 210–219, 2001.
[51] J. Gottlieb, B.A. Julstrom, G.R. Raidl, and F. Rothlauf. Prüfer
Numbers: A Poor Representation of Spanning Trees for Evolutionary
Search. In Proceedings of the Genetic and Evolutionary Computation
Conference (GECCO-2001), pages 343–350, San Francisco, California,
USA, 2001.
[52] C. Greene and G.A. Iba. Cayley’s Formula for Multidimensional Trees.
Discrete Mathematics, 13:1–11, 1975.
[53] R. Greenlaw, M.M. Halldórsson, and R. Petreschi. On Computing
Prüfer Codes and Their Corresponding Trees Optimally in Parallel.
In Proceedings of Journes de l’Informatique Messine (JIM’00), Metz,
France, 2000.
[54] R. Greenlaw and R. Petreschi. Computing Prüfer Codes Efficiently in
Parallel. Discrete Applied Mathematics, 102(3):205–222, 2000.
[55] Y. Han and X. Shen. Parallel Integer Sorting is More Efficient than
Parallel Comparison Sorting on Exclusive Write PRAMS. SIAM Journal on Computing, 31(6):1852–1878, 2002.
[56] F. Harary and J.P. Hayes. Edge Fault Tolerance in Graphs. Networks,
23:135–142, 1993.
[57] F. Harary and E.M. Palmer. On Acyclic Simplicial Complexes. Mathematika, 15:115–122, 1968.
[58] D. Harel and R.E. Tarjan. Fast Algorithms for Finding Nearest Common Ancestors. SIAM Journal on Computing, 13(2):338–355, 1984.
[59] J. JáJá. An Introduction to Parallel Algorithms. Addison-Wesley, 1992.
BIBLIOGRAPHY
155
[60] A. Joyal. Une Theorie Combinatoire des Series Formelles. Advances in
Mathematics, 42:1–81, 1981.
[61] B.A. Julstrom. The Blob Code: A Better String Coding of Spanning
Trees for Evolutionary Search. In Representations and Operators for
Network Problems (ROPNET 2001), pages 256–261, San Francisco,
California, USA, 2001.
[62] B.A. Julstrom. The Blob Code is Competitive with Edge-Sets in Genetic Algorithms for the Minimum Routing Cost Spanning Tree Problem. In Proceedings of Genetic and Evolutionary Computation Conference (GECCO’05), pages 585–590, Washington DC, USA, 2005.
[63] S. Kannan, M. Naor, and S. Rudich. Implicit representation of graphs.
In Proceedings of the 20th ACM Symposium on Theory of Computing
(STOC’88), pages 334–343, 1988.
[64] H. Kaplan and T. Milo. Short and Simple Labels for Small Distances
and other Functions. In Proceedings of the 7th Workshop on Algorithms
and Data Structures (WADS’01), pages 246–257, 2001.
[65] H. Kaplan, T. Milo, and R. Shabo. A Comparison of Labeling Schemes
for Ancestor Queries. In Proceedings of the 13th ACM-SIAM Symposium on Discrete Algorithms (SODA’02), pages 954–963, 2002.
[66] M. Katz, N.A. Katz, A. Korman, and D. Peleg. Labeling Schemes
for Flow and Connectivity. SIAM Journal on Computing, 34(1):23–40,
2004.
[67] M. Katz, N.A. Katz, and D. Peleg. Distance Labeling Schemes for
Well-Separated Graph Classes. In Proceedings of the 17th Symposium
on Theoretical Aspects of Computer Science (STACS’00), pages 516–
528, 2000.
[68] P. Klingsberg. Doctoral Dissertation. PhD thesis, University of Washington, Seattle, Washington, 1977.
156
BIBLIOGRAPHY
[69] D.E. Knuth. Oriented Subtrees of an Arc Digraph. Journal of Combinatorial Theory, 3:309–314, 1967.
[70] D.E. Knuth. The Generating All Trees – History of Combinatorial
Generation, volume 4(4) of Art of Computer Programming. AddisonWesley, 2006.
[71] T. Kopelowitz and M. Lewenstein. Dynamic Weighted Ancestors. In
Proceedings of the 18th ACM-SIAM Symposium on Discrete Algorithms
(SODA’07), pages 565–574, 2007.
[72] C. Lamathe. The Number of Labelled k-Arch Graphs. Journal of
Integer Sequences, 7, 2004.
[73] L. Markenzon, P.R. Costa Pereira, and O. Vernet. The Reduced Prüfer
Code for Rooted Labelled k-Trees. In Proceedings of 7th International
Colloquium on Graph Theory, Electronic Notes in Discrete Mathematics, volume 22, pages 135–139, 2005.
[74] P. Micikevičius. Parallel Graph Algorithms for Molecular Conformation
and Tree Codes. PhD thesis, University of Central Florida, 2002.
[75] M. Mitchell. An Introduction to Genetic Algorithms. MIT Press, 1996.
[76] M. Moir and N. Shavit. Concurrent Data Structures. In Handbook of
Data Structures and Applications. CRC Press, 2005.
[77] J.W. Moon. The Number of Labeled k-Trees. Journal of Combinatorial
Theory, 6:196–199, 1969.
[78] J.W. Moon. Counting Labeled Trees. William Clowes and Sons, London, 1970.
[79] E.H. Neville. The Codifying of Tree-Structure. In Proceedings of Cambridge Philosophical Society, volume 49, pages 381–385, 1953.
[80] A. Nijenhuis and H.S. Wilf. Combinatorial Algorithms. Academic
Press, New York, 1978.
BIBLIOGRAPHY
157
[81] J.B. Orlin. Line-Digraphs, Arborescences, and Theorems of Tutte and
Knuth. Journal of Combinatorial Theory, 25:187–198, 1978.
[82] C.C. Palmer and A. Kershenbaum. Representing Trees in Genetic Algorithms. In First IEEE Conference on Evolutionary Computation,
pages 379–384, Orlando, FL, 1994.
[83] T. Paulden and D.K. Smith. The Rainbow Code: A Superior Genetic
Algorithm Representation for Layered Trees. In Proceedings of the 34th
International Conference on Computers and Industrial Engineering,
2004.
[84] T. Paulden and D.K. Smith. From the Dandelion Code to the Rainbow
Code: A Class of Bijective Spanning Tree Representations With Linear
Complexity and Bounded Locality. IEEE Transactions on Evolutionary Computation, 10(2):108–123, 2006.
[85] T. Paulden and D.K. Smith. Recent Advances in the Study of the
Dandelion Code, Happy Code, and Blob Code Spanning Tree Representations. In Proceedings of the IEEE Congress on Evolutionary
Computation, pages 2111–2118, 2006.
[86] D. Peleg. Proximity-Preserving Labeling Schemes and Their Applications. In Proceedings of the 25th International Workshop on GraphTheoretic Concepts in Computer Science (WG’99), pages 30–41, 1999.
[87] D. Peleg. Informative Labeling Schemes for Graphs. In Proceedings
of the 25th International Symposium on Mathematical Foundations of
Computer Science (MFCS’00), pages 579–588, 2000.
[88] D. Peleg. Informative Labeling Schemes for Graphs. Theoretical Computer Science, 340(3):577–593, 2005.
[89] S. Picciotto. How to Encode a Tree. PhD thesis, University of California, San Diego, 1999.
[90] H. Prüfer. Neuer Beweis eines Satzes über Permutationen. Archiv der
Mathematik und Physik, 27:142–144, 1918.
158
BIBLIOGRAPHY
[91] Raidl and B.A. Julstrom. Edge-sets: An Effective Evolutionary Coding
of Spanning Trees. IEEE Transactions on Evolutionary Computation,
7(3):225–239, 2003.
[92] C.R. Reeves and J.E. Rowe. Genetic Algorithms: A Guide to GA
Theory. Springer, 2003.
[93] J.B. Remmel and G. Williamson. Spanning Trees and Function Classes.
Electronic Journal of Combinatorics, R24, 2002.
[94] A. Rényi and C. Rényi. The Prüfer Code for k-Trees. In P. Erdös at
al., editor, Combinatorial Theory and its Applications, pages 945–971,
North-Holland, Amsterdam, 1970.
[95] D.J. Rose. On Simple Characterizations of k-Trees. Discrete Mathematics, 7:317–322, 1974.
[96] F. Rothlauf. Representations for Genetic and Evolutionary Algorithms.
Physica-Verlag, Heidelberg, Germany, 2002.
[97] B. Schieber and U. Vishkin. On Finding Lowest Common Ancestors: Simplification and Parallelization. SIAM Journal on Computing,
17(6):1253–1262, 1988.
[98] N.J.A. Sloane and S. Plouffe. The Encyclopedia of Integer Sequences.
Academic Press, 1995. http://www.research.att.com/∼njas/sequences.
[99] M. Thorup. Compact Oracles for Reachability and Approximate Distances in Planar Digraphs. In Proceedings of the 42nd IEEE Symposium
on Foundations of Computer Science (FOCS’01), pages 242–251, 2001.
[100] P. Todd. A k-Tree Generalization that Characterizes Consistency of
Dimensioned Engineering Drawings. SIAM Journal Discrete Mathematics, 2:255–261, 1989.
[101] E. Tompson, T. Paulden, and D.K. Smith. The Dandelion Code: A
New Coding of Spanning Trees for Genetic Algorithms. IEEE Transactions on Evolutionary Computation, 11(1):91–100, 2007.
BIBLIOGRAPHY
159
[102] W. Tutte. The Dissection of Equilateral Triangles into Equilateral Triangles. In Proceedings of Cambridge Philosophical Society, volume 44,
pages 463–482, 1948.
[103] I. Vardi. Computational Recreations in Mathematica, chapter Computing Binomial Coefficients. Addison-Wesley, Redwood City, CA, 1991.
[104] Y.L. Wang, H.C. Chen, and W.K. Liu. A Parallel Algorithm for Constructing a Labeled Tree. Transactions on Parallel and Distributed
Systems, 8(12):1236–1240, 1997.
[105] Xingzhi Wen and Uzi Vishkin. PRAM-on-Chip: First Commitment
to Silicon. In Proceedings of the 19th ACM Symposium on Parallel
Algorithms and Architectures (SPAA’07), pages 301–302, 2007.
[106] G. Zhou and M. Gen. A Note on Genetic Algorithms for DegreeConstrained Spanning Tree Problems. Networks, 30:91–95, 1997.