Space and time trade-off for the k shortest simple paths problem

The k shortest simple path problem (KSSP) asks to compute a set of top-k shortest simple paths from a vertex s to a vertex t in a digraph. Yen (1971) proposed the ﬁrst algorithm with the best known theoretical complexity of O ( kn ( m + n log n )) for a digraph with n vertices and m arcs. Since then, the problem has been widely studied from an algorithm engineering perspective, and impressive improvements have been achieved. In particular, Kurz and Mutzel (2016) proposed a sidetracks-based (SB) algorithm which is currently the fastest solution. In this work, we propose two improvements of this algo-rithm. We ﬁrst show how to speed up the SB algorithm using dynamic updates of shortest path trees. We did experiments on some road networks of the 9th DIMAC’S challenge with up to about half a million nodes and one million arcs. Our computational results show an average speed up by a factor of 1.5 to 2 with a similar working memory consumption as SB. We then propose a second algorithm enabling to signiﬁcantly reduce the working memory at the cost of an increase of the running time (up to two times slower). Our experiments on the same data set show, on average, a reduction by a factor of 1.5 to 2 of the working memory.


Introduction
The classical k shortest paths problem (kSP) returns the top-k shortest paths between a pair of source and destination nodes in a graph. This problem has numerous applications in various kinds of networks (road and transportation networks, communications networks, social networks, etc.) and is also used as a building block for solving optimization problems. Let D = (V, A) be a digraph, an s-t path is a sequence (s = v 0 , v 1 , · · · , v l = t) of vertices starting with s and ending with t, such that (v i , v i+1 ) ∈ A for all 1 ≤ i < l. It is called simple if it has no repeated vertices, i.e., v i = v j for all 0 ≤ i < j ≤ l. The length of a path is the sum of the weights of its arcs and the top-k shortest paths is therefore the set containing a shortest s-t path, a second shortest s-t paths, etc. until the k th shortest s-t path. Several algorithms for solving kSP have been proposed. In particular, Eppstein [5] proposed an exact algorithm that computes k shortest paths (not necessarily simple) with time complexity in O(m + n log n + k), where m is the number of arcs and n the number of vertices of the graph. An important variant of this problem is the k shortest simple paths problem (kSSP) introduced in 1963 by Clarke et al. [3] which adds the constraint that reported paths must be simple. This variant of the problem has various applications in transportation network when paths with repeated vertices are not desired by the user. It is also a subproblem of other important problems like constrained shortest path problem, vehicle and transportation routing [10,12,19]. It can be applied successfully in bio-informatics [1], especially in biological sequence alignment [17] and in natural language processing [2]. For more applications, see Eppstein's recent comprehensive survey on k-best enumeration [6].
The algorithm with the best known time complexity for solving the kSSP problem has been proposed by Yen [20], with time complexity in O(kn(m + n log n)). Since, several works have been proposed to improve the efficiency of the algorithm in practice [10,14,11,7,16].
Recently, Kurz and Mutzel [16,15] obtained a tremendous running time improvement, designing an algorithm with the same flavor as Eppstein's algorithm. The key idea was to postpone as much as possible the computation of shortest path trees. To do so, they define a path using a sequence of shortest path trees and deviations. With this new algorithm, they were able to compute hundreds of paths in graphs with million nodes in about one second, while previous algorithms required an order of tens of seconds on the same instances. For instance, Kurz and Mutzel's algorithm computed k = 300 hundred shortest paths in 1.15 second for the COL network [4] while it required about 80 seconds for the Yen's algorithm and about 30 seconds by its improvement by Feng [7].
Our contribution. We propose two variations of the algorithm proposed by Kurz and Mutzel. We first show how to speed up their algorithm using dynamic updates of shortest path trees resulting in an average speed up by a factor of 1.5 to 2 and with a similar working memory consumption (i.e., the total memory consumption excluding the memory allocated for the input and the output). We then propose a second algorithm enabling a significant reduction of the working memory at the cost of a small increase of the running time.
This paper is organized as follows. First, in Section 2.2, we describe Yen's algorithm and then show in Section 2.3, how Kurz and Mutzel's algorithm improves upon it. In Section 3, we present our algorithms by precisely describing how they differ from Kurz and Mutzel's algorithm. Finally, Section 4 presents our simulation settings and results.

