## 1 Introduction

Consider a *fully dynamic* setting where we start from an initially empty graph on fixed vertices ,
and at each time step a single edge is either inserted in the graph or deleted from it, resulting in graph .
The problem of maintaining a large matching or a small vertex cover in such graphs has attracted a lot of research attention in recent years.
In general, one would like to devise an algorithm for maintaining a “good” matching and/or vertex cover with *update time*
(via a data structure that answers queries of whether an edge is matched or not in constant time),
where being “good” means to provide a good approximation to the maximum matching and/or the minimum vertex cover,
and the update time is the time required by the algorithm to update the matching/vertex cover at each step.

One may try to optimize the *amortized* (i.e., average) update time of the algorithm or its *worst-case* (i.e., maximum) update time, over a worst-case sequence of graphs. There is a strong separation between the state-of-the-art amortized bounds and the worst-case bounds.
A similar separation exists for various other dynamic graph problems, such as spanning tree, minimum spanning tree and two-edge connectivity.
Next, we provide a brief literature survey on dynamic matchings. (See [24, 4, 26, 27] for a detailed survey.)

In FOCS’11, Baswana et al. [4] devised an algorithm for maintaining a *maximal matching* with an expected amortized update time of
under the oblivious adversarial model.^{1}^{1}1The *oblivious adversarial model* is a standard model, which has been used for analyzing randomized data-structures such as universal hashing [13]
and dynamic connectivity [19].
The model allows the adversary to know all the edges in the graph and their arrival order, as well as
the algorithm to be used. However, the adversary is not aware of the random bits used by the algorithm,
and so cannot choose updates adaptively in response to the randomly guided choices of the algorithm.
Building on the framework of [4], Solomon [27] devised a different randomized algorithm whose expected amortized update time is .
Note that a maximal matching provides a 2-approximation for both the maximum matching and the minimum vertex cover.
Moreover, under the unique games conjecture (UGC), the minimum vertex cover cannot be efficiently approximated within any factor better than 2 [20].
In SODA’15, Bhattacharya et al. [8] devised a deterministic algorithm for maintaining -approximate vertex cover with
amortized update time .
In STOC’16, Bhattacharya et al. [9] devised a different deterministic algorithm for maintaining -approximate matching
with amortized update time .

All the known algorithms for maintaining a better-than-2 approximate matching or vertex cover require polynomial update time.^{2}^{2}2This statement is true for general graphs.
For low arboricity graphs, significantly better results are known; see [21, 17, 26].
In FOCS’13, Gupta and Peng [16] devised a deterministic algorithm for maintaining -approximate matching with
a worst-case update time .
Bernstein and Stein [7] maintained -approximate matching with an amortized update time ,
generalizing their earlier work [6] for bipartite graphs (in which they provide a worst-case update time guarantee).

There are two main open questions in this area.
The first is whether one can maintain a *better-than-2* approximate matching in *amortized* polylogarithmic update time.
The second is the following:

###### Question 1

Can one maintain a “good” (close to 2) approximate matching and/or vertex cover with *worst-case* polylogarithmic update time?

In a recent breakthrough, Bhattacharya, Henzinger and Nanongkai
devised a deterministic algorithm that maintains a *constant* approximation to the minimum vertex cover,
and thus also a constant-factor estimate of the maximum matching size, with polylogarithmic worst-case update time.
While this result makes significant progress towards Question 1, this fundamental question remained open.^{3}^{3}3Later (in SODA’17 Proc. [10]) Bhattacharya et al. significantly improved the approximation factor all the way to .
However, our result was done independently to [10].
Moreover, even if one considers the improved result of [10], it solves Question 1 in the affirmative only for vertex cover, leaving the question on matching open;
see App. A for a more detailed discussion, which also covers a recent paper by Arar et al. [3].
In particular, no algorithm for maintaining a matching with sub-polynomial worst-case update time was known,
even if a polylogarithmic approximation guarantee on the matching size is allowed!

In this paper we devise a randomized algorithm that maintains an *almost-maximal matching (AMM)* with a polylogarithmic update time.
We say that a matching for is *almost-maximal* w.r.t. some slack parameter , or *-maximal* in short,
if it is maximal w.r.t. any graph obtained from after removing arbitrary vertices, where is a maximum matching for .
Just as a maximal matching provides a 2-approximation for the maximum matching and minimum vertex cover, an AMM provides a -approximation.
We show that for any ,
one can maintain an AMM with worst-case update time , where the -maximality guarantee holds with high probability.
Specifically, our update time is ; although reducing this upper bound towards constant is an important goal,
this goal lies outside the scope of the current paper (see Section 8 for some details).

The algorithm’s worst-case guarantee can be strengthened, using [28], to bound the number of changes (replacements) to the matching. Optimizing this measure is important in applications such as job scheduling, web hosting and hashing, where a replacement of a matched edge by another one is costly. This measure is important also in cases where the matching algorithm is a blackbox subroutine inside a larger data structure (cf. [7, 2]).

Our result resolves Question 1 in the affirmative, up to the dependency.
In particular, under the unique games conjecture, it is essentially the best result possible for the dynamic vertex cover problem.^{4}^{4}4Since our result (which started to circulate in Nov. 2016) was done independently of the -approximate vertex cover result of [10], it provides the first -approximation
for both vertex cover (together with [10]) and (integral) matching.

On the way to this result, we devise a *deterministic* algorithm that maintains an almost-maximal matching with a polylogarithmic update time
in a natural *offline model* that is described next.
This deterministic algorithm may be of independent interest, as discussed in Section 1.1. Our randomized algorithm for the oblivious adversarial model is derived from this deterministic algorithm in the offline model,
and this approach is likely to be useful in other dynamic graph problems.

### 1.1 A Technical Overview

Our algorithm and its analysis are elaborate and quite intricate.
While this may be viewed as a drawback, the strength of our paper is not just in the results we achieve, but also in the techniques we develop in establishing them.
We believe that our techniques are of independent interest and will find applications to other dynamic graph problems, also outside the context of matchings

The offline model. We start with describing an offline model that is a useful starting point for designing algorithms for matching in fully dynamic graphs. Suppose that the entire update sequence is known in advance, and is stored in some data structure.
Suppose further that for any , accessing the th edge update via the data structure is very efficient, taking or even time.
A natural question to ask is whether one can exploit this knowledge of the future to obtain better algorithms for maintaining a good matching and/or vertex cover.
Consider in particular the *maximal matching* problem.
Handling edge insertions can be done trivially in constant time. Handling edge deletions is the problematic part.
Consider a deletion of a matched edge from the graph.
If has a free neighbor, we need to match them, and similarly for .
The algorithm may naively scan the neighbors of and , which may require time.
Surprisingly, this naive bound is the state-of-the-art for general (dense) graphs, unless one allows both randomization and amortization as in [4, 27].
Can one do better in the offline setting?

A natural strategy is to match a vertex along its incident edge that will be deleted last. Indeed, by the time edge gets deleted from the graph, all other edges incident on must be “new”, i.e., having been inserted to the graph since the last time was matched. So when becomes free, we should be able to afford (in the amortized sense) scanning all neighbors of , to find a free neighbor.

This approach, however, only works when all neighbors of are free, which holds only initially. If some of them are matched, and we stick to the strategy of picking the incident edge that will be deleted last, the update algorithm itself may be forced to delete matched edges from the matching. Alas, due to deletions of matched edges by the algorithm, whenever a vertex becomes free, its neighbors are not necessarily new. Instead, one may want to pick the incident edge that will be deleted last among those leading to free neighbors – but determining the free neighbors of a vertex is indeed the crux of this problem!

