Sublinear Random Access Generators for Preferential Attachment Graphs

We consider the problem of sampling from a distribution on graphs, specifically when the distribution is defined by an evolving graph model, and consider the time, space, and randomness complexities of such samplers. In the standard approach, the whole graph is chosen randomly according to the randomized evolving process, stored in full, and then queries on the sampled graph are answered by simply accessing the stored graph. This may require prohibitive amounts of time, space, and random bits, especially when only a small number of queries are actually issued. Instead, we propose a setting where one generates parts of the sampled graph on-the-fly, in response to queries, and therefore requires amounts of time, space, and random bits that are a function of the actual number of queries. Yet, the responses to the queries correspond to a graph sampled from the distribution in question. Within this framework, we focus on two random graph models: the Barabási-Albert Preferential Attachment model (BA-graphs) (Science, 286 (5439):509–512) (for the special case of out-degree 1) and the random recursive tree model (Theory of Probability and Mathematical Statistics, (51):1–28). We give on-the-fly generation algorithms for both models. With probability 1-1/poly(n), each and every query is answered in polylog(n) time, and the increase in space and the number of random bits consumed by any single query are both polylog(n), where n denotes the number of vertices in the graph. Our work thus proposes a new approach for the access to huge graphs sampled from a given distribution, and our results show that, although the BA random graph model is defined by a sequential process, efficient random access to the graph’s nodes is possible. In addition to the conceptual contribution, efficient on-the-fly generation of random graphs can serve as a tool for the efficient simulation of sublinear algorithms over large BA-graphs, and the efficient estimation of their on such graphs.

Preferential attachment [3]. We restrict our attention to the case in which each vertex is connected to the previous vertices by a single edge (i.e., m = 1 in the terminology of Reference [3]). 2 We thus denote the random process that generates a graph over V n according to the preferential attachment model by BA n . The random process BA n generates a sequence of n directed edges E n {e 1 , . . . , e n }, where the tail of e i is v i , for every i ∈ {1, . . . , n}. (We abuse notation and let BA n = (V n , E n ) also denote the graph generated by the random process.) We refer to the head of e i as the parent of v i .
The process BA n draws the edges sequentially starting with the self-loop e 1 = (v 1 , v 1 ). Suppose we have selected BA j−1 , namely, we have drawn the edges e 1 , . . . , e j−1 , for j > 1. The edge e j is drawn such its head is node v i with probability . Note that the out-degree of every vertex in (the directed graph representation of) BA n is exactly one, with only one self-loop in v 1 . Hence, BA n (without the self-loop) is an in-tree rooted at v 1 .
Evolving copying model [18]. Let Z n denote the evolving copying model with out-degree d = 1 and copy factor α = 1/2. As in the case of BA n , the process Z n selects the edges E n = {e 1 , . . . , e n } one-by-one starting with a self-loop e 1 = (v 1 , v 1 ). Given the graph Z n−1 = (V n , E n ), the next edge e n emanates from v n . The head of edge e n is chosen as follows: Let b n ∈ {0, 1} be an unbiased random bit. Let u (n) ∈ {1, . . . , n − 1} be a uniformly distributed random variable (the random variables b 1 , . . . ,b n and u (1), . . . ,u (n) are fully independent.) The head v i of e n is determined as follows: Random recursive tree model [34]. If we eliminate from the evolving copying model the bits b i and define head(e n ) u (n), then we get a model where each new node n is connected to one of the previous nodes, chosen uniformly at random. This is the extensively studied (random) recursive tree model [34].

Results
Our main results are stated in the following theorems (which are informal versions of Theorem 19 and Lemma 16, respectively): Theorem 1 (Informal). There exists an algorithm that provides query access to the adjacency-lists of a graph G over n nodes, where n is a parameter and G is drawn according to the random process BA n . With high probability, the complexities of executing each query are as follows: 28:4 G. Even et al.
(1) The increase, during that query, of the space used by our algorithm is O (log 3 n). (2) The number of random bits used during that query is O (log 5 n). (3) The time complexity of that query is O (log 6 n).
Theorem 2 (Informal). There exists an algorithm that provides query access to the adjacency-lists of a graph G over n nodes, where n is a parameter and G is drawn according to the random process Z n . With high probability, the complexities of executing each query are as follows: (1) The increase, during that query, of the space used by our algorithm is O (log 2 n). (2) The number of random bits used during that query is O (log 4 n). (3) The time complexity of that query is O (log 5 n).

Related Work
A linear time randomized algorithm for efficiently generating BA-graphs is given in Batagelj and Brandes [4]. See also Kumar et al. [18] and Nobari et al. [27]. A parallel algorithm is given in Alam et al. [1]. See also Yoo and Henderson [35]. An external memory algorithm was presented by Meyer and Peneschuck [24].
Efficient generation of other graph models was also studied. Miller and Hagberg [25] introduced a randomized algorithm that generates a graph with a given sequence of expected degrees (also called the Chung and Lu model) with expected running time of O (n +m), where n is the number of vertices and m is the number of edges of the generated graph. Additional efficient random graph generation algorithms for other graph models (e.g., Kronecker and the Stochastic block model) are provided in Ramani, Eikmeier, and Gleich [30].
Goldreich, Goldwasser, and Nussboim initiate the study of the generation of huge random objects [15] while using a "small" amount of randomness. They provide an efficient stateless query access to an object modeled as a function, when the object has a predetermined property, for example graphs that are connected. They guarantee that these objects are indistinguishable from random objects that have the same property. This refers to the setting where the size of the object is exponential in the number of queries to the function modeling the object. In a followup paper by Bogdanov and Wee [6], stateful implementations of huge random objects were considered. They showed how to generate in an "on the fly" fashion a random Boolean function that supports XOR queries over sub-cubes of the function's domain hypercube. We note that our stateful generator provides access to graphs that are random BA-graphs and not just indistinguishable from random BA-graphs.
Mansour, Rubinstein, Vardi, and Xie [22] consider local generation of bipartite graphs for local simulation of Balls into Bins online algorithms. They assume that the balls arrive one-by-one and that each ball picks d bins independently and is then assigned to one of them. The local simulation of the algorithm locally generates a bipartite graph. Mansour et al. show that with high probability one needs to inspect only a small portion of the the bipartite graph to run the simulation and hence a random seed of logarithmic size is sufficient.
Our work has inspired subsequent work in the setting that we propose here, i.e., on-the-fly local generation of graphs according to a given distribution. In fact, subsequent to the initial publication of the present work [11], Biswas, Rubinfeld, and Yodpinyanee [5] have devised local graph generators for, most notably, the Erdös-Rényi model (with next-neighbor, and other, queries).