Definition and Notation
Let D = (V, A) be a directed graph (digraph for short) with vertex set V and arc set A. Let n = |V | be the number of vertices and m = |A| be the number of arcs of D. Given a vertex v ∈ V , N + (v) = {w ∈ V | vw ∈ A} denotes the out-neighbors of v in D. Let w D : A → R + be a length function over the arcs. For every u, v ∈ V , a (directed) path from 18:3 Note that vertices may be repeated, i.e., paths are not necessarily simple. A path is simple if, moreover, v i = v j for all 0 ≤ i < j ≤ r. The length of the path P equals Given two paths (v 1 , · · · , v r ) and Q = (w 1 , · · · , w p ) and v r w 1 ∈ A, let us denote the v 1 -w p path obtained by the concatenation of the two paths by (v 1 , · · · , v r , Q). Given s, t ∈ V , a top-k set of shortest s-t paths is any set S of (pairwise distinct) simple s-t paths such that |S| = k and w(P ) ≤ w(P ) for every s-t path P ∈ S and s-t path P / ∈ S. The k shortest simple paths problem takes as input a weighted digraph D = (V, A), w D : A → R + and a pair of vertices (s, t) ∈ V 2 and asks to find a top-k set of shortest s-t paths (if they exist).
Let t ∈ V . An in-branching T rooted at t is any sub-digraph of D that induces a tree containing t, such that every u ∈ V (T ) \ {t} has exactly one out-neighbor (that is, all paths go toward t). An in-branching T is called a shortest path (SP) in-branching rooted at t if, for every u ∈ V (T ), the length of the (unique) u-t path P T ut in T equals d D (u, t). Note that an SP in-branching is sometimes called reversed shortest path tree.
In the forthcoming algorithms, the following procedure will often be used (and the key point when designing the algorithms is to limit the number of such calls and to optimize each of them). Given a sub-digraph H of D and u, t ∈ V (H), we use Dijkstra's algorithm for computing an SP in-branching rooted in t that contains a shortest u-t path in H. Note that, the execution of the Dijkstra's algorithm may be stopped as soon as a shortest u-t path has been computed (when u is reached), i.e., the in-branching may only be partial (i.e., not spanning D). The key point will be that this way to proceed not only returns a shortest u-t path in H (if any) but an SP in-branching rooted in t, containing u. Note that any such call has worst-case time complexity O(m + n log n).
Let P = (v 0 , v 1 , · · · , v r ) be any path in D and i < r.
is called an extension of P at a (or at v i ). Note that neither P nor Q is required to be simple. However, if Q is simple, it will be called a simple extension of P at a (or at v i ). In addition, Q is called a shortest (simple) extension at v i if and only if Q is an extension with minimum length among all (simple) extensions of P at v i . Furthermore, Q is called a shortest (simple) extension at a if and only if Q is an extension with minimum length among all (simple) extensions of P at a.