Despite this hurdle, we argue that a dynamic maximal matching can be maintained in the offline setting *deterministically*
with constant *amortized* update time.
To this end, we make the following surprisingly simple observation:
The machinery of [4, 27] extends seamlessly to the offline setting above.
More specifically, instead of choosing the matched edge of uniformly at random among a certain subset of adjacent edges
(which is computed carefully by the algorithms [4, 27], details will be provided next),
in the offline setting we choose the matched edge to be the one that will be deleted last among .
It is not difficult to verify that the analysis of [4, 27] carries over to the offline setting directly.

Notice that the resulting deterministic algorithm for the offline setting is inherently *amortized*, whereas our focus in this work is on *worst-case* bounds.
To obtain good worst-case bounds, we build on the machinery of [4, 27]. The price of translating the amortized bounds of [4, 27] into a similar worst-case bound is that the maintained matching is no longer maximal, but rather almost-maximal.^{5}^{5}5The amortized update time analysis of the algorithm from [4] (both the FOCS’11 and subsequent journal SICOMP’15 versions) was erroneous,
but was corrected in a subsequent erratum by the same authors. (The amortized update time analysis of the algorithm from [27] is different than the one used in [4], and does not have that mistake.) Although our algorithm builds on the machinery of [4, 27],
the mistake in [4] does not affect the current paper, as we provide an independent analysis for a different algorithm, which bounds the worst-case update time of our algorithm rather than the amortized update time.
This translation is highly non-trivial, and is carried out in two stages.
First, we consider the offline setting, and devise a deterministic algorithm there.
Coping with the offline setting is easier than with the standard setting, as it allows us to ignore intricate probabilistic considerations, and to handle them separately.
The second stage is to convert the results for the offline setting to the standard setting.
The algorithm itself remains essentially the same. (Instead of choosing the edge that will be deleted last, choose a random edge.)
On the other hand, showing that the maintained matching remains almost-maximal requires more work.
This two-stage approach
thus provides an elegant way to analyze randomized dynamic algorithms, and we believe it would be useful in other dynamic graph problems as well.

Furthermore, the offline setting seems important in its own right. Indeed, in some real-life situations (e.g., road networks),
we might get some estimated schedule regarding future deletions of edges.
Moreover, there are some applications where the users of the network themselves may determine (to some extent) the lifespan of an edge (see, e.g., [25]).
Note also that our algorithm does not need a complete knowledge of the future,
just an oracle access to an edge that will be deleted after a constant fraction of the other edges (from ) have been deleted.
In fact, the oracle does not have to be correct all the time, just on average.
Therefore, it seems that the offline setting may capture various practical scenarios.

The framework of [4, 27].
We next provide a rough description of the *amortized* framework of [4, 27].^{6}^{6}6Note that [27] builds on the framework of [4] and extends it; for clarity, we will not distinguish between [4] and [27].

Matched edges will be chosen randomly. If an edge is chosen to the matching uniformly at random among adjacent edges of either or , w.l.o.g. ,
we say that its *potential* is .
Under the oblivious adversarial model, the expected number of edges incident on that are deleted from the graph before deleting edge
is .
Thus, following a deletion of a matched edge with potential from the graph, we have time to handle and in the amortized sense.

Each vertex in the graph maintains a dynamically changing *level* ;
roughly speaking, ’s level will be logarithmic in the potential value of the only matched edge adjacent to .
Free vertices will be at level , and matched vertices will be at levels between 0 and .
Based on the levels of vertices, a dynamic edge orientation is maintained, where each edge is oriented towards the lower level endpoint.

When a vertex becomes free, the algorithm (usually) chooses a mate for it randomly. If this mate is already matched, say to , the algorithm will have to delete edge from the matching in order to match with . However, note that we can compensate for the loss in potential value (caused by deleting edge from the matching) if this potential loss is significantly smaller than the potential of the newly created matched edge on . Recalling that vertices’ levels are logarithmic in their potential, all neighbors of with lower level should have potential value at most half the potential value of the edge that was just deleted on . In other words, for each of these neighbors, we can afford to break their old matched edge. Consequently, the mate of will be chosen uniformly at random among ’s neighbors with lower level.

A central obstacle is to distinguish between neighbors of with level and those with lower level.
Indeed, it is possible that most of ’s neighbors have level , and none of them can be chosen as mates for .
Roughly speaking, the execution of the algorithm splits into two cases.
If the current out-degree of is not (much) larger than its out-degree at the time its old matched edge got created,
then we should be able to afford to scan all of them, due to sufficiently many adversarial edge deletions that are expected to occur.
Notice that in this case the charging argument is based on *past* edge deletions.

The second case is when the out-degree of is (much) larger than what it was when the old matched edge got created.
The time needed for distinguishing ’s neighbors at level from those at lower levels could be significantly larger than
the “money” that we got from past edge deletions. In this case the algorithm *raises* to a possibly much higher level ,
where there are not too many neighbors for at that level as compared to the number of its neighbors at lower levels.
Having raised to that level, we can perform the random sampling of its mate among all its neighbors of level lower than .
Notice that in this case the charging argument is not based on past edge deletions, but rather on *future* edge deletions.
(Future edge deletions may not occur, but we may charge the edges remaining in the graph to their last insertion.)

Our approach.
Notice that the framework of [4, 27] is inherently *amortized*:
Every once in a while there are very “expensive” operations, which are charged to “cheap” operations that occurred in the past or will occur in the future.
To obtain a low worst-case update time, we should be *cheap in any time interval*, meaning that we can rely neither on the past nor the future.
Consider a matched edge that is deleted by the adversary. We expect the adversary to make many edge deletions on at least one of these endpoints
before deleting this edge. However, it is possible that all these edge deletions occurred a long time ago.
The worst-case algorithm will not be able to exploit these edge deletions at this stage.

Consider the offline setting, and let be arbitrary matched edges with the same potential value .
For each such edge , let be its *sample*, i.e., the set of all edges from which was chosen to the matching.
In the offline setting, we are guaranteed that will be deleted only after all other edges from its sample have been deleted.
However, it is possible that the adversary first deletes the first edges from the samples of each and every one of the matched edges,
and only then turn to deleting the matched edges.
(Recall that the worst-case update time should hold with respect to a worst-case update sequence.)
Assuming is large, it takes a long time for the adversary to delete the first edges from all samples.
During all this time, the amortized algorithms of [4, 27] remain idle.
On the other hand, an algorithm with a low worst-case update time must be active throughout this time interval,
because immediately afterwards the adversary can remove the matched edges from the graph rather quickly,
much faster than the algorithm can add edges to the matching in their place, leading to a poor approximation guarantee.
Consequently, at any point in time, the algorithm needs to be proactive and protect itself from such a situation happening in the future.

Generally, while in an amortized algorithm invariants may be violated from time to time, and then restored via expensive operations,
an algorithm with a low worst-case update time should persistently “clean” the graph, making sure that it is never close to violating any invariant.
Naturally, we will need to maintain additional invariants to those maintained by the amortized algorithms of [4, 27].
To this end we employ four different data structures that we call *schedulers*, each for a different purpose.
Each of these schedulers consists of a logarithmic number of sub-schedulers, a single sub-scheduler per level.
Next we fix some level , where is the potential of the matched edges on that level, and focus on it.