Applications
One reason for generating large BA-graphs is to simulate algorithms over them, or to experimentally estimate some of their properties (cf. Reference [9]). Such algorithms often access only small portions of the graphs. In such instances, it is wasteful to generate the whole graph. An interesting example is sublinear approximation algorithms [26,28,29,36] that probe a constant number of neighbors. 3 In addition, local computation algorithms probe a small number of neighbors to provide answers to optimization problems such as maximal independent sets and approximate maximum matchings [2, 12, 13, 19-23, 31, 32]. Support of adjacency list queries is especially useful for simulating (partial) DFS and BFS over graphs.

Techniques
The main difficulty in providing the on-the-fly generator is in "inverting" the random choices of the BA process. That is, we need to be able to randomly choose the next "child" of a given node x, although it will only "arrive in the future" and its choice of a parent in the BA-graph will depend on what will have happened until it arrives (i.e., on the node degrees in the BA-graph when that node arrives). One possibility to do so is to maintain, for any future node that does not yet have a parent, how many potential parents it still has, and then go sequentially over the future nodes and randomly decide if its parent will indeed be x. This is too costly because (1) we will need to go sequentially over the nodes, and (2) it may be too costly in computation time to calculate what is the probability that the parent of a node y that does not have yet a parent, will be node x (given the random choices already done in response to previous queries).
To overcome this difficulty, we define for any node, even if it has already a parent, its probability to be a candidate to be a child of x. We show how these probabilities can be calculated efficiently given the previous choices taken in response to previous queries, and show how, based on these probabilities, we can define an efficient process to chose the next candidate. The candidate node may however already have a parent, and thus cannot be a child of x. If this is the case, then we repeat the process and choose another candidate, until we chose an eligible candidate that then is chosen to be the actual next child of x. We show that with high probability this process terminates quickly and finds an eligible candidate, so that with high probability, we have an efficient process to find "into the future" the next child of x. This is done while sampling exactly according to the distribution defined by the BA-graphs process.
In addition to the above technique, which is arguably the crux of our result, we use a number of data structures, based on known constructions, to be able to run the on-the-fly generator with polylogarithmic time and space complexities. In the sequel, we give, in addition to the formal definitions of the algorithms, some supplementary intuitive explanations into our techniques.

PRELIMINARIES
The normalized degree distribution of G is a vector Δ(G) with n coordinates, one for each vertex in G. The coordinate corresponding to v i is defined by 28:6 G. Even et al.
In the sequel, when we say that an event occurs with high probability (or w.h.p.), we mean that it occurs with probability at least 1 − 1 n c , for some constant c > 0. We state the complexities of our algorithm (and our subroutines) with the guarantee of high probability. Therefore, there is some negligible probability that our algorithm will require more resources. 4 For ease of presentation, define the algorithm making use of arrays of size n. However, to give the desired upper bounds on the space complexity, we implement these arrays by means of balanced search trees, where the keys are in {1, . . . , n}. To access item i in the virtual array, key i is searched in the tree and the value in that node is returned; if the key is not found, then nil is returned. Thus, the space used by the virtual arrays is the number of keys stored, and the time complexity of our algorithms is multiplied by a factor of O (log n) compared to the time complexity that it would have with a standard random-access implementation of the arrays. When we state upper bounds on time, we take into account these O (log n) factors. As common, we analyze the space complexity in terms of words of size O (log n).

QUERIES
Consider an undirected graph G = (V n , E), where V n = {v 1 , . . . ,v n }. Slightly abusing notation, we sometimes consider and denote node v i as the integer number i and so we have a natural order on the nodes. The access to the graph is done by means of a user-query BA-next-neighbor: {1, . . . , n} → {1, . . . , n + 1}, which outputs entries of the adjacency list in increasing order (where n + 1 denotes "no additional neighbor"). For example, consider the node 3 (namely, the node that arrived third) whose parent is 1 and its children are 7, 10, and 50. When the user executes the query BA-next-neighbor(3) for the first time, 1 is returned. The next time the query BA-next-neighbor(3) is executed 7 is returned, and then 10 and 50. After that, BA-next-neighbor(3) always returns n + 1, since 3 does not have any additional neighbors. More formally, we number the queries according to the order they are issued, and we call this number the time of the query. Let q(t ) be the node on which the query at time t was issued, i.e, at time t the query BA-next-neighbor(q(t )) is issued by the user. For each node j ∈ V and any time t, let last t (j) be the largest numbered node that was previously returned as the value of BA-next-neighbor(j), or 0 if no such query was issued before time t. That is, At time t the query BA-next-neighbor(j) returns arg min i >last t (j ) {(i, j) ∈ E}, or n + 1 if no such i exists. When the implementation of the query has access to a data structure holding the whole of E, then the implementation of BA-next-neighbor is straightforward just by accessing this data structure. Figure 1 illustrates a "traditional" randomized graph generation algorithm that generates the whole graph, stores it, and then can answers queries by accessing the data structure that encodes the whole generated graph.