General framework: Yen's algorithm and Feng's improvements
We start by describing the general framework used by the kSSP algorithms in [10,14,11,7,16]. Precisely, let us describe Yen's algorithm [20] trying to give its main properties and drawbacks. Then, we explain how Kurz and Mutzel's algorithm improves upon it (Section 2.3). Finally, we will detail our adaptation of the latter method in Section 3.
All of the algorithms described below start by computing a shortest s-t path P 0 = (s = v 0 , v 1 , · · · , v r = t) (in what follows we always assume that there is at least one such path). This is done by applying Dijkstra's algorithm from t (as described in previous section), so also computes an SP in-branching T 0 rooted at t and containing s. Note that P 0 is simple since weights are non-negative. Obviously, a second shortest s-t simple path must be a shortest simple extension of P 0 at one of its vertices. Yen's algorithm computes a shortest simple extension of P 0 at v i for every vertex v i in P 0 as follows. For every 0 ≤ i < r, let D i (P 0 ) be the graph obtained from D by removing the vertices v 0 , · · · , v i−1 (this is to avoid non simple extension) and the arc v i v i+1 (to ensure that the computed path is a new one, i.e., different from P 0 ). For every 0 ≤ i < r, an SP in-branching in D i (P 0 ) rooted at t is computed using Dijkstra's algorithm until it reaches v i and therefore returns a shortest path Q i from v i to t. Note that Yen's algorithm no longer uses the SP in-branchings and this will be one key improvement described further. For every 0 ≤ i < r, the extension (v 0 , · · · , v i , Q i ) of P 0 at v i is added to a set Candidate (initially empty). Note that the index i (called below deviation-index) where the path (v 0 , · · · , v i , Q i ) deviates from P 0 is kept explicit 1 . Once (v 0 , · · · , v i , Q i ) has been added to Candidate for all 0 ≤ i < r, by remark above, a shortest path in Candidate is a second shortest s-t simple path.
More generally, by induction on 0 < k < k, let us assume that a top-k set S of shortest s-t paths has been computed and the set Candidate contains a set of simple s-t paths such that there exists a shortest path Q ∈ Candidate such that S ∪ {Q} is a top-(k + 1) set of shortest s-t paths. Yen's algorithm pursues as follows. Let Q = (v 0 = s, · · · , v r = t) be any shortest path in Candidate 2 and let 0 ≤ j < r be its deviation-index. First, Q is extracted from Candidate and it is added to S (as the (k + 1) th shortest s-t path). Then, every shortest extension of Q is added to Candidate (since they are potentially a next shortest s-t path). For this purpose, for every j ≤ i < r, let D i (Q) be the digraph obtained from D by first removing the vertices v 0 , · · · , v i−1 (this is to avoid non simple extension). Then, here is one important bottleneck of Yen's algorithm, for every arc v i v such that Candidate already contains some path with prefix This therefore ensures to compute only new paths. Indeed, the computed extensions are distinct from every path previously computed as they have different prefixes (this is the reason to keep explicitly the deviation-index). For every j ≤ i < r, an SP in-branching rooted at t is computed using Dijkstra's algorithm until it reaches v i and therefore returns a shortest path This process is repeated until k paths have been found (when k = k).
Therefore, for each path Q that is extracted from Candidate, O(|V (Q)|) applications of Dijkstra's algorithm are done. This gives an overall time-complexity of O(kn(m + n log n)) which is the best theoretical (worst-case) time-complexity currently known (and of all algorithms described in this paper) to solve the k-shortest simple paths problem.
One expensive part in the pre-described framework of Yen's algorithm are the multiple calls of Dijkstra's algorithm. Feng [7] proposed a practical improvement by trying to avoid some calls. Essentially, when a path Q = (v 0 , · · · , v r ) with deviation-index j is extracted, its extensions are computed from i = j to r − 1. Roughly, for every j < i ≤ r, the computation of the extension at v i is actually done with the help of the initial SP in-branching T 0 .
In practice, this process significantly accelerates the executions of Dijkstra's algorithm. At the price of a larger memory consumption, Kurz and Mutzel improved Yen's framework which leads to the fastest algorithm currently known (Section 2.3) for the k shortest simple paths problem.