The scheduler unmatch-schedule will periodically remove edges from the matching, one after another,
by always picking a matched edge whose *remaining sample* (i.e., the set of edges from the sample that have not been deleted yet from the graph) is smallest;
indeed, such an edge is closest to getting deleted by the adversary.
As strange as it might seem, this strategy enables us to guarantee that only few matched edges will ever be deleted by the adversary.
Note that removing a matched edge from the matching is not a cheap operation, because we need to find new mates for the two endpoints of the edge.
Therefore, the execution of the scheduler must be *simulated* over sufficiently many adversarial update operations.
During this time interval, the adversary may perform additional edge deletions.
Nevertheless, we control the rate at which the scheduler is working, and we can make sure that it works sufficiently faster than the adversary.
Thus, in this game between the scheduler and the adversary, the scheduler will always win.

The role of unmatch-schedule is to make sure that all the samples are pretty full. Intuitively, this provides the counter-measure of relying on past adversarial edge deletions, as done in the amortized argument. The next scheduler rise-schedule provides the counter-measure of relying on future adversarial edge deletions. Recall that future edge deletions are used in the amortized argument only in the case that a vertex had to rise to a higher level. A vertex is rising only if its out-degree became too large with respect to its current level. For this reason, we need to make sure that the out-degrees of vertices are always commensurate with their level, and this is where rise-schedule comes into play. This scheduler will periodically raises vertices to the level of which it is in charge, one after another, by always choosing to raise a vertex with the largest number of neighbors at level lower than . Intuitively, such a vertex is closest to getting chosen to rise to level or higher. Although the two schedulers are based on the same principle, the game that we play here is not between the scheduler and the adversary, because here the algorithm itself may change the level of vertices and their out-degree, so rise-schedule has to compete against both the adversary and the algorithm. In contrast to the other scheduler, speeding up the rate at which rise-schedule works will not help winning the game. Instead, we manage to bound the speed of the scheduler with respect to that of the (adversary + algorithm), which enables us to show that the out-degree of vertices is always in check.

For the offline model, these two schedulers suffice. However, in the standard oblivious adversarial model, the adversary will manage to destroy some matched edges from time to time. The scheduler free-schedule periodically handles all the vertices that become free due to the adversary, one after another. Using the property that all samples are always pretty full, we manage to prove that only an -fraction of the matched edges get destroyed by the adversary at any time interval. Note that this bound is probabilistic – to make sure that it indeed occurs with high probability, we also use another scheduler shuffle-schedule, which periodically removes a random edge from the matching. For technical reasons, it is vital that shuffle-schedule would work sufficiently faster than some of the other schedulers.

Finally, we point out another difficulty in getting a worst-case update time out of the algorithms of [4, 27].
Following a single update operation, these amortized algorithms may remove *themselves* many matched edges from the matching, one after another.
Before this process finishes, the algorithms make sure to add new matched edges instead of the ones that got removed.
However, these algorithms may require a lot of time before starting to “repair” the matching.
In particular, if we simulate their execution, performing just a few computational steps per adversarial update,
we might get a very poor matching at some points in time, even without the “help” of the adversary!
Our new algorithm employs the aforementioned schedulers to guarantee that it never removes many matched edges before adding others in their place.

Technical Highlights.
Describing the algorithm and analysis will require some lengthy preliminaries.
Before diving into details,
we wish to highlight some novel technical aspects of the paper.

The schedulers unmatch-schedule and rise-schedule are implemented and analyzed by analogy to a balls and bins game between two players from [22] (Section 4). While this game has been studied before even in the context of dynamic graph algorithms that guarantee worst-case update times [29, 1, 31], the worst-case update time achieved in these papers (for the dynamic all-pairs shortest paths problem in [29, 1] and for the dynamic MSF in [31]) is polynomial. In particular, the approach that we take is inherently different from that in previous works [29, 1, 31], and we believe that it will inspire more usage of this game in dynamic graph algorithms with polylogarithmic worst-case update time in the future.

In the balls and bins formulation, unmatch-schedule competes with an adversary that is removing balls from bins and trying to get the number of balls in some bin below a certain threshold (Section 4.2). On the other hand, unmatch-schedule can remove bins to prevent them from becoming under full. Since we make sure that unmatch-schedule works sufficiently faster than the adversary, we can show that unmatch-schedule wins the game, which ensures that all the samples are close to full. In Section 4.3 we analyze a similar in spirit yet far more intricate game, concerning rise-schedule. There balls are being added to bins, and we don’t want the bins become over full. The main twist is that the algorithm itself is competing against rise-schedule, so changing the speed of rise-schedule relative to the adversary does not help. The key question is: can rise-schedule maintain all bins below a certain overload threshold? This ensures that outdegrees of vertices are commensurate with their level. The difficulty is not in analyzing the abstract balls and bins game, but rather in translating the algorithm’s operation to this game, which is where the various schedulers come into play. One of the challenges in this translation is to cope with the interdependencies between games played at multiple levels.

In Section 6 (Lemma 6.1) we show that (with high probability) there cannot be too many temporarily free vertices due to the adversary at any point in time, thus ensuring that the matching maintained by the algorithm is almost-maximal. To prove this lemma, we build on the invariant that all samples are close to full (due to unmatch-schedule). Alas, this invariant by itself is not enough for guaranteeing a high probability bound, and thus we resort to the random shuffling provided by shuffle-schedule. We believe that this shuffling “fix” can be applied in various dynamic graph problems, also outside the scope of matchings or even of worst-case bounds, and is one example of the generality of our techniques. In general, we believe that our techniques are of broader applicability and interest than to the area of dynamic matchings.

### 1.2 Organization

##### Main text.

The invariants, data structures and basic principles that govern the operation of the update algorithm are presented in Section 2, whereas the procedures that the update algorithm employs, as well as the analyses of those procedures, are given in Section 3. The various schedulers used and orchestrated by our algorithm are the focus of Section 2.2, but are also described in other parts of Section 2 and in Section 3. Section 4 is devoted to the analysis of those schedulers. Our mechanism for resolving potential conflicts between the schedulers is described and analyzed in Section 5.

We prove in Section 6 that the matching maintained by our algorithm is almost-maximal. Lemma 6.1 is central to the argument of the almost-maximality guarantee, and its proof is spread over Sections 6.1–6.3. In Section 6.4 we derive the main results of this paper as corollaries of Lemma 6.1.

Two simplifying assumptions used by our algorithm are formally justified in Section 7.

Finally, a brief discussion with some open problems is given in Section 8.

##### Appendix.

## 2 The Update Algorithm, Part I: Basics and Infrastructure

###
2.1 *Levels*: data structures and invariants

Our algorithm builds on the amortized algorithms by [4, 27],
which maintain for each vertex a *level* , with , where .^{7}^{7}7Note that we use logarithms in base ,
whereas [4] and [27] use logarithms in base 2 and 5, respectively. For the offline setting, we can use base 2, and this change leads to shaving a logarithmic factor from the update time.
Based on the levels of vertices, a dynamic edge orientation is maintained; the out-degree of a vertex under this orientation, which is the number of its outgoing edges, serves as an important parameter.
The amortized algorithms of [4, 27] maintain the following invariants (Invariants 1(a)-1(d)) at all times.
This means that these invariants hold at the *end of the execution* of the corresponding update algorithms,
i.e., before the next update operation takes place.
These invariants may become violated *throughout the execution* of the update algorithms.
Furthermore, the runtime of the update algorithms of [4, 27] may be in the worst case,
thus it may take them a lot of time to restore the validity of these invariants, once violated.
We added a comment to the right of each of these invariants, where the comment is /* maintained */ or /* partially maintained */,
to indicate whether the respective invariant is maintained fully or only partially by our new algorithm.