ON-THE-FLY GRAPH GENERATORS
An on-the-fly graph generator is an algorithm that gives access to a graph by means of the BA-next-neighbor query defined above, but itself does not have access to a data structure that encodes the whole graph. Instead, in response to the queries issued by the user, the generator modifies its internal data structure (a.k.a. state), which is initially some empty (constant)  state. The generator must ensure, however, that its answers are consistent with some graph G. An on-the-fly graph generator for a given distribution on a family of graphs (such as the family of Preferential Attachment graphs on n nodes) must in addition ensure that it samples the graphs according to the required distribution. That is, its answers to a sequence of queries must be distributed identically to those returned when a graph was first sampled (according to the desired distribution), stored, and then accessed (See Definition 20 and Theorem 21). Figure 2 illustrates an on-the-fly graph generation algorithm as the one we build in the present article.
We now relate the various models defined in Section 1.1. The following relation is crucial for our implementation of the on-the-fly generator for BA-graphs: ). The random graphs BA n and Z n are identically distributed.
Proof. The proof is by induction on n. The basis (n = 1) is trivial. To prove the induction step, assume that BA n−1 and Z n−1 are identically distributed. We need to prove that the next edges e n and e n in the two processes are also identically distributed, given a graph G as the realization of BA n−1 and Z n−1 , respectively. The head of e n is chosen according to the degree distribution Δ(BA n−1 ) = Δ(G). Since the outdegree of every vertex is one, Thus, an equivalent way of choosing the head of e n is as follows: (1) with probability 1/2, choose a random vertex uniformly (this corresponds to the 1 2 · 1 n−1 term), and (2) with probability 1/2 toss a Δ in (BA n−1 )-dice (this corresponds to the 1 2 term). Hence, case (1) above corresponds to the case when b n = 1, in the process of Z n . To complete the proof, we observe that, conditioned on the event that b n = 0, the choice of the head of e n in Z n can be defined as choosing according to the in-degree distribution of the nodes in Z n−1 = G: Indeed, choosing according to the in-degree distribution Δ in (G) is identical to choosing a uniformly distributed random edge in G and then taking its head. But, since the out-degrees of all the vertices in V n−1 are all the same (and equal one), this is equivalent to choosing a uniformly distributed random node in V n−1 .
We use the following claim in the sequel: Claim 4 (cf. [14], Thm. 1 and Thm. 6.32 [10]). Let T be a rooted directed tree on n nodes denoted 1, . . . , n, and where node 1 is the root of the tree. If the head of the edge emanating from node j > 1 is uniformly distributed among the nodes in [1, j − 1], then, with high probability, the following two properties hold: Note that the claim still holds if we add to the tree a self loop on node 1.

THE POINTERS TREE
We now consider a graph inspired by the random recursive tree model [34] and the evolving copying model [18]. Each vertex i has a variable u (i) that is uniformly distributed over [1, i − 1] and can be viewed as a directed edge (or pointer) from i to u (i). We denote this random rooted directed in-tree (namely, a tree such that all its edges point towards the root) by UT . Let u −1 (j) denote the set {i : u (i) = j}. We refer to the set u −1 (i) as the u-children of i and to u (i) as the u-parent of i.
In conjunction with each pointer, we keep a flag indicating whether this pointer is to be used as an imm (immediate) pointer, that is, whether it points to the head, or as a rec (recursive) pointer (as defined in Equation (1)). We thus use the directed pointer tree to represent a graph in the evolving copying model (which is equivalent, when the flag of each pointer is equally distributed between rec and imm, to the BA model). See Figure 3 for an illustrative example.
In this section, we consider the subtask of giving access to a random UT , together with the flags of each pointer. Ignoring the flags, this section thus gives an on-the-fly random access generator for the extensively studied model of random recursive trees (cf. Reference [34]).
We define the following queries: • (i, f laд) ← parent(j): i is the parent of j in the tree, and f laд is the associated flag.
• i ← next-child-flag(j, k, f laд), where k ≥ j: i is the least numbered node i > k such that the parent of i is j and the flag of that pointer is of type f laд. If no such node exists, then i is n + 1. (since 10 has a recursive pointer to node 8). Node 11 is a child of node 3, since it has a recursive pointer to node 5 (in particular, it is not a child of node 5).
Given a query next-child-flag(j, k, ·), we assume that k is bounded above by the largest value returned by next-child-flag(j, ·, ·) thus far (and j if it is the first time next-child-flag(j, ·, ·) is executed). Clearly, even under this assumption the neighbors of every node can be revealed one-by-one by repeated calls to next-child-flag (by setting k to be the returned value of the former execution of next-child-flag). In this case the output of the queries is the entries of the adjacency list, in increasing order (where the entries with the wrong flag are filtered).
The "ideal" way to implement next-child-flag is to go over all n nodes, and for each node j (1) uniformly at random choose its parent in [1, j − 1], (2) uniformly at random chose the associated flag in {imm, rec}. Then store the pointers and flags and answer the queries by accessing this data structure.
In this section, we give an on-the-fly generator that answers the above queries. As explained in more detail in the sequel, randomly selecting the parent of a node, to answer a parent query, is a rather easy task (even if the generator already has a state). The challenge is in randomly selecting the children of a node that corresponds to next-child queries. In this case, we need to randomly select (according to the appropriate distribution) the first child of j between {j+1, . . . , n} and then the second child and so on. In what follows, we start with a naïve, non-efficient implementation that illustrates the task to be done. Then, we give our efficient implementation.

Notations
We say that j is exposed if u (j) nil (initially all pointers u (j) are set to nil). We denote the set of all exposed vertices by F . As a result of answering and processing next-child-flag and parent queries, the on-the-fly generator commits to various decisions (e.g., prefixes of adjacency lists). These commitments include edges but also non-edges (i.e., vertices that can no longer serve as u (j) for a certain j). Therefore, each query may change the state of the generator. Note that the answers of the generator to queries depend on its state, thus, the queries parent and next-child-flag are also a function of this state (which is not given as a parameter).

The Front of a Node.
For every node i ∈ {1, . . . , n − 1}, the generator saves the front of i, which is roughly speaking, a value k > i for which it holds that (1) u (k ) = i; and (2) the generator decided for every node j ∈ [i +1, k −1] whether u (j) = i or not. Formally, at any given time, front(i) is a pointer to a node in [i + 1, n + 1] that has the following properties: (1) Initially front(i) = nil. The first time front(i) nil is after the first next-child-flag(i, ·, ·) query is issued. (2) If front(i) = nil, then for any node in j ∈ [i + 1, n] for which u (j) = nil it is possible that u (j) will be set to i in the future.  (a) for any node in j ∈ [i + 1, k − 1] for which u (j) = nil it is not possible that u (j) will be set to i in the future; (b) for any node in j ∈ [k + 1, n] for which u (j) = nil it is possible that u (j) will be set to i in the future.

The Set of Potential Parents.
We denote the set of vertices that can become u-parents of j at any given time t, by Φ(j) and their number by φ(j) (for brevity, we omit t from the notation). The formal definition is as follows: Definition 5. At a given time t, and for any node j, let Φ(j) and φ(j) be defined as follows: We note that from technical reasons that will become clear below, according to the definition, Φ(j) is not necessarily empty even if u (j) is already determined. Moreover, counterintuitively, it might be the case that u (j) Φ(j).