Kurz and Mutzel's algorithm
All of Yen's improvements aim at minimizing the time consumed by Dijktra's algorithm calls. Instead, Kurz and Mutzel [16] chose to use a smaller number of such calls. This can be done by memorizing the SP in-branchings previously computed by the algorithm. More precisely, instead of keeping the paths in the set Candidate, the algorithm keeps only a representation of it using a sequence of SP in-branchings and deviations. These representations will allow to extract any shortest path P in time O(|P |) and the length of P in constant time. As a result, for each shortest s-t path P , the memorized SP in-branching can be used to extract a shortest extension of P at a vertex v i in a pivot step. Unfortunately, there is no guarantee that the extracted shortest extension will be simple. If it is not simple, a new Dijsktra's algorithm call has to be done. However, in many cases the extracted extension is simple and a Dijsktra's algorithm call can be avoided.
Precisely, Kurz and Mutzel's algorithm mainly relies on two key improvements. First, following a principle of Eppstein's algorithm [5], it explicitly keeps the SP in-branchings computed during the execution of the algorithm (this is achieved at some non-negligible cost of working memory consumption, but leads to an improvement of the practical running-time). Moreover, instead of computing the extensions of the extracted path in each iteration, the algorithm adds to Candidate a representation of each extension (together with a lower bound of its length). Then, only when such a representation is extracted from Candidate, the corresponding extension is explicitly computed. This way of postponing the computations allows to avoid the actual computation of many extensions (which are not used any further), which leads to a drastic improvement of the running time.
Let us describe the Kurz and Mutzel's algorithm whose pseudo code is given in Algorithm 1. As usual, the algorithm starts with the computation of a shortest s-t (simple) path P 0 = (v 0 = s, v 1 , · · · , v r = t) together with an SP in-branching tree T 0 rooted at t and containing s. T 0 is added to a set T initially empty (this set T will contain all computed SP inbranchings). Then, for every 0 ≤ i < r, and for every deviation e at v i (i.e., arcs ) is a lower bound on the length of any shortest simple extension of P 0 at e (and it is its actual length if the path Q(i, e) is simple). The algorithm proceeds as follows. First, by categorizing the vertices of T 0 (using a trick due to Feng [7] that we do not detail here), it is possible to decide in constant time, for each i < r and each deviation e at v i , whether Q(e, i) is simple or not. Then, for every 0 ≤ i < r, and for every deviation e at v i , the algorithm adds ((T 0 , e, T 0 ), lb(e)) in a heap (ordered using lb) Candidate simple if Q(e, i) is simple, and it adds ((T 0 , e, T i ), lb(e)) in a heap Candidate not−simple otherwise, where T i is the name of a new SP in-branching rooted at t in D \ {v 0 , · · · , v i } whose actual computation is postponed. Hence, T 0 is the only SP in-branching that has been computed (using Dijkstra's algorithm) so far.
More generally, by induction on 0 < k < k, let us assume that a top-k set S of shortest s-t paths and two heaps Candidate simple and Candidate not−simple have been computed. Following Eppstein's idea, each element of these heaps is of the form ((T 0 , e 0 , · · · , T h , e h , T h+1 ), lb) (describing a path as explained below) such that, for every 0 ≤ i ≤ h, T i is an SP in-branching that has already been computed and stored in T , while (only if the element comes from Candidate not−simple ) T h+1 may not have already been computed but has a pointer associated to it stored in T . That is, even if T h+1 has not yet been computed, it is defined and can be referred to. Observe that we may have T j = T j+1 for some 0 ≤ j ≤ h + 1.
Require: A digraph D = (V, A), a source s ∈ V , a sink t ∈ V , and an integer k Ensure: k shortest simple s-t paths 1: Let Candidate simple ← ∅, Candidate not−simple ← ∅, T ← ∅ and Output ← ∅ 2: T 0 ← an SP in-branching of D rooted at t containing s 3: Add ((T 0 ), w(P st (T 0 ))) to Candidate simple 4: while Candidate simple ∪ Candidate not−simple = ∅ and |Output| < k do 5: if ext represents a simple path then 12: Add (ext, lb ) to Candidate simple 13: else 14: T ← the name of an SP in-branching of D h (P ) // T is not computed yet 15: Add T to T

21:
Add ε to Candidate simple

22: return Output
Let P 1 be the simple path that starts in s and, for every 0 ≤ j ≤ h, follows the (already computed) tree T j from the current vertex till the tail of the deviation e j and then follows deviation e j to its head. Hence, P 1 ends in the head z of e h . Now, if the element is in Candidate simple , we know by induction that the SP in-branching T h+1 has already been computed and that the path P obtained by concatenating P 1 and the shortest z-t path P zt (T h+1 ) is guaranteed to be simple and has length w(P ) = w(P 1 ) + w(P zt (T h+1 )) = lb(e h ). If the element is in Candidate not−simple (the shortest z-t path P zt (T h ) intersects P 1 ) the algorithm actually computes the SP in-branching T h+1 rooted at t (if not already done). Observe that the digraph in which T h+1 is computed is a subdigraph of the digraph in which T h has been computed. Furthermore, w(P zt (T h+1 )) ≥ w(P zt (T h )) (by setting w(P zt (T h+1 )) = +∞ if there is no z-t path in T h+1 ) and z is the only common vertex of P 1 and P zt (T h+1 ). Hence, the path P obtained by concatenating P 1 and the shortest z-t path P zt (T h+1 ) (if it exists) is guaranteed to be simple and has length w(P ) = w(P 1 ) + w(P zt (T h+1 )) ≥ w(P 1 ) + w(P zt (T h )) = lb(e h ).
An iteration of Kurz and Mutzel's algorithm proceeds as follows. First an element ε = ((T 0 , e 0 , T 1 , e 1 , · · · , T h , e h , T h+1 ), lb) with smallest lb is extracted from Candidate simple and Candidate not−simple (with priority for Candidate simple in case of equal lb). If ε was in Candidate simple , the path P as defined above is the next shortest simple s-t path and it is added to the output. Then, all possible deviations of P along the path P zt (T h+1 ) are determined and added to Candidate simple or Candidate not−simple depending on whether they are simple or not (note that only a representation of them is build and not the actual path).
Otherwise, the algorithm actually computes the SP in-branching T h+1 (if not already done) to determine the shortest z-t path in T h+1 (if it exists), and adds T h+1 to T . If such path exists, the algorithm adds to Candidate simple a new element ((T 0 , e 0 , T 1 , e 1 , · · · , T h , e h , T h+1 ), lb ) describing a simple s-t path with length lb = w(P 1 ) + w(P zt (T h+1 )) = lb − w(P zt (T h )) + w(P zt (T h+1 )).
Actually, the same SP in-branching can be used for all deviations at the same vertex v i of a given path P . So, for each vertex v i in P , a single call of Dijkstra's algorithm is needed. As a result, finding all of the extensions of a given path P can be done in O(|P |(m + n log n)) time. Therefore, the time complexity of Kurz and Mutzel's algorithm (in the worst case) is bounded by O(kn(m + n log n)) as the algorithm extends no more than k paths and the number of vertices of each path is bounded by n.
Overall, Kurz and Mutzel's algorithm computes k shortest simple s-t paths with a much lower number of applications of Dijkstra's algorithm and so its running time is in general much better than the algorithms proposed by Yen or Feng. On the other hand, it requires to store many SP in-branchings previously computed which implies a larger working memory consumption.