###### Invariant 1

(a) Any matched vertex has level at least 0. /* maintained */

(b) The endpoints of any matched edge are of the same level, and this level remains unchanged until the edge is deleted from the matching.
(We henceforth define the level of a matched edge, which is at least 0 by item (a), as the level of its endpoints.) /* maintained */

(c) Any free vertex has level -1 and out-degree 0. (The matching is maximal.) /* partially maintained */

(d) An edge with is *oriented* by the algorithm as . (If , the orientation of will be determined
suitably by the algorithm.) /* partially maintained */

Our algorithm will maintain Invariants 1(a) and 1(b) at all times, as in the amortized algorithms [4, 27]. On the other hand, we maintain Invariants 1(c) and 1(d) only partially. Next, we make this statement precise.

Once a matched vertex becomes free, its level will exceed -1 until the update algorithm handles it.
We say that such a vertex is *temporarily free*, meaning that it is not matched to any vertex yet, but its level and out-degree remain temporarily as before.
From now on, we distinguish between *free* vertices and *temporarily free* vertices:
Free vertices are unmatched and their level is -1, while temporarily free vertices are unmatched and their level exceeds -1.
By making this distinction, Invariant 1(c) holds true as stated.
Moreover, combining it with Invariant 1(a), we obtain:

###### Invariant 2 (Invariant 1’(c))

Any vertex of level -1 is unmatched and has out-degree 0.

Note that Invariants 1(c) and 2 do not apply to temporarily free vertices. In particular, there may be edges between temporarily free vertices, which are unmatched by definition, meaning that the maintained matching is not necessarily maximal. The challenge is to guarantee that the number of temporarily free vertices is small with respect to the number of matched vertices, yielding an almost-maximal matching.

Temporarily free vertices are handled via data structures that we call *schedulers*.
We distinguish between vertices that become temporarily free due to the adversary and those due to the update algorithm itself.
For each level , we maintain a queue of level- vertices that become temporarily free due to the adversary,
and the vertices in will be handled, one after another, via appropriate schedulers.
As vertices in the queues are being handled and are thus removed from the queues, other vertices may become temporarily free due to the adversary and thus join the queues.
We will need to make sure that the total number of vertices over the queues of all levels is in check at any point in time.
In contrast, there is no reason to add vertices that become temporarily free due to the update algorithm itself to the queues,
as the update algorithm controls the rate in which such vertices become temporarily free, hence it will create temporarily free vertices periodically
at a rate that matches the time needed to handle them, again via appropriate schedulers.
The various schedulers need to work together, without conflicting each other;
the exact way in which they work constitutes the heart of our algorithm, and is described first in Section 2.2, and then in more detail in subsequent sections.
The analysis of the schedulers is provided in Section 4, and our mechanism for resolving conflicts between them is presented in Section 5.

A temporarily free vertex that is being handled by some scheduler is called *active*, and the process of handling it,
which involves updating various data structures, may be simulated over multiple update operations.
Therefore, there might be *inconsistencies* in the data structures throughout this process concerning the active vertices.
In particular, an active vertex that rises or falls from level to level is stored as a level- vertex in the data structures
of some of its neighbors and as a level- vertex in the data structures of its remaining neighbors.
Moreover, among the edges whose orientation needs to be “flipped” as a result of this rise or fall of ,
so as to satisfy Invariant 1(d), some have performed the flip and the rest have not done so yet.
To account for these inconsistencies, we hold a list of active vertices, denoted , and we will make sure that this list is of size at any point in time.^{8}^{8}8Note that , but we make no attempt here to optimize factors that are polynomial in .
(Although the number of temporarily free vertices should be small with respect to the number of matched vertices, it may be significantly larger than the number of active vertices.)
By bounding the number of active vertices, we can *authenticate* the updated information concerning active vertices efficiently;
this *authentication process* is described in Section 2.3.
Our algorithm will maintain Invariant 1(d) for any edge with both and not in the list, or in other words:

###### Invariant 3 (Invariant 1’(d))

Any edge , with and , is *oriented* as .

Following [27], for each vertex , we maintain linked lists and of its neighbors and outgoing neighbors, respectively. The information about ’s incoming neighbors will be maintained via a more detailed data structure :
A hash table, where each element corresponds to a distinct level .
Specifically, an element of corresponding to level holds a *pointer* to the head of a non-empty linked list that
contains all incoming neighbors of with level .
If that list is empty, then the corresponding pointer is not stored in the hash table.
Hence the total space over all hash tables is linear in the dynamic number of edges in the graph.

By Invariant 3, any edge , with and is oriented as .
The consequence is that for any such edge, and . In particular,
the data structure provides information on the levels of ’s incoming neighbors that do not belong to the list.
It will not be in ’s *responsibility* to maintain the
data structure , but rather within the responsibility of ’s incoming neighbors.
On the other hand, no information whatsoever on the levels of ’s outgoing neighbors is provided by the data structure .
In particular, to determine if has an outgoing neighbor at a certain level (most importantly at level -1, i.e., a free neighbor),
we need to scan the entire list . On the other hand, has an incoming neighbor at a certain level iff
the corresponding list is non-empty.
We keep mutual pointers between the elements in the various data structures: For any vertex and any outgoing neighbor of ,
we have mutual pointers between all elements .
(We do not provide a description of the trivial maintenance of these pointers for the sake of brevity.)

###
2.2 *Schedulers*: overview and invariants

Our algorithm will employ four different schedulers, each for a different purpose.
Each of these schedulers consists of sub-schedulers, a single sub-scheduler per level .
It is instructive to think of each sub-scheduler as running threads of execution,
and of its scheduler as synchronizing threads, a single thread per level.
Each thread executed by a level- sub-scheduler, hereafter *level- thread*, will run in exactly the same amount of time
, by “sleeping” if finishing the execution prematurely.
We assume that the constant hiding in this -notation is sufficiently large, thus rendering sufficiently larger than the overall runtime of
any procedure described in the sequel that has a total runtime of ; as will be shown in the sequel, this enables us to guarantee
that each level- thread finishes its run within the “time slot” of computation steps allocated to it.

Synchronization.
As mentioned, any level- thread runs in exactly the same amount of time , for each level .
However, to achieve a low worst-case update time, the execution of this thread is not carried out at once, but is rather carried out (hereafter, *simulated*) over multiple update operations, carrying out (or simulating) a fixed number of computation steps per update operation.
We refer to that number of computation steps as a *simulation parameter*,
and for technical reasons we use
two simulation parameters, and .
Each of the two simulation parameters does not change with the level, and is not associated with a level- thread or with the sub-scheduler running it,
but rather with the corresponding scheduler. Each of the schedulers will use exactly one of these two simulation parameters and will stick to it throughout;
since ,
in this way we make sure that some schedulers will consistently work faster than others by a logarithmic factor, a property that will be useful for our analysis.
Note that the simulation parameters, or , determine the number of update operations required to finish the execution of the thread,
or , respectively. We refer to this number as the *(level ) simulation time*; unlike the simulation parameters,
which do not change with the level, the corresponding simulation times grow with each level by a factor of .
Therefore the simulation time of a thread depends not only on the respective scheduler but also on the respective level ,
and is associated with both the respective thread and the sub-scheduler running it.
A sub-scheduler or a thread with a lower (respectively, higher) simulation time than another is said to be *faster* (resp., *slower*) than it;
we may compare the simulation times of sub-schedulers or threads even if they are at different levels.
For any levels and such that , a level- sub-scheduler may be either faster or at the same speed as any level- sub-scheduler.
Since and ,
any two such sub-schedulers are at the same speed if only if and the simulation time of the level- sub-scheduler is
while the simulation time of the level- sub-scheduler is . In the complementary case, i.e., when either
or when the schedulers corresponding to those sub-schedulers have the same simulation parameter, the level- sub-scheduler is slower than
the level- sub-scheduler by at least a factor of (i.e., the simulation time of the level- sub-scheduler is higher than that of the level- scheduler by that factor).