A Naïve Implementation of next-child
We give a naïve implementation of a next-child query, with time complexity O (n), with the purpose of illustrating the main properties of this query and to contrast it with the more efficient implementation later. We do so in a simpler manner without looking into the "flag. " The naïve implementation of next-child is listed in Figure 4. This implementation, and that of parent, share an array of pointers u, both updating it. A query next-child(i, k ) is processed by scanning the vertices one-by-one starting from k + 1. If u (x ) = i, then x is the next child. If u (x ) is nil, then a coin c (x ) is flipped and u (x ) = i is set when c (x ) comes out 1; the probability that c (x ) is 1 is 1/φ(x ). If c (x ) = 0, then we proceed to the next vertex. The loop ends when some c (x ) is 1 or all vertices have been exhausted. In the latter case the query returns n + 1.
The correctness of naïve-next-child, i.e., the fact that the graph is generated according to the required probability distribution, is based on the observation that given that u (x ) has not been determined yet, all the vertices in Φ(x ) are equally likely to serve as u (x ). Note that the description above does not explain how φ(x ) is computed, as explained next, this is one of the main challenges in designing our on-the-fly generator.

The Challenge in Obtaining an Efficient Implementation of next-child
We first shortly discuss the challenges on the way to an efficient implementation of next-child. Consider the simple special case where the only two queries issued are, for some j, a single parent(j) query followed by a single next-child(j) query (to simplify this discussion, we assume that the the value of k is globally known). Consider the situation after the query parent(j). At this point, every node x ∈ [j + 1, n] may be a u-child of j (namely, u (x ) may be set to j). In particular, and for P n+1 (i.e., j has no child) P n+1 = j−1 n−1 . As explained in the sequel, each of the probabilities P k = k x =j+1 P x can be calculated in O (1) time, therefore, this random choice can be done in O (log n) time by choosing uniformly at random a number in [0, 1] and performing a binary search on [j + 1, n + 1] to find which index it represents (see a more detailed and accurate statement of this procedure below). However, in general, at the time of a certain next-child query, limitations may exist, due to previous queries, on the possible consistent values of certain pointers u (x ). There are two types of limitations: (i) u (x ) might have been already determined, or (ii) u (x ) is still nil but the option of u (x ) = i has been excluded, since front(i) > x. These limitations change the probabilities P x and P x , rendering them more complicated and time-consuming to compute, thus rendering the above-defined process not efficient (i.e., not doable in O (log n) time). In the rest of this section, we define and analyze a modified procedure that uses polylog(n) random bits, takes polylog(n) time, and increases the space (that is used to store the state of the generator) by polylog(n). This procedure will be at the heart of the efficient implementation of next-child.

The Invariants Regarding the State of the Generator
In the implementation of the on-the-fly generator of the pointers tree, we will maintain two invariants that are described below. We will later discuss the cost (in running time and space) of maintaining these invariants.
The purpose of these invariants is to obtain an efficient computation of the probabilities P x and P x discussed in Section 5.3. Roughly speaking, if for every node x ∈ {1, . . . , n − 1} it was the case that φ(x + 1) − φ(x ) = 1, then these probabilities were easy to calculate. However, since the commitments of the generator impose changes on φ, this property can not hold for all the nodes. The invariants ensure that the set of nodes for which this property does not hold are easy to identify and for which the u-parent is already determined. Invariant 6. For every node j, the first next-child-flag(j, ·, ·) query is always preceded by a parent(j) query.
We will use this invariant to infer that front(j) nil implies that u (j) nil. (recall that if a next-child-flag(j, ·, ·) query was not issued thus far, then front(j) = nil). One can easily maintain this invariant by introducing a parent(j) query as the first step of the implementation of the next-child-flag(j, ·, ·) query (for technical reasons, we do that in a lower-level procedure next-child.) Invariant 7. For every vertex j, front(j) nil implies that front(front(j)) nil.
The second invariant is maintained by issuing an "internal" next-child(front(j), front(j)) query whenever front(j) is updated. This is done recursively, the base of the recursion being node n + 1. When analyzing the complexities of our algorithm, we will take into account these recursive calls.  5. The state of the generator. The state of the generator after performing a next-child(5) and a parent(10) queries. After the first query, which returns 8, the parent of node 5 is determined (to keep Invariant 6) and set to be node 3 and front (5) and the first child of node 5 are set to be 8. Consequently, to keep Invariant 7 the next child of node 8 is also discovered and is set to 10 + 1 (it has no children). After the second query, the parent of node 10 is set to be node 5. However, this does not change front (5) and so, potentially, node 9 could also pick node 5 as its parent (or any other node in {1, . . . , 7}).
Let front −1 (j) denote the vertex i such that front(i) = j, if such a vertex i exists; (note that there can be at most one such node i, except for the case of j = n + 1); otherwise, front −1 (j) = nil. We get that if front −1 (j) nil, then u (j) nil. See Figure 5 for an illustrative example.
We note that if at a given time we consider a node j such that u (j) = nil (i.e., its parent in the pointers tree is not yet determined), then the set Φ(j) is the set of all the nodes that can still be the parent of node j in the pointers tree. As mentioned above, the set Φ is, however, defined also for nodes for which their parent is already determined.
We are now ready to define the set K that is, as we prove in the sequel, the set of nodes, i, for which φ(i + 1) = φ(i). Moreover, we prove that if x K, then φ(i + 1) − φ(i) = 1. It is also the case that for x ∈ K it holds that u (x ) nil, as desired Definition 8. Let K denote the following set: We shall prove the following lemma: For any x ∈ {1, . . . , n − 1}: Lemma 9 follows directly from the following more general claim: For every x ∈ {1, . . . , n − 1}: Proof. We first observe that Item 1 follows directly from the definition of Φ and the properties of front. The see this, we first note that the fact that Φ(x ) ⊆ Φ(x + 1) follows from that fact that for every i such that front(i) < x it clearly holds that front(i) < x + 1. However, there might be only a single node, i, such that front(i) < x + 1 but front(i) ≥ x. This is possible only when front(i) = x, which might be the case only for a single node, which is the u-parent of x. Finally, x ∈ Φ(x + 1) if and only if front(x ) = nil.
To prove Item 2, observe that by Item 1, Finally, to prove Item 3, we need to show that it is not possible for both x and front −1 (x ) to belong to Φ(x + 1). Indeed, if front −1 (x ) ∈ Φ(x + 1), then there exists a vertex i such that front(i) = x. Invariant 7 implies that front(x ) = front(front(i)) nil. However, x ∈ Φ(x + 1) implies front(x ) = nil, a contradiction.