Our contributions
We propose two independent variants of the SB algorithm (Algorithm 1). The first one, called SB*, gives, with respect to our experimental results, an average speed up by a factor of 1.5 to 2 compared with SB algorithm. The second one, called PSB (Parsimonious SB), is based on a different manner to handle non simple candidates in order to reduce the number of computed and stored SP in-branchings. This leads to a significant reduction of the working memory at the price of a slight increase in running time compared with SB algorithm.

The SB* algorithm
Here, we propose a variant of the SB algorithm, strongly based on Kurz and Mutzel's framework, that is a tiny modification of SB algorithm allowing to speed it up. More precisely, each time a representation (T 0 , e 0 , T 1 · · · , e h−1 = (u h−1 , v h−1 ), T h , e h = (u h , v h ), T h+1 ) is extracted from Candidate not−simple and that T h+1 has not been computed yet (i.e., it is only a pointer), our algorithm does not compute T h+1 from scratch as SB algorithm does. Instead, the SB* algorithm creates a copy T of T h , discards vertices of the path from v h−1 to u h in T h , and updates the SP in-branching T using standard methods for updating a shortest path tree [9]. Then, the pointer T h+1 is associated with the new in-branching T .
It is clear that the SB* algorithm computes (and stores) exactly the same number of in-branchings as the SB algorithm. The computational results presented in Section 4.2 show that this update procedure gives an average speed up by a factor of 1.5 to 2.