The execution threads are run by the various schedulers in a precise *periodic* manner so as to achieve the following *nesting property*.
Viewing the time axis as a 1-dimensional line and the simulation times of the execution threads as intervals of this line, the intervals of any two threads are either disjoint or one of them is nested in the other; in what follows we may identify threads with the corresponding 1-dimensional intervals, and may henceforth say that two threads are disjoint or one of them is nested in the other.
Multiple threads may be nested in a single thread at a higher level. Moreover, multiple level- threads may be nested in a single level- thread, for any ,
but this may happen for at most such threads, and only if their simulation time is
while that single thread’s simulation time is .

The description of the schedulers is provided below. The schedulers
and shuffle-schedule have a simulation parameter of , whereas the scheduler unmatch-schedule has a simulation parameter of ,
and is thus slower than the other schedulers by a factor of .
Consider any scheduler among the four, and denote its simulation parameter by , where is either or .
While it may be instructive to view the execution threads (over all levels) that this scheduler runs following every update operation as operating in parallel, these threads are handled *sequentially*.
It is technically useful to handle these threads by decreasing order of simulation times, and thus by decreasing order of levels,
i.e., the -level thread is handled first, then the -level thread, etc., until the -level thread. Following each update operation, the -level thread simulates computation steps of its execution,
the -level thread simulates computation steps of its own execution, and so on.
When any of these threads finishes its execution (sleeping if the execution has finished prematurely),
the corresponding sub-scheduler starts executing a new thread at that level, which again simulates computation steps following each update operation.
Hence the total time spent by this scheduler following a single update operation is ,
which is either or , depending on whether is or , respectively. Observe that this scheme gives rise to a worst-case update time of , and this bound holds deterministically.

Observe that when any level- thread starts its execution, all lower level threads run by the same scheduler are also about to start their execution, and they will finish their execution before the level- thread does. As for threads run by other schedulers, things are slightly more involved, as a lower level thread may have the same simulation time as that level- thread (if their simulation parameters are and , respectively). We can generalize the above observation by noting that all lower level threads run by the four schedulers start their execution at the same update operation as the level- thread does, and they will finish their execution before or at the same update operation as the level- thread does. This observation, which follows from the fact that we handle the threads by decreasing order of simulation times (and levels) and from the aforementioned nesting property of threads, will play a central role in our mechanism for resolving potential conflicts between the various schedulers, described in Section 5.

Each of the level- sub-schedulers will run a single level- thread at any point in time.
Each level- thread will handle vertices of level at most , but some of these threads will handle a super constant number of such vertices.
We will make sure to address this issue, and also show that any level- thread (including those that handle a super constant number of vertices) requires an overall time of to complete its execution.

1st scheduler. The first scheduler free-schedule handles all vertices that become temporarily free due to the adversary. More specifically, for each level , the corresponding sub-scheduler
handles all vertices of , one after another.
The exact procedure for handling a temporarily free vertex , , is described in Section 3.2. Procedure will be executed by a single level- thread corresponding to that runs in an overall time of ,
simulating steps of this procedure following each update operation. The execution threads (over all levels) executed by free-schedule are handled sequentially. Note that these threads execute different calls of Procedure handle-free, which handle vertices at different levels.
As mentioned, the -level thread is handled first,
then the -level thread, etc., until the -level thread. Following each update operation, the -level thread simulates steps of its own call of Procedure handle-free,
the -level thread simulates steps of its own call, and so on, hence the total time spent by free-schedule following a single update operation is
.

By the same principle, the total time spent by rise-schedule and shuffle-schedule following a single update operation
will be bounded by . On the other hand, unmatch-schedule has a simulation parameter of rather than ,
hence the total time spent by this scheduler following a single update operation will be bounded by .

2nd scheduler.
The second scheduler unmatch-schedule removes matched edges from the matching in a specific order.
As strange as it might seem, this strategy enables us to guarantee that the remaining matched edges are unlikely to be destroyed by the adversary.
More specifically, for each level , the corresponding sub-scheduler removes level- edges from the matching,
one after another, in the following way.
Similarly to the amortized algorithms, each level- matched edge is sampled uniformly at random from edges,
but there is a difference: While in the amortized algorithms the matched edge is sampled from precisely edges, here, for technical reasons,
we sample the matched edge from between and edges.
(In the offline setting, we choose the edge that will be deleted last among those.)
We denote this edge set by , and refer to it as the *sample space* (shortly, *sample*) of edge . As time progresses, some edges of may be deleted from the graph;
we denote by the *original* sample of , with , and by its sample *remaining* at time , omitting the subscript when it is clear from the context.
The goal of is to guarantee that the samples of all level- matched edges will never reach :

###### Invariant 4

For any level- matched edges with and any , .

To maintain this invariant, will always remove a matched edge of smallest remaining sample.
Observe that the samples are changed only due to edge removals,
and each edge removal reduces the size of at most two samples by one unit each.
Hence, it is easy to maintain the samples of all level- edges via a data structure that supports all the required operations, including the removal of a matched edge of smallest sample, in constant time; we do not describe this data structure for the sake of brevity.
For each level- matched edge that is removed by , its two endpoints and become temporarily free,
and they are handled by appropriate calls to Procedure handle-free. More specifically, we execute Procedure and then by running a level- thread, which runs in an overall time of , simulating steps of execution following each update operation.
The intuition as to why is able to maintain Invariant 4 is the following.
(See Section 4.2 for the formal argument.)
Since and ,
the simulation time of a thread run by (which designates the number of update operations needed for simulating its entire execution) is .
In other words, can remove a level- matched edge within adversarial update operations.
On the other hand, the expected number of adversarial edge deletions needed to turn a “full” level- matched edge (with sample )
into an “under full” edge (with sample ) is .
Thus is faster than the adversary by at least a logarithmic factor, assuming (which holds when ),
a property that suffices for showing that no edge is ever under full, or in other words, the samples of all level- matched edges will always be in check.
This is the basic idea behind maintaining the validity of Invariant 4 in any level for which the simulation time satisfies .
This invariant, in turn, guarantees that the adversary is unlikely to delete any particular edge from the matching,
using which we show (in Section 6) that the maintained matching is always almost-maximal with high probability.
The complementary regime of levels, namely, levels with simulation time satisfying , is trivial and does not rely on Invariant 4,
as then the adversary does not make any edge deletion within the time required by a level- thread to complete its entire execution.

3rd scheduler.
Let denote the set of neighbors of with level strictly lower than , and write .
For each vertex , we will maintain the values for all levels greater than the current level of .
For any level , the corresponding value will not be maintained, and the algorithm will have to compute it on the fly, if needed.
The algorithm of [4] maintains the invariant that , for any and .
(Recall that is taken to be constant in [4], whereas here we take to be .)
The scheduler rise-schedule maintains the following relaxation of the invariant from [4], and it does so by raising vertices to higher levels in a specific order, as described next.