Description of the Efficient Implementation
We are now ready to describe the implementation of next-child-flag(j, k, f laд) and next-child(j). The state of the generator is stored using the following data structures, of which the implementation of next-child (and of parent) makes use of: • An array of length n, u (j).
• An array of length n, f laд(j).
• An array of length n, front(j) (we also maintain an array front −1 (i) with the natural definition). • An array of n balanced search trees, called child(j), each holding the set of nodes i > j such that u (i) = j. The operations we use on the search trees are insert and successor, where insert(T , j) inserts the element j to the tree T and successor(T , j) returns the smallest elements in T that is larger than j. For technical reasons, all trees child(j) are initiated with n + 1 ∈ child(j). • A number of additional data structures that are implicit in the listing, described and analyzed in the sequel.
As seen in Figure 6, next-child-flag(j, k, f laд) is merely a loop of next-child-from(j, k ), and next-child-from(j, k ) is essentially a call to next-child(j). The "real work" is done in the implementation of next-child(j) that we describe now. Note that if j does not have children larger than k, then next-child-from(j, k ) returns n + 1.
If front(j) > k when next-child-from(j, k ) is called, then the next child is already fixed and it is just extracted from the data structures.
Otherwise, an interval I = [a, b] is defined, and it will contain the answer of next-child(j). The next child can be sampled according to the desired distribution in a straightforward way by going sequentially over the vertices in I \ (F \ {b}), and tossing for each vertex x a coin that has probability 1/φ(x ) to be 1, until indeed one of those coins comes out 1, or all vertices are exhausted (in which case node b is taken as the next child). We denote by D (x ), x ∈ I \ F , the probability that x is chosen when the the above procedure is applied. This procedure, however, takes linear time.
To start building our efficient implementation for next-child , we note that by the definition of K, K ⊆ F , and we consider a process where we toss |[a, b) \K | coins sequentially for the vertices in [a, b) \ K. The probability that the coin for x ∈ [a, b) \ K is 1 is still 1/φ(x ). We stop as soon as 1 is encountered or on b if all coins are 0. The vertex on which we stop, denote it x, is a candidate next u-child. If x ∈ F \ K \ {b}, then x cannot be a child of j (because it already has a u-parent) so we proceed by repeating the same process recursively, but with the interval [x +1, b] instead of the interval [a, b]. We denote by D (x ), x ∈ I \ F , the probability that x is chosen when this procedure is applied.