The PSB algorithm
Our main contribution is the Parsimonious SB algorithm (PSB) presented in this section whose main goal is to solve the k shortest simple paths problem with a good tradeoff between the running time and the working memory consumption. Indeed, a weak point of the SB algorithm comes from the fact that it keeps all the SP in-branchings that it computes throughout its execution in the memory . In order to reduce the working memory consumption, the main difference between the SB algorithm and the one presented here consists of the types of the elements that the PSB algorithm stores in the heap Candidate not−simple and the way they are used. We now describe the PSB algorithm by detailing how its differs from the SB algorithm. Let us mention that the PSB algorithm uses a heap Candidate simple similar (i.e., containing exactly the same type of elements) to the one used by SB algorithm.
Let us start considering a step of PSB algorithm when an element ε = ((T 0 , e 0 , T 1 , e 1 , · · · , T h , e h = (u h , v h ), T h+1 ), lb) is extracted from Candidate simple . The first difference between the SB algorithm is that T h+1 may have not yet been computed, in which case it must be computed at that step and stored in T . Next, as the SB algorithm, the PSB algorithm first adds the (simple) path P corresponding to ε to the output. Then, it considers all deviations of P at the vertices between v h and t, i.e., all arcs (not in P ) with tail in P v h t (T h+1 ). For every such deviation e = uv with u ∈ V (P v h t (T h+1 )), by using Feng's "trick" (already mentioned without details), it can be decided in constant time whether it admits a simple extension, i.e., whether the path P e that "follows" the path P 1 corresponding to ε from s to v h , then follows the path P v h u (T h+1 ), the arc e and finally the path P vt (T h+1 ) is simple or not. In the case when P e is simple, then the element ((T 0 , e 0 , T 1 , e 1 , · · · , T h , e h , T h+1 , e, T h+1 ), lb e ) is added to Candidate simple , where lb e = w(P 1 ) + w(P v h u (T h+1 )) + w(e) + w(P vt (T h+1 )) (exactly as it is done by the SB algorithm). The second difference with the SB algorithm relies on the set X = {f 1 , · · · , f r } of deviations for which the extension using T h+1 is not simple. The key point is that we create a single element for all deviations in X. This ensures that the size of Candidate not−simple is at most k, as at most one element is added to Candidate not−simple per path added to the output. More precisely, let X = {f 1 , · · · , f r } be the set of "non simple" deviations ordered in such a way that, for every 1 ≤ i < j < l ≤ r, the tail of f j is between (or equal to) the tails of f i and f l on the path P v h t (T h+1 ). For every 1 ≤ i ≤ r t (T h+1 )). The PSB algorithm then adds the element ε = ((T 0 , e 0 , T 1 , e 1 , · · · , T h , e h , T h+1 , X, T h+1 ), min 1≤i≤r lb i ) to Candidate not−simple , and so the weight of ε in the heap Candidate not−simple is the smallest lower bound among all lower bounds related to the "non simple" deviations in X.
Let us now consider a step of the PSB algorithm when an element is extracted from Candidate not−simple . This happens, as in the SB algorithm, when the smallest key (lower bound) of the elements in Candidate simple ∪ Candidate not−simple corresponds only to an element of Candidate not−simple . Let ε = ((T 0 , e 0 , T 1 , e 1 , · · · , T h , e h , T h+1 , X, T h+1 ), lb) be this element and let X = {f 1 , · · · , f r }. Let also e h = u h v h , let P 1 = (s = x 1 , · · · , x o = v h ) be the prefix (from s to v h ) of the path represented by ε, let P v h t (T h+1 ) = (v h , v h+1 , · · · , v p = t) and let f j = v ij v j for every 1 ≤ j ≤ r (by the way the f j 's are ordered, h ≤ i j ≤ i j ≤ p for all 1 ≤ j ≤ j ≤ r). The fact that the type of the elements in Candidate not−simple is more involved (so, allowing to decrease a lot the working memory) requires an advanced way to treat them (and potentially more costly in term of running time). To limit the increase of the running time, the PSB algorithm proceeds in such a way that several deviations in {f 1 , · · · , f r } are somehow considered "simultaneously". More precisely, it proceeds as follows.
Let 1 ≤ i * ≤ r be the smallest integer such that lb i * = lb. The PSB algorithm proceeds as follows to deal with the deviations f r , f r−1 , · · · , f i * in this order. First, it applies Dijkstra's algorithm to compute an SP in-branching T r rooted at t in is a simple s-t path and the element ((T 0 , e 0 , T 1 , e 1 , · · · , T h , e h , T h+1 , f r , T r ), w(Q r )) is added to Candidate simple . However, the in-branching T r is not saved into T but only its name is kept (this allows to reduce the working memory size while it may require to recompute the tree T r later. The bet here is that it will not be necessary to actually redo this computation). Then, for j = r − 1 down to i * , the SP in-branching T r is updated to become the SP in-branching T j rooted in t in D j = D − {x 1 , · · · , x o = v h , · · · , v ij }, possibly reaching v j and so providing a simple path Q j . To speed up the computation of T j , it is actually computed by updating T j+1 which is done using standard methods for updating a shortest path tree [9]. Finally, the element ((T 0 , e 0 , T 1 , e 1 , · · · , T h , e h , T h+1 , f j , T j ), w(Q j )) is added to Candidate simple . In the current implementation of our PSB algorithm (the one used in the experiments described in next section), the in-branching T j is saved in T only if j = i * 3 . Finally, the element ((T 0 , e 0 , T 1 , e 1 , · · · , T h , e h , T h+1 , X , T h+1 ), min 1≤j<i * lb j ), with The correctness of the PSB algorithm follows from the one of the SB algorithm by noticing that the elements extracted from Candidate simple ∪ Candidate not−simple are the ones with smallest lower bound and the fact that, each time that a path is extracted, a shortest extension of each of its deviations is considered.
Finally, as already mentioned, the number of elements in the heap Candidate not−simple is bounded by k as each of its elements correspond to a path that has been added to the output, while with the SB algorithm, this heap may contain O(km) elements. Furthermore, as for the SB algorithm, we may keep only the k elements with smallest lower bound in Candidate simple . Hence, the working memory used by the PSB algorithm for the heaps is significantly smaller than for the SB algorithm. However, the largest part of the working memory is due to the number of SP in-branchings that are computed and stored in T . Although this number seems difficult to evaluate, we observe experimentally (see Section 4) that it is significantly smaller with the PSB algorithm.

4
Experimental evaluation
Following [16], we have implemented a pairing heap data structure [8] supporting the decrease key operation, and we use it for the Dijkstra shortest path algorithm. Our implementation of the Dijkstra shortest path algorithm is lazy, that is it stops computation as soon as the distance from query node v to t is proved to be the shortest one. Further computations might be performed later for another node v at larger distance from t. Our implementation of Dijkstra's algorithm supports an update operation when a node or an arc is added to the graph. In addition, we have implemented a special copy operation that enables to update the in-branching when a set of nodes is removed from the graph. This corresponds to the operations performed when creating an in-branching T h+1 from T h . Observe that in our implementations of NC, SB, SB* and PSB, the parameter k is not part of the input, and so the sets of candidates are simply implemented using pairing heaps. This choice enables to use these methods as iterators able to return the next shortest path as long as one exists (Note that if k is part of the input, the data structure used to store candidates could be changed in order to contain only the k best candidates, but the algorithm would only return exactly k paths even if more exist). We have evaluated the performances of our algorithms on some road networks from the 9th DIMACS implementation challenge [4]. The characteristics of these graphs are reported in Table 1. In the following, we refer to the graphs ROME, 3 A way to establish an even better space versus time tradeoff would be to determine a good threshold τ such that an in-branching T j is stored in T if and only if w(Q j ) ≤ τ . Due to lack of time we have not been able to establish such a parameter τ but it will be one of the objectives of our future works. 4 Despite several queries, we have not been granted access to the code used for experiments in [7,16]. DC and DE as the small networks, and to the graphs NY, BAY and COL as the large networks. We also generated random networks using method RandomGNM of SageMath [18] with n ∈ {5000, 10000, 20000} and for each n, we let m = 10n, 50n and 100n. For each network (both the random and the road networks), we have measured the execution time and the number of stored SP in-branchings. Note that the number of stored in-branchings gives a rigorous indication of the memory consumption (in particular, this is independent of the implementation) [13]. For each network we run the algorithms on a thousand pairs of vertices randomly chosen. We report in Tables 2 and 7 the average and the median of their running times and number of stored SP in-branchings. All reported computations have been performed on computers equipped with 2 quad-core 3.20GHz Intel Xeon W5580 processors and 64GB of RAM.

Experimental results
The simulations show that our tiny improvement SB* of SB algorithm allows to decrease the running-time by a factor between 1,5 (on NY) and 2 (on DE) on median (Tables 2 and 3). The fact that in each network a few queries are extremely slow makes the median a better indicator than the average. In particular, in all the networks considered, SB* algorithm is, for most of the queries, faster than SB algorithm (Figures 1a and 2a). By design, the number of stored in-branchings is the same in both algorithms. It is interesting to note that the gain increase with the size of the networks. It seems that the differences of performances depends on the structure of the queries and of the networks. In the future work, we plan to investigate the relationship between the kinds of queries and/or networks and the gain in running time.
The simulations comparing PSB algorithm and SB algorithm show a significant reduction of the working memory when using PSB (Tables 4 and 5 and Figures 1c and 2c). Again, the gain increases when considering larger networks. In term of running time, SB algorithm is slightly faster on average but Figures 1b and 2b indicate that globally, they are quite comparable. It would be interesting to understand the impact of the length of the queries on the performances of both algorithms.
Finally, following some simulations in [16], we have also compared all the algorithms on random graphs (Edös-Rényi). Due to lack of time, we considered only one graph per setting (number of vertices, of edges and k) and the average is done on 1000 requests (note that this setting is similar to the one in [16]). The performances (Table 6) are similar than in the ones obtained for road networks. That is, the SB* algorithm is faster than all other algorithms (more than twice as fast in the case of large graphs when k = 1000). Surprisingly, the NC algorithm is sometimes (for dense graphs) faster than SB and PSB. Moreover, the PSB algorithm always uses less memory than SB algorithm (Table 7), with a more significant difference in the case of sparse large graphs.
In the future work, we will continue our experiments in order to discover which conditions (structure of graphs and queries...) make one of the prescribed algorithms faster or / and less memory consuming than the others.