###### Invariant 5

For any vertex and any level , .

For each level , the corresponding sub-scheduler is responsible for maintaining the invariant with respect to that level.
Whenever a new level- thread is initiated by , it starts by *authenticating* the values over all vertices using the list.
(The authentication process takes time to guarantee that all values are up to date, and is described in Section 2.3.)
Then the thread picks a vertex whose value is highest among all vertices with level lower than .
Observe that each change of a value is either an increment or a decrement of one unit.
Hence it is easy to maintain the level- values of all relevant vertices
via a data structure that supports all the required operations, including the extraction of a vertex with highest level- value,
in constant time; we do not describe this data structure for the sake of brevity.
These two steps (authenticating the values and picking a vertex of maximum value, thus extracting it from the data structure) can be implemented within time , and are therefore carried out by the thread “instantly”, i.e., without simulating their execution over subsequent update operations.
The same execution thread continues to removing ’s old matched edge (if exists) from the matching, and raises to level by
executing Procedure , whose description is provided in Section 3.1.
The execution of this procedure, however, cannot be carried out instantly, so it is simulated over multiple update operations; we make sure to simulate
execution steps of this procedure following each update operation.
Then the same execution thread handles the two temporarily free vertices and using Procedure handle-free, which is again simulated over multiple update operations.
That is, the same thread continues to executing the call to and then the call to , simulating execution steps following each update operation.

4th scheduler.
The fourth scheduler shuffle-schedule removes matched edges from the matching uniformly at random. By working sufficiently faster than some of the other schedulers,
it forms a dominant part of the algorithm, using which we basically show (in Section 6.2) that it provides a near-uniform random shuffling of the matched edges.
This random shuffling facilitates the proof of the assertion that the adversary is unlikely to delete any particular edge from the matching. More accurately, to prove this assertion, it suffices that shuffle-schedule would be sufficiently faster than unmatch-schedule, for technical reasons that will become clear later on.
For each level , the corresponding sub-scheduler
will always pick a matched edge uniformly at random among all remaining level- edges, and will remove it from the matching.
As with , for each level- matched edge that is removed by , its two endpoints and become temporarily free,
and they are handled by appropriate calls to Procedure handle-free. Moreover, as before, we execute these calls (to and then to )
by running a level- thread, which runs in an overall time of . The difference is that now we simulate (rather than ) execution steps following each update operation,
which ensures that shuffle-schedule is faster than unmatch-schedule by a logarithmic factor.
We only need to apply the shuffling in levels for which the simulation time satisfies , as
in the complementary regime () the adversary does not make
any edge deletion within the time required by a level- thread to complete its entire execution, and then a random shuffling is redundant.

### 2.3 The authenticating process

By invariant 3, any edge between two non-active vertices and is oriented towards the lower level endpoint,
i.e., the corresponding data structures of and are updated with the right levels of and ,
thus if , then and .
On the other hand, if or are active, then these data structures may not be updated with the right levels yet.
The fact that the data structures are outdated should not be viewed as an error or an exception of the algorithm,
but rather as an inherent consequence to the way our algorithm works, by simulating the execution of the various procedures over multiple update operations.
We should henceforth handle this issue of having outdated data structures in a systematic manner.
In particular, for every vertex that changes its level, hereafter, in the process of either *falling* to a lower level or *rising* to a higher level (see Section 3.1 for more details), the data structures of and some of its neighbors might not have been updated regarding ’s new level.
Noting that any vertex that changes its level (i.e., falls or rises) must be active to do so, we can efficiently authenticate the updated levels of vertices whenever needed
by monitoring the short list.

Consider any procedure used by our update algorithm that is carried out by a thread whose execution is simulated over multiple update operations. Throughout the execution of that procedure, many vertices may change their level, possibly multiple times. For this reason, just before the end of the execution, we perform an authentication process that takes time. More specifically, if the procedure handles vertex , then we scan the entire list, looking for neighbors of . For any such neighbor that we find, we update the relevant data structures accordingly within time. (The bottleneck is to update a possibly logarithmic number of values, for each neighbor of that has changed its level.) This may not be enough, however, as some neighbors of in the list may leave it prior to this scan of . Consequently, when any vertex leaves the list, we update the relevant data structures of all its neighbors currently in the list regarding . Since the list is of size and as we spend time per vertex for updating the data structures of regarding , the time needed for this part of the authentication process, and thus also for the entire process, is .

While any authentication process takes time , the simulation parameters and , which is the time reserved for any execution thread run by unmatch-schedule and the remaining schedulers following a single update operation, respectively, is much larger. This allows us to run an authentication process following any update operation and by any of the threads (when and where needed) instantly, i.e., without simulating its execution over subsequent update operations, while increasing the worst-case update time of the algorithm by a negligible factor. Recall that these threads run sequentially, i.e., when any execution thread runs, the other ones are idle. During this time, the running thread is the only one that may access and modify the list. Hence, the list remains intact during any authentication process, which is crucial for the validity of this process.

As mentioned, the outdated values stored in the data structures lead to *inconsistencies*, particularly regarding the levels of vertices.
These inconsistencies may lead to conflicts between the various schedulers, e.g., some level- vertex may choose
as its mate uniformly at random among all its neighbors of level lower than , while is in the process of rising to level and should thus not be chosen as a mate for .
Since all inconsistencies in the data structures concern active vertices and as there are just few of those,
we can detect those inconsistencies effectively as part of the authentication process. There is a difference, however, between *detecting* an inconsistency and *resolving* it,
as the latter may require a long process, which needs to be simulated over multiple update operations. During this time interval of resolving an inconsistency,
a conflict between schedulers may arise as a result of that inconsistency.
The authentication process is not aimed at resolving *all* potential conflicts between the various schedulers,
but should rather be viewed as a tool for detecting them and minimizing their number and variety.
In Section 5 we describe and analyze our mechanism for resolving all potential conflicts between the various schedulers,
which heavily relies on the authentication process. Although this mechanism is technically elaborate,
it does not overcome a major conceptual challenge, but rather a minor technicality;
indeed, all potential conflicts concern only vertices, namely, the active ones, and it is not too difficult to resolve conflicts
that concern vertices with a polylogarithmic update time.

## 3 The Update Algorithm, Part II: The Underlying Procedures

### 3.1 Procedure

Whenever the update algorithm examines a vertex , it may need to re-evaluate its level.
After the new level is determined, the algorithm calls
Procedure . (The exact way in which the new level of is determined is not part of this procedure; it is either determined by rise-schedule
as described in Section 2.2 or by the procedures described in Sections 3.2 and 3.3.)
Note that setting the level of to can be done instantly.
The task of Procedure is to update the relevant data structures as a result of this level change.
This process involves updating the sets of outgoing and incoming neighbors of and some of its neighbors (which can be viewed
as flipping the respective edges) so as to maintain Invariant 3 (or Invariant 1(d)), and also updating the values of and its relevant neighbors.
We refer to this process as the *falling* or *rising* of , depending on whether or , respectively.
(If , Procedure does not do anything.)
We remark that the rising/falling of vertex to level is a (possibly long) process that does not end until the corresponding call to finishes its execution,
and the level of is viewed as its destination level starting from the beginning of the rising/falling process.