Efficiently Selecting a u-child
Candidate. We now build our efficient procedure that selects the candidate, without sequentially going over the nodes. To this end, observe that the sequence of probabilities of the coins tossed in the last-described process behaves "nicely. " Namely, the  ([a, b) \ K ) ∪ {b} is chosen as candidate in the sequential procedure defined above. Since 1/φ(x ) forms the harmonic sequence for x ∈ [a, b) \ K, we can, given φ(a), calculate in O (1) time, for any 0 ≤ i ≤ s + 1, the probability P i = q <i P q (i.e., the probability that a node of some rank q, q < i, is chosen). Indeed, for i = 0, P i = 1 φ (a) ; for 0 < i < s, , and for i = s + 1, P s+1 = 1. This allows us to simulate one iteration (i.e., choosing the next candidate next u-child) by choosing uniformly at random a single number in [0, 1], and then performing a binary search over 0 to s to decide what rank h this number "represents. " After the rank h ∈ [0, s] is selected, h is then mapped to the vertex of rank h in ([a, b) \ K ) ∪ {b}, denote it x, and this is the candidate next u-child. As before, if x ∈ F \ K \ {b}, then x cannot be a child of j so we ignore it and proceed in the same way, this time with the interval [x + 1, b]. We denote byD(x ), x ∈ I \ F the probability that x is chosen when this third procedure is applied. See Figure 7 for a formal definition of this procedure and that of next-child.
Observe that this procedure takes O (log s) time (see Section 5.7 for a formal statement of the time and randomness complexities). We note that we cannot perform this selection procedure in the same time complexity for the set [a, b) \ F , because we do not have a way to calculate each and every probability To conclude the description of the implementation of next-child, we give the following lemma that states that the probability distribution on the next child is the same for all three processes described above: Proof. To prove the claim, we prove thatD(x ) = D (x ) and that D (x ) = D (x ). To prove the latter, denote by x 1 < x 2 < . . . < x k the nodes in the set I \ F , where k = |I \ F |, and let p( . When we consider the sequential process where one tosses a coin sequentially for all nodes in I \ K (and not only for the nodes in I \ F ), we extend the definition of D (·) to be defined also for nodes in I \ K. For a node z ∈ (I \ K ) ∩ F , D (z) is the probability that x is chosen as a candidate next u-child. Thus, if we denote by y 1 < y 2 < . . . < y , = |I \ K |, the nodes in I \ K, then we have that D (y j ) = p(y j ) · Π 1≤i <j;y j ∈I \F (1 − p(y i )), and for y (which is the node denoted b in the discussion above), D (y ) = 1 − Π 1≤i < ;y j ∈I \F (1 − p(y i )). Thus, for any x ∈ I \ F , We now extendD(·) to be defined for all nodes in I \ K. The assertionD(x ) = D (x ), for any x ∈ I \ K, follows from the fact a number M ∈ [0, 1] is selected uniformly at random and then the interval in which it lies is found. That is, i is selected if and only if P i ≤ M < P i+1 , which, by the definitions of P i and P i , occurs with probability P i = D (x i ).

Implementation of parent
The implementation of parent is straightforward (see Figure 6). However, note that updating the various data structures, while implicit in the listing, is accounted for in the time analysis.

Analysis of the Pointer Tree Generator
We first give the following claim that we later use a number of times: Lemma 12. With high probability, for each and every call to next-child, the size of the recursion tree of that call, for calls to next-child, is O (log n).
Proof. Consider the recursive invocation tree that results from a call to next-child. Observe that (1) by the code of next-child this tree is in fact a path; and (2) this path corresponds to a path in the pointers tree, where each edge of this tree-path is "discovered" by the corresponding call to next-child. That is, the maximum size of a recursion tree of a call of next-child is bounded from above by the height of the pointers tree. By Claim 4, with high probability, this is O (log n).

Efficient Rolling of Dice
In Algorithm 7, we implement a rolling of a dice whose time complexity is, with high probability, O (log n), as explained next. The cumulative probabilities P y of each side of the dice are a function of ξ and y and are computable in O (1) time. To determine the outcome of a roll of the dice, we pick r = c log n random unbiased bits, which are interpreted as a binary representation of a subinterval [ 1 , 2 ] of [0, 1] of length 2 −r . We say that the subinterval is good if it is contained in an interval, the endpoints of which are consecutive cumulative probabilities. Namely, Note that in this case, we choose i as the outcome of the roll of the dice. The number of bad subintervals is bounded by t, which is at most n. Hence, the probability that the subinterval is bad is at most n · 2 −r . Since r = c log n, the probability of determining the side of the dice after r random bits is at least 1 −n −c+1 . Hence, the time complexity in this case is dominated by the time complexity of the random search that is O (log n). However, if the interval we picked is bad (which happens with negligible probability), then we refine the size of the intervals so all intervals are good (Line 10). In this case, the time complexity is O (max{t, log n}).
We note that by a standard technique, we could alternatively implement a Las Vegas algorithm that rolls the dice. In this case the algorithm continues refining the intervals (by using additional random bits, one at a time) until the subinterval is good. Namely, the Las Vegas algorithm, after each random bit, checks in time O (log n) if the subinterval is good by performing a binary search over the commulative probabilities and adds an additional bit only if the selected interval is bad.

Data Structures and Space Complexity.
The efficient implementation of next-child makes use of the following data structures: • A number of arrays of length n, u (j) and f laд(j), front(j) and front −1 (j), used to store various values for nodes j. Since we implement arrays by means of search trees, the space complexity of each array is O (m), where m is the maximum number of distinct keys stored with a nonnull value in that array, at any given time. The time complexity for each operation on these arrays is O (log m) = O (log n) (since they are implemented as balanced binary search trees). • For each node j, a balanced binary search tree called child(j), where child(j) includes all nodes i such that u (i) = j (for technical reasons, we define child(j) to always include node n + 1). 5 Observe that for each child i stored in one of these trees, u (i) is already determined. Thus, the increase, during a given period, in the space used by the child trees is bounded from above by the the number of nodes i for which u (i) got determined during that period. For the time complexity of the operations on these trees , we use a coarse standard upper bound of O (log n) on each tree operation. 6 We store the roots of all non-empty trees child(j) in an "array. " Thus, using our implementation of arrays as balanced search trees, the space used by this "array" is O (m) and the time to access the root of a certain child( where m is the number of non-empty trees child(j) at a given time.
The listings of the implementations of the various procedures leave implicit the maintenance of two data structures, related to the set K and to the computation of φ(·): • A data structure that allows one to retrieve the value of φ(a) for a given node a. This data structure is implemented by retrieving the cardinality of the set of nodes that are not 5 So we maintain low space complexity, for a given (j ), child(j ) is initialized only at the first use of child(j ), at which time node n + 1 is inserted . 6 In fact, we can use the fact that with high probability potential parent of a, i.e., (a − 1) − φ(a), for a given node a. The latter is equivalent to counting how many nodes i < a have front(i) nil and front(i) ≥ a. We use two balanced binary search trees (or order statistics trees) in a specific way and have that by standard implementations of balanced search trees the space complexity is O (k ) (and all operations are done in time O (log k ) = O (log n)). Here k denotes the number of nodes i such that front(i) nil. More details of the implementation of this data structure appear in the appendix (see Section A.1).
• A data structure that allows one to find the vertex of rank h in the ordered set [a, n + 1] \ K.
This data structure is implemented by a balanced binary search tree storing the nodes in K, augmented with the queries rank K (i) (as in an order-statistics tree [

Time Complexity.
Time complexity of toss(φ, s). The time complexity of this procedure is with high probability O (log n) (see Section 5.8) Time complexity of "x ← the vertex of rank h in [a, n + 1] \ K. " This operation is implemented using the data structure defined above, and takes O (log 2 n) time.
Time complexity of parent(j). As stated in Lemma 16, the time complexity of parent is O (log n).
Time complexity of next-child. First consider the time complexity consumed by a single invocation of next-child (i.e., without taking into account the time consumed by recursive calls of next-child) 8 : The call to parent takes O (log n) time. Therefore, until the start of the repeat loop, the time is O (log n) (the time complexity of successor is O (log n)). Now, the time complexity of a single iteration of the loop (without taking into account recursive calls to next-child) is (O log 2 n) because: • Each access to an "array" takes O (log n) time. Proof. We consider a process where the iterations continue until the selected node is node b. A random variable, R, depicting this number dominates a random variable that depicts the actual number of iterations. For each iteration, an additional node is selected by toss. By Lemma 11 the probability that a node j < b is selected by toss is 1/φ(j), and we have that 1/φ(j) ≤ 1 j−1 . Thus, where X j is 1 iff node j was selected, 0 otherwise. Since μ = b−1 j=a 1 φ (j ) ≤ log n, using Chernoff bound 9 we have, for any constant c > 6, P[R > c · log n] ≤ 2 −c ·log n = n −Ω(1) .
We thus have the following: Lemma 14. For any given invocation of next-child, with high probability, the time complexity is O (log 3 n).

Randomness Complexity.
Randomness is used in our generator to randomly select the parent of the nodes (in parent) and to randomly select a next child for a node (in toss). We use the common convention that, for any given m, one can choose uniformly at random an integer in [0, m − 1] using O (log m) random bits and in O (1) computation time. We give our algorithms and analyses based on this building block.
In procedure parent we use O (log n) random bits whenever, for a given j, this procedure is called with parameter j for the first time.
In procedure toss the if condition holds with probability 1 − 1/n c−1 (where c is the constant used in that procedure). Therefore, given a call to toss, with probability 1 − 1/n c−1 this procedure uses O (log n) bits. By Claim 13, in each call to next-child the number of times that toss is called is, w.h.p., O (log n). We thus have the following: Lemma 15. During a given call to next-child, w.h.p., O (log 2 n) random bits are used.
The following lemma states the time, space, and randomness complexities of the queries. Lemma 16. The complexities of next-child-flag and parent are as follows: • Given a call to parent the following hold for this call: (1) The increase, during the call, of the space used by our algorithm is O (1).
(2) The number of random bits used during that call is O (log n).
(3) The time complexity of that call is O (log n).
• Given an call to next-child-flag, with high probability, all of the following hold for this call: (1) The increase, during that call, of the space used by our algorithm is O (log 2 n).
(2) The number of random bits used during that call is O (log 4 n).
(3) The time complexity of that call is O (log 5 n).
Proof. During a call to parent(j) the size of the used space increases when a pointer u (j) becomes non-null or when additional values are stored in child(u (j)). To select u (j), O (log n) random bits are used, and O (log n) time is used to insert j in child(u (j)) and to update the data structure for the set K (this is implicit in the listing).
For the analysis of next-child-flag, we first consider next-child. Observe that by Lemma 12, w.h.p., each and every root (non-recursive) call of next-child has a recursion tree of size O (log n). In each invocation of next-child, O (1) variables front(j) and u (j) may be updated. Therefore, w.h.p., for all root (non-recursive) calls to next-child it holds that the increase in space during this call is O (log n) (see Section 5.8.1). Using Lemmas 15 and 12, we have that, w.h.p., each root call of next-child uses O (log 3 n) random bits. Using Lemmas 14 and 12, we have that, w.h.p., the time complexity of each root call of next-child is O (log 4 n).
Because the flags of the pointers are uniformly distributed in {imm, rec}, each call to next-child-flag results, w.h.p., in O (log n) calls to next-child. The above complexities are thus multiplied by an O (log n) factor to get the (w.h.p.) complexities of next-child-flag.

ON-THE-FLY GENERATOR FOR BA-GRAPHS
Our on-the-fly generator for BA-graphs (see definition in Section 1.1) is called O-t-F-BA, and simply calls BA-next-neighbor(v) for each query on node v. We present an implementation for the BA-next-neighbor query, and prove its correctness, as well as analyze its time, space, and randomness complexities. The on-the-fly BA generator maintains n standard heaps, one for each node. The heaps store nodes, where the order is the natural order of their serial numbers. 10 The heap of node j stores some of the nodes already known to be neighbors of j. In addition, the generator maintains for purely technical reasons an array of size n, f irst_query, indicating if a BA-next-neighbor query has been issued for a given node. The implementation of the BA-next-neighbor query works as follows (see Figure 8): • For the first BA-next-neighbor(j) query, for a given j, we proceed as follows: We find the parent of j in the constructed BA-graph, which is done by following, in the pointers tree, the pointers of the ancestors of j until we find an ancestor pointed to by an imm pointer (and not a rec pointer). See Figure 8. In addition, we initialize the process of finding neighbors of j to its right (i.e., with a bigger serial number) by inserting into the heap of j the "final node" n + 1 as well as the first child of v. • For any subsequent BA-next-neighbor(j) query for node j, we proceed as follows: Observe that any subsequent query is to return a child of j in the constructed BA-graph. The children of j in the BA-graph are those nodes x that have, in the pointers tree, a path of u (·) pointers starting at x and ending at j and with all pointers on that path, except the last one, being rec (the last one being imm). The query BA-next-neighbor(j) has, however, to report the children in increasing order of their index. To this end, the heap of node j is used; it stores at any given time some of the children of j in the BA-graph, not yet returned by a BA-next-neighbor(j) query. We further have to update this heap so BA-next-neighbor(j) will continue to return the next child according to the index order. To this end, we proceed as follows: Whenever node, r is extracted from the heap, to be returned as the next child, we update the heap to include the following: -If r has an imm pointer to j, then we add to the heap (1) the next node, after r , with an imm pointer to j, and (2) the first node that has a rec pointer to r . -If r has a rec pointer to a node r , then we add to the heap (1) the first node, after r , with a rec pointer to r , and (2) the first node that has a rec pointer to r .
The proof of Lemma 17 below is based on the premise that the heap contains only children of v in the BA-graph, and that it always contains the child of v just after the one last returned. Lemma 17. The procedure BA-next-neighbor returns the next neighbor of v.
Proof. Given a pointers tree, we define the following notions: • The set of nodes that have an imm pointer to a given node j. That is, for 1 ≤ j ≤ n, D (j) {i | u (i) = j, f laд(i) = imm}. • The set of nodes that have a rec pointer to a given node j. That is, for 1 ≤ j ≤ n, R(j) {i | u (i) = j, f laд(i) = rec}.
We prove that the invariant holds by induction on . The induction basis, for call number = 1, holds, since (1) the first call to BA-next-neighbor(j) results in inserting into heap j the first node x, which has an imm pointer to node j, and (2) heap j was previously empty (see Figure 8). Thus, all points of the invariant hold after call = 1. For > 1 assume that the induction hypothesis holds for − 1 and let r be the node returned by the 'th call to BA-next-neighbor(j). We claim that the invariant still holds after call by verifying each one of the two cases for the pointer of r and the insertions into the heap for each such case.
If r has an imm pointer, then the following nodes are inserted into heap j : (1) The first node after r with an imm pointer to j. Since this is a neighbor of j in the BA graph, Point 1 continues to hold. Since r , just extracted from the heap, was the minimum node in the heap, Point 2 continues to hold.
(2) The first node after r that has a rec pointer to r . Since this is a neighbor of j in the BA graph, Point 1 continues to hold; Point 3 continues to hold, since nothing has changed for any other i r , i ∈ M (j) \ {q}, and for r the minimum node in R(i) \ M (j) is just inserted.
If r has a rec pointer, and let q be the parent of r in the pointers tree, then the following nodes are inserted into heap j : (1) The first node after r , which has a rec pointer to q; denote it x. Since x is a neighbor of j in the BA graph, Point 1 continues to hold. Since r , just extracted from the heap, was the minimum node in the heap, x is the minimum node in R(i) \ M (j) and Point 3 continues to hold (nothing changes for any q q, q ∈ M (j) \ {q}).
(2) The first node after r , which has a rec pointer to r . The same arguments as those for the corresponding case when r has an imm pointer hold, and thus both Point 1 and Point 3 continue to hold. This concludes the proof of the invariant.
We now use the above invariant to prove that, for any ≥ 1, N (j) = M (j). We do this by induction on . For = 1, the claim follows from the facts the first neighbor of node j is its parent in the BA graph and that the first call BA-next-neighbor(j) returns the value that BA-parent(j) returns. This proves the induction basis. We now prove the claim for > 1 given the induction hypothesis for all < . Let node x be the 'th neighbor of j. We have two cases: (1) node x has an imm pointer to j; (2) node x has a rec pointer to another child of j in the BA graph (i.e., to another neighbor of j in the BA graph, which is not the first neighbor).
Case (1): By the induction hypothesis N −1 (j) = M −1 (j), hence by Point 2 of the invariant x is in the heap heap j when the 'th call occurs. Since any node returned by BA-next-neighbor(j) is no longer in heap j , by Point 1 of the invariant, heap j does not contain any node smaller than x. Therefore, the node returned by the 'th call of BA-next-neighbor(j) is node x.
Case (2): Let node y be the parent of node x in the pointers tree, i.e., u (x ) = y. Since y is a neighbor of j in the BA graph, and y < x, it follows that y ∈ N −1 (j), and by the induction hypothesis y ∈ M −1 (j). Moreover, any node x < x has u (x ) = y, f laд(x ) = rec if and only if it is a neighbor of j, hence any such node x is in N −1 (j), and by the induction hypothesis also in M −1 (j). It follows from Point 3 of the invariant that x is in the heap heap j when the 'th call occurs. Since any node returned by BA-next-neighbor(j) is no longer in heap j , by Point 1 of the invariant, heap j does not contain any node smaller than i. Therefore, the node returned by the 'th call of BA-next-neighbor(j) is node i. This completes the proof of the lemma.
Lemma 18. For any given root (non-recursive) call of BA-parent, with high probability, that call takes O (log 2 n) time.
Proof. Consider the execution of BA-parent as stated in Figure 8. Consider the recursive invocation tree that results from a call to BA-parent. Each path in this tree corresponds to a path in the pointers tree. Since, By Claim 4, w.h.p. the height of the pointers tree is bounded by O (log n), the lemma follows by Lemma 16.
We can now conclude with the following theorem: Theorem 19. For any given call of BA-next-neighbor, with high probability, all of the following hold for that call: (1) The increase, during that call, of the space used by our algorithm is O (log 3 n).
(2) The number of random bits used during that call is O (log 5 n).
(3) The time complexity of that call is O (log 6 n).
Proof. Each call of BA-next-neighbor is executed, w.h.p., by O (log n) number of calls (and a constant number of calls in expectation) to BA-parent and next-child-flag, as well as O (log n) number of calls to heap-insert and heap-extract-min, and accesses to "arrays. " The claim then follows from standard deterministic heap implementations (which uses O (1) space per stored item and O (log n) time per query) and from Lemma 16.
We now state the properties of our on-the-fly graph generator for BA-graphs.
Definition 20. For a number of queries T > 0 and a sequence of BA-next-neighbor queries Q = (q(1), . . . , q(T )), let A(Q ) be the sequence of answers returned by an algorithm A on Q. If A is randomized, then A(Q ) is a probability distribution on sequences of answers.
Let Opt-BA n be the (randomized) algorithm that first runs the Markov process to generate a graph G on n nodes according to the BA model, stores G, and then answers queries by accessing the stored G. Let O-t-F-BA n be the algorithm O-t-F-BA run with graph-size n. From the definition of the algorithm, we have the following: Theorem 21. For any sequence of queries Q, Opt-BA n (Q ) = O-t-F-BA n (Q ).
We now conclude by stating the complexities of our on-the-fly BA generator.
Theorem 22. For any T > 0 and any sequence of queries Q = (q(1), . . . , q(T )), when using O-t-F-BA n it holds w.h.p. that, for all 1 ≤ t ≤ T : (1) The increase in the used space, while processing query t, is O (log 3 n). Therefore, the total space that is used to store the state of the generator after T queries is O (T · log 3 n). (2) The number of random bits used while processing query t is O (log 5 n).
(3) The time complexity for processing query t is O (log 6 n).
Proof. A query BA-next-neighbor(v) at time t is a trivial if at some t < t a query BA-next-neighbor(v) returns n + 1. Observe that trivial queries take O (log n) deterministic time, do not use randomness, and do not increase the used space. Since there are less than n 2 non-trivial queries, the theorem follows from Theorem 19 and a union bound.
We note that the various assertions in this article of the form of "with high probability ... is O (log c n)" can also be stated in the form of "with probability 1 − 1 n d ... is f (d ) · log c n. " Therefore, we can combine these various assertions, and together with the fact that the number of non-trivial queries is poly(n), we get the final result stated above.

A DIRECTION FOR EXTENDING TO GENERAL OUT-DEGREES
Given a random process that generates a Preferential-attachment graph with out-degree 1, Bollobás and Riordan [7] consider the following generalization for defining a process that generates a Preferential-attachment graph with general out-degree. A BA m n -graph, where BA m n denotes an n node Preferential-attachment graph with out-degree m, is constructed from a BA nm -graph by identifying each of the n nodes of the BA m n -graph with a block of m nodes in the BA nm -graph 11 Hence, a BA m n -graph can be generated on-the-fly by using an on-the-fly generator for a BA nmgraph, in a black-box manner, by increasing the complexity by a factor of m (up to poly-logarithmic factors). We leave working out the details for this generalization to higher out-degrees, as well as for other variants of generalization, for further research.

CONCLUSIONS
We introduce an approach to probing and accessing a huge random graph, sampled from a given distribution, in a way that does not require to first sample the whole huge graph and then access it. The latter approach may require prohibitive amounts of time, space, and randomness that could be avoided if only relatively small parts of the random graph are accessed. The feasibility of such savings may be especially challenging when the distribution at hand is usually defined by an evolving sequential process (over, e.g., nodes) such as in the Barabási-Albert Preferential Attachment model or the random recursive tree model.
We show how to achieve such savings for the Barabási-Albert graphs of out-degree 1, as well as for the evolving tree model, and we give on-the-fly generation algorithms for both models such that with probability 1 − 1/poly(n), each and every query is answered in polylog(n) time, and the increase in space and the number of random bits consumed by any single query are both polylog(n), where n denotes the number of vertices in the graph.

A IMPLEMENTATIONS OF DATA STRUCTURES A.1 Data Structure for φ(·)
We use two balanced binary search trees (or order statistic trees). One, called left, stores all vertices i such that front(i) nil. The other, called right, stores (the multi-set) {front(i) | front(i) nil}. To determine φ(a), we find, using tree right, how many nodes i have front(i) > a − 1 (and front(i) nil). Let this number be R. Using tree left, we find how many nodes i < a have front(i) nil. Let this number be L. Then φ(a) = R − L.
By standard implementations of balanced search trees the space complexity is O (k ) and all operations are done in time O (log k ) = O (log n)) Here, k denotes the number of nodes i such that front(i) nil.
A.2 Data Structure to Find the Node of Rank h in [a, n + 1] \ K We start with a number of definitions useful for specifying the data structure and its operations.
Note that for technical reasons for j = 1, we define Q (j) = 1 whether or not j ∈ Q. 11 Bollobás and Riordan define the process BA n slightly different from the model we are using in this article. According to their definition, the head of the edge e j is the node v i , for 1 ≤ i ≤ j − 1, with probability and is v j with probability 1 2j −1 (so there is some, vanishing, probability of forming self-loops).