Procedure , which carries out the falling/rising process of from level to level , is invoked by our update algorithm either by , in which case we have , or by Procedure handle-free that is described in Section 3.3. We argue that any call to is executed by a level- thread, where . This assertion is immediate if the call to set-level is due to , since may only run level- threads. If the call to set-level is due to Procedure handle-free, then the assertion follows from Corollary 3.8; see Section 3.3.2 for details.

Note that the thread that executes Procedure simulates multiple execution steps of this procedure following each update operation; the exact number of execution steps is either or , depending on the scheduler that runs this thread, but in any case the execution of this thread is simulated over multiple update operations. We next describe Procedure , disregarding the fact that it is being simulated over multiple update operations. In this description we also disregard the fact that some neighbors of may be active at the beginning of the procedure’s execution or become active throughout the execution. Then we address the technicalities that arise from these facts.

##### A high-level description.

Procedure starts by storing the old level of in some temporary variable and setting the new level of to , i.e., . (Thus the level of is set as its destination level from the beginning of the rising/faling process.) Then the procedure updates the outgoing neighbors of about ’s new level. Specifically, we scan the entire list , and for each vertex , we move from to .

Suppose that . In this case the level of is decreased by at least one, i.e., is falling. As a result, we need to update the values , for all ; this can be carried out by scanning the list , as all non-active neighbors of with level at most are in . Moreover, we need to update the values of the relevant neighbors of . Specifically, we scan the entire list , and for each vertex with , we increment by 1, for all . We also need to flip the outgoing edges of towards vertices of level between and to be incoming to . Specifically, we scan the list , and for each vertex such that , we perform the following operations: Delete from , add to , delete from , and add to .

If , the level of is increased by at least one, i.e., is rising. As a result, we need to flip ’s incoming edges from vertices of level between and to be outgoing of . Specifically, for each non-empty list , with , and for each vertex , we perform the following operations: Delete from , add to , delete from , and add to . Note that we do not know for which levels the corresponding list is non-empty; the time overhead needed to verify this information is . We also update the values of the relevant neighbors of . Specifically, we scan the updated list , and for each vertex with , we decrement by 1, for all .

The following observation is implied by Invariant 3 and the high-level description of this procedure.

###### Observation 3.1

Any non-active neighbor of scanned by Procedure has level at most . Moreover, at the beginning of the procedure’s execution, all non-active neighbors of with level less than are outgoing of and all non-active outgoing neighbors of at that time have level at most .

##### Zooming in.

Since the execution of this procedure is simulated over multiple update operations,
it is possible that some neighbors of are falling and/or rising throughout this time interval, possibly multiple times.
Consequently, we need to apply the authentication process described in Section 2.3.
Although this process was described in Section 2.3, we find it instructive to repeat the details of this process that are relevant to Procedure set-level, for concreteness.
We authenticate the values of ’s neighbors that the procedure scans using the list,
as well as scan the entire list at the end of this procedure, to make sure that no relevant neighbor is missed.
The level of an active vertex (as stored in the list) is viewed as its *destination* level (i.e., the level to which it falls/rises).
In addition, we update the vertices that belong to the list about the new level of , when is removed from that list (after the procedure’s execution terminates).
Recall that we do that not just for , but rather for every vertex that leaves the list, which guarantees that the data structures of any active vertex
are always updated regarding any neighbor that changes its level throughout the time interval during which is active.
In particular, Procedure may not be able to handle properly neighbors of that change their level throughout the procedure’s execution. This is why when any such neighbor leaves the list, we update the data structures of (the currently active) regarding ’s new level.

Throughout the execution of Procedure , may acquire new neighbors at levels at most ,
either due to neighbors falling to such levels or due to adversarial edge insertions.
Similarly, may lose neighbors at such levels, either due to neighbors rising to levels higher than or due to adversarial edge deletions.
As each adversarial edge update occurs, we make sure to update the data structures of the two endpoints in time.
If an edge is added/removed to/from the graph throughout the execution of this procedure, we update the relevant data structures according to the *destination* level of rather than the old one;
if is also in the process of rising/falling, we update the data structures according to the destination level of rather than the old one.
Focusing on the data structures of , we will store the new neighbors of in temporary data structures and throughout the execution
of Procedure , and merge them with the old data structures at the end of the execution.
In this way we can avoid scanning the new neighbors of throughout the procedure’s execution, which is useful
for bounding the total runtime of this procedure.
We deal with new/old falling/rising neighbors of just like we deal with new/old neighbors due to adversarial edge insertions/deletions.
In particular, a neighbor of that falls to level at most will be stored (when needed) in the aforementioned temporary data structures and ;
note that the relevant data structures of are not updated regarding as part of Procedure ,
but rather at the end of the execution of Procedure , as leaves the list.
In addition, at the end of the execution of Procedure , we need to update the data structures of regarding ’s new neighbors that appear in the list,
but this update is done as part of the authentication process, which does not distinguish between new and old neighbors of .
As mentioned in Section 2.3, the runtime of the authentication process is bounded by , and this bound holds independently of the number of neighbors that acquires (or loses) during the execution of Procedure .

The correctness of Procedure follows from the description above and Observation 3.1.

Recall that , where denotes the level of just before the execution of Procedure starts. We next analyze the total runtime of Procedure , denoted , for a vertex that falls or rises from level to level .

###### Lemma 3.2

Denote by the number of ’s neighbors of level lower than at the beginning of the execution of this procedure. Then .

Proof: First, recall that the authentication process takes time. Also, the time needed for updating the values in the case that falls to level , for all , is .

By Observation 3.1, any non-active neighbor of scanned by the procedure has level at most . We may restrict our attention to ’s neighbors of level at most , since any time spent by the procedure for handling active neighbors of of higher level is encapsulated within the authentication process. Note that only neighbors of at the beginning of the procedure’s execution may be active. For each neighbor of of level at most (active or not), the procedure spends at most time for updating the appropriate data structures , and at most time for updating the relevant values. This, however, does not imply that the procedure’s runtime is , as the set of neighbors of is not static, but rather changes dynamically throughout the procedure’s execution. In particular, the corresponding set (of ’s neighbors of level lower than ) is not static.

A vertex joins in one of two ways, the first is due to adversarial edge insertions. Whenever an edge adjacent to is added to the graph, the data structures are updated appropriately. This update of the data structures is not part of Procedure , but rather part of the procedure that handles the insertion of edge , namely Procedure handle-insertion, described in Section 3.2.

The second way for a vertex to join is by falling from level higher than to level at most . It is possible that changes its level throughout the execution of this procedure multiple times. For every such change in level except for maybe the last, the data structures of are updated as part of the respective calls to Procedure , and more concretely, each time is removed from the list throughout the execution of Procedure , the data structures of are updated accordingly. Those data structures may be outdated only if belongs to the list at the end of the execution of Procedure . For this reason we scan the entire list at that stage, spending time per active neighbor of for updating the relevant data structures. This update of the data structures is part of the authentication process, whose cost was already taken into account.

When a vertex leaves , the data structures of (either the old or the temporary ones) are updated accordingly. As with vertices that join , this update of the data structures is not part of Procedure . If leaves due to an edge deletion, this update is part of the procedure that handles the deletion of edge , namely Procedure handle-deletion, described in Section 3.2. The second way for a vertex to leave is by rising from level at most to level higher than , in which case the data structures of are updated either as part of the respective calls to Procedure or as part of the authentication process.

When a vertex joins or leaves , the temporary data structures of , namely or , are updated accordingly; if joins , these data structures are updated according to the new level of . Also, the data structures of each such neighbor are updated according to the destination level of . Hence there is no need for Procedure to update the temporary data structures of nor to update the respective data structures of vertices belonging to the temporary data structures of , and so the only cost due to vertices that join or leave incurred by this procedure is that of merging the old data structures of with the temporary ones. Merging with takes constant time, whereas merging with takes time, since we merge here two hash tables (or we may create a new one instead), and moreover, we need to merge the respective lists and one by one, where may range from 1 to . Thus the extra cost due to vertices that join or leave is .

Summarizing, we have shown that .

The following observation is implied by the description of this procedure.

###### Observation 3.3

At the end of the execution of Procedure , Invariant 3 holds with respect to all edges adjacent to that lead to non-active neighbors of .

### 3.2 Procedures and

Following an edge insertion , we apply Procedure . Besides updating the relevant data structures in the obvious way within time, this procedure matches between and if they are both at level -1, otherwise it leaves them unchanged. By Invariant 2, any vertex at level -1 is unmatched. Note that unmatched vertices whose level exceed -1, namely temporarily free vertices, are not matched by this procedure. Matching and involves setting their level to 0 by making the calls to and ; this guarantees that Invariants 1(a) and 1(b) will continue to hold. Note also that no matter how the new edge is oriented, Invariant 3 will continue to hold. The other invariants also continue to hold. Invariant 5 implies that both and are bounded by , hence the runtime of these calls is at most by Lemma 3.2. As the runtime of this procedure is , we can complete its entire execution well within the time reserved for a single update operation, namely, the worst-case update time of the algorithm, .

Following an edge deletion , we apply Procedure . If edge does not belong to the matching, we only need to update the relevant data structures. In this case the runtime of this procedure will be , and we can complete its execution well before the next update step starts. If edge is matched, both and become temporarily free, and they are inserted to the appropriate queue ; recall that by Invariant 1(b). Note that all invariants continue to hold. The sub-scheduler makes sure to handle and (by making the calls to and , as described in Section 2.2), one after another, after handling all the preceding vertices in the queue. Note that until each of them is handled, its level will exceed and its out-degree may be positive.

### 3.3 Procedure

This procedure handles a temporarily free vertex , and is first invoked by the various schedulers as described in Section 2.2, but then also recursively. We first provide a high-level overview of this procedure, and later zoom in on the parts that require further attention.

##### A high-level description.

The procedure starts by computing the highest level , where , as well as the corresponding vertex set of in order to randomly sample a neighbor of level lower than as the new mate of . The sampling is not done from the entire set , but rather from its subset of all non-active vertices in . Specifically, if , we sample from arbitrary vertices of ; otherwise, we sample from the entire set . This sampling is done only for levels satisfying , which are the levels for which Invariant 4 is maintained. For such levels we have , and since and the number of active vertices is , it follows that only an -fraction of the vertices of may be active. Appropriate scaling thus yields , so we sample from between and vertices, as required.

The complementary regime of levels, i.e., levels satisfying , is trivial, as the simulation time is either or (depending on the thread executing this procedure), each of which is smaller than 1. In this case we do not rely on Invariant 4, and a naive deterministic treatment suffices. (See Section 3.3.1 for details.)

In order to match with , we first delete the old matched edge on (if exists), thus rendering temporarily free. Second, we let and fall and rise to the same level , respectively, by calling to and . (Note that the random sampling of was intentionally done prior to making these calls to set-level.) We then match with , thus creating a new level- matched edge, which satisfies the validity of Invariant 1(b). Note that Invariants 1(a) and 3 also continue to hold. Finally, assuming was previously matched to , we handle recursively by calling to .

In the degenerate case that no level as above exists, we have , i.e., does not have any neighbor at level -1. In this case remains free, and we make the call to . Note that Invariants 1(c) and 2 continue to hold.

In general, it is easy to verify that this procedure does not invalidate any invariant, disregarding Invariants 4 and 5, whose validity is proved in Section 4 as part of the analysis of the schedulers.

By the description of the update algorithm, this procedure is executed by a level- thread, where is ’s level at the beginning of the procedure’s execution. The same thread is used also for the recursive call , and for all subsequent recursive calls. While the level of the thread executing this procedure matches the level of , the vertex at the top recursion level, we show in Section 3.3.2 that it exceeds the levels of vertices handled by subsequent recursive calls. This thread runs in an overall time of , simulating or execution steps following each update operation (depending on the sub-scheduler running it).

##### Zooming in.

Recall that none of the values and vertex sets , for , is maintained by the update algorithm, and computing them is a process that is simulated over multiple update operations. By the time we finish this process, some of the scanned neighbors of may have different levels than those they had at the time they were scanned. Also, may acquire new neighbors of level at most and lose others. (Recall that we had similar problems with Procedure set-level.) By Invariant 3, when the execution of this procedure starts (i.e., just after becomes temporarily free), every non-active neighbor of at level lower than is an outgoing neighbor of . We thus restrict our attention to the outgoing neighbors of in order to compute “estimations” for the values and vertex sets , for all . Once we finish computing these estimations, which might be simulated over multiple update operations, we make these estimations accurate by running the authentication process described in Section 2.3. Recall that the authentication process also considers vertices that belong to the list at that stage. It enables us to update the values and vertex sets in time, for , and so this update can be carried out within the time reserved for a single update operation, namely, either or depending on the thread executing the procedure, during which the list remains intact. We then compute in time the highest level , where . Moreover, the same amount of time suffices for computing the vertex set . Indeed, this set can be obtained by pruning the active vertices from the previously computed set , which can be carried out within time linear in the size of the list if we keep mutual pointers between the elements of and . Following similar lines to those in the proof of Lemma 3.2, the runtime of this process is , where denotes the number of ’s neighbors of level lower than at the beginning of the execution of this procedure. By Invariant 5, at any time , hence the runtime of this process is .

For every vertex that joins or leaves throughout the procedure’s execution, for , we make sure to update the values and vertex sets accordingly. However, all such updates are not done as part of Procedure , but rather as part of the respective calls to Procedures or in the case of adversarial edge insertions and deletions, respectively, or as part of the respective calls to Procedure .

Note that setting the level of to by calling to is another long process, which involves updating the relevant data structures,
and is thus simulated over multiple update operations.
During this process, the vertex set that we have just computed may change.
For this reason,
we randomly sample a vertex from as the new mate of *before* initiating this process.
By doing this, we sample from at least vertices,
whereas if we were to make the random sampling after this process is finished, the sample space could a-priori be much smaller than , which would, in turn, invalidate our almost-maximality guarantee.
(Although it is not difficult to prove that the sample space does not reduce significantly during the call to , there is no need for it.)
Since does not contain any active vertices,
is not active when it is chosen as a random mate for ,
and in particular, it is not in the process of falling or rising; we then add to the list.
It is crucial that the new mate of will be at level at most . We argue that the random sampling of can be carried out within the time or reserved for a single update operation.
(Although it is not difficult to prove that the sample space does not reduce significantly during the time it takes
to randomly sample *naively*, i.e., via a procedure whose runtime is linear in the sample space, there is no need for it.)
To this end, the set that we have just computed should be stored via a data structure that allows for fast retrieval of a random element,
such as a balanced (of logarithmic depth) tree in which every node is uniquely associated with a vertex of via mutual pointers,
and it also holds a counter for the number of nodes in its subtree.
Sampling a random integer from takes at most time.
Since the depth of this tree is , it is straightforward to retrieve the th element in the tree in another