Template:Short description Template:About {{safesubst:#invoke:Unsubst||date=__DATE__|$B= Template:Ambox }}
In computer science, cycle detection or cycle finding is the algorithmic problem of finding a cycle in a sequence of iterated function values.
For any function Template:Mvar that maps a finite set Template:Mvar to itself, and any initial value Template:Math in Template:Mvar, the sequence of iterated function values
- <math> x_0,\ x_1=f(x_0),\ x_2=f(x_1),\ \dots,\ x_i=f(x_{i-1}),\ \dots</math>
must eventually use the same value twice: there must be some pair of distinct indices Template:Mvar and Template:Mvar such that Template:Math. Once this happens, the sequence must continue periodically, by repeating the same sequence of values from Template:Math to Template:Math. Cycle detection is the problem of finding Template:Mvar and Template:Mvar, given Template:Mvar and Template:Math.
Several algorithms are known for finding cycles quickly and with little memory. Robert W. Floyd's tortoise and hare algorithm moves two pointers at different speeds through the sequence of values until they both point to equal values. Alternatively, Brent's algorithm is based on the idea of exponential search. Both Floyd's and Brent's algorithms use only a constant number of memory cells, and take a number of function evaluations that is proportional to the distance from the start of the sequence to the first repetition. Several other algorithms trade off larger amounts of memory for fewer function evaluations.
The applications of cycle detection include testing the quality of pseudorandom number generators and cryptographic hash functions, computational number theory algorithms, detection of infinite loops in computer programs and periodic configurations in cellular automata, automated shape analysis of linked list data structures, and detection of deadlocks for transactions management in DBMS.
ExampleEdit
The figure shows a function Template:Mvar that maps the set Template:Math to itself. If one starts from Template:Math and repeatedly applies Template:Mvar, one sees the sequence of values
The cycle in this value sequence is Template:Math.
DefinitionsEdit
Let Template:Mvar be any finite set, Template:Mvar be any endofunction from Template:Mvar to itself, and Template:Math be any element of Template:Mvar. For any Template:Math, let Template:Math. Let Template:Mvar be the smallest index such that the value Template:Mvar reappears infinitely often within the sequence of values Template:Mvar, and let Template:Mvar (the loop length) be the smallest positive integer such that Template:Math. The cycle detection problem is the task of finding Template:Mvar and Template:Mvar.<ref name=Joux>Template:Citation.</ref>
One can view the same problem graph-theoretically, by constructing a functional graph (that is, a directed graph in which each vertex has a single outgoing edge) the vertices of which are the elements of Template:Mvar and the edges of which map an element to the corresponding function value, as shown in the figure. The set of vertices reachable from starting vertex Template:Math form a subgraph with a shape resembling the Greek letter rho (Template:Mvar): a path of length Template:Mvar from Template:Math to a cycle of Template:Mvar vertices.<ref name="j224">Template:Harvtxt.</ref>
Practical cycle-detection algorithms do not find Template:Mvar and Template:Mvar exactly.Template:R They usually find lower and upper bounds Template:Math for the start of the cycle, and a more detailed search of the range must be performed if the exact value of Template:Mvar is needed. Also, most algorithms do not guarantee to find Template:Mvar directly, but may find some multiple Template:Math. (Continuing the search for an additional Template:Math steps, where Template:Mvar is the smallest prime divisor of Template:Mvar, will either find the true Template:Mvar or prove that Template:Math.)
Computer representationEdit
Except in toy examples like the above, Template:Mvar will not be specified as a table of values. Such a table implies Template:Math space complexity, and if that is permissible, an associative array mapping Template:Mvar to Template:Mvar will detect the first repeated value. Rather, a cycle detection algorithm is given a black box for generating the sequence Template:Mvar, and the task is to find Template:Mvar and Template:Mvar using very little memory.
The black box might consist of an implementation of the recurrence function Template:Mvar, but it might also store additional internal state to make the computation more efficient. Although Template:Math must be true in principle, this might be expensive to compute directly; the function could be defined in terms of the discrete logarithm of Template:Math or some other difficult-to-compute property which can only be practically computed in terms of additional information. In such cases, the number of black boxes required becomes a figure of merit distinguishing the algorithms.
A second reason to use one of these algorithms is that they are pointer algorithms which do no operations on elements of Template:Mvar other than testing for equality. An associative array implementation requires computing a hash function on the elements of Template:Mvar, or ordering them. But cycle detection can be applied in cases where neither of these are possible.
The classic example is Pollard's rho algorithm for integer factorization, which searches for a factor Template:Mvar of a given number Template:Mvar by looking for values Template:Mvar and Template:Math which are equal modulo Template:Mvar without knowing Template:Mvar in advance. This is done by computing the greatest common divisor of the difference Template:Math with a known multiple of Template:Mvar, namely Template:Mvar. If the gcd is non-trivial (neither 1 nor Template:Mvar), then the value is a proper factor of Template:Mvar, as desired.<ref name="j224"/> If Template:Mvar is not prime, it must have at least one factor Template:Math, and by the birthday paradox, a random function Template:Mvar has an expected cycle length (modulo Template:Mvar) of Template:Math.
AlgorithmsEdit
If the input is given as a subroutine for calculating Template:Mvar, the cycle detection problem may be trivially solved using only Template:Math function applications, simply by computing the sequence of values Template:Math and using a data structure such as a hash table to store these values and test whether each subsequent value has already been stored. However, the space complexity of this algorithm is proportional to Template:Math, unnecessarily large. Additionally, to implement this method as a pointer algorithm would require applying the equality test to each pair of values, resulting in quadratic time overall. Thus, research in this area has concentrated on two goals: using less space than this naive algorithm, and finding pointer algorithms that use fewer equality tests.
Floyd's tortoise and hareEdit
Floyd's cycle-finding algorithm is a pointer algorithm that uses only two pointers, which move through the sequence at different speeds. It is also called the "tortoise and the hare algorithm", alluding to Aesop's fable of The Tortoise and the Hare.
The algorithm is named after Robert W. Floyd, who was credited with its invention by Donald Knuth.<ref name="knuth">Template:Citation</ref><ref>Handbook of Applied Cryptography, by Alfred J. Menezes, Paul C. van Oorschot, Scott A. Vanstone, p. 125, describes this algorithm and others</ref> However, the algorithm does not appear in Floyd's published work, and this may be a misattribution: Floyd describes algorithms for listing all simple cycles in a directed graph in a 1967 paper,<ref>Template:Citation</ref> but this paper does not describe the cycle-finding problem in functional graphs that is the subject of this article. In fact, Knuth's statement (in 1969), attributing it to Floyd, without citation, is the first known appearance in print, and it thus may be a folk theorem, not attributable to a single individual.<ref>The Hash Function BLAKE, by Jean-Philippe Aumasson, Willi Meier, Raphael C.-W. Phan, Luca Henzen (2015), p. 21, footnote 8</ref>
The key insight in the algorithm is as follows. If there is a cycle, then, for any integers Template:Math and Template:Math, Template:Math, where Template:Mvar is the length of the loop to be found, Template:Mvar is the index of the first element of the cycle, and Template:Mvar is a whole integer representing the number of loops. Based on this, it can then be shown that Template:Math for some Template:Math if and only if Template:Math (if Template:Math in the cycle, then there exists some Template:Mvar such that Template:Math, which implies that Template:Math; and if there are some Template:Mvar and Template:Mvar such that Template:Math, then Template:Math and Template:Math). Thus, the algorithm only needs to check for repeated values of this special form, one twice as far from the start of the sequence as the other, to find a period Template:Mvar of a repetition that is a multiple of Template:Mvar. Once Template:Mvar is found, the algorithm retraces the sequence from its start to find the first repeated value Template:Math in the sequence, using the fact that Template:Mvar divides Template:Mvar and therefore that Template:Math. Finally, once the value of Template:Mvar is known it is trivial to find the length Template:Mvar of the shortest repeating cycle, by searching for the first position Template:Math for which Template:Math.
The algorithm thus maintains two pointers into the given sequence, one (the tortoise) at Template:Math, and the other (the hare) at Template:Math. At each step of the algorithm, it increases Template:Mvar by one, moving the tortoise one step forward and the hare two steps forward in the sequence, and then compares the sequence values at these two pointers. The smallest value of Template:Math for which the tortoise and hare point to equal values is the desired value Template:Mvar.
The following Python code shows how this idea may be implemented as an algorithm.
<syntaxhighlight lang="python"> def floyd(f, x0) -> (int, int):
"""Floyd's cycle detection algorithm.""" # Main phase of algorithm: finding a repetition x_i = x_2i. # The hare moves twice as quickly as the tortoise and # the distance between them increases by 1 at each step. # Eventually they will both be inside the cycle and then, # at some point, the distance between them will be # divisible by the period λ. tortoise = f(x0) # f(x0) is the element/node next to x0. hare = f(f(x0)) while tortoise != hare: tortoise = f(tortoise) hare = f(f(hare)) # At this point the tortoise position, ν, which is also equal # to the distance between hare and tortoise, is divisible by # the period λ. So hare moving in cycle one step at a time, # and tortoise (reset to x0) moving towards the cycle, will # intersect at the beginning of the cycle. Because the # distance between them is constant at 2ν, a multiple of λ, # they will agree as soon as the tortoise reaches index μ.
# Find the position μ of first repetition. mu = 0 tortoise = x0 while tortoise != hare: tortoise = f(tortoise) hare = f(hare) # Hare and tortoise move at same speed mu += 1 # Find the length of the shortest cycle starting from x_μ # The hare moves one step at a time while tortoise is still. # lam is incremented until λ is found. lam = 1 hare = f(tortoise) while tortoise != hare: hare = f(hare) lam += 1 return lam, mu
</syntaxhighlight>
This code only accesses the sequence by storing and copying pointers, function evaluations, and equality tests; therefore, it qualifies as a pointer algorithm. The algorithm uses Template:Math operations of these types, and Template:Math storage space.<ref>Template:Harvtxt, Section 7.1.1, Floyd's cycle-finding algorithm, pp. 225–226.</ref>
Brent's algorithmEdit
Richard P. Brent described an alternative cycle detection algorithm that, like the tortoise and hare algorithm, requires only two pointers into the sequence.<ref name="brent">Template:Citation.</ref> However, it is based on a different principle: searching for the smallest power of two Template:Math that is larger than both Template:Mvar and Template:Mvar. For Template:Math, the algorithm compares Template:Math with each subsequent sequence value up to the next power of two, stopping when it finds a match. It has two advantages compared to the tortoise and hare algorithm: it finds the correct length Template:Mvar of the cycle directly, rather than needing to search for it in a subsequent stage, and its steps involve only one evaluation of the function Template:Mvar rather than three.<ref>Template:Harvtxt, Section 7.1.2, Brent's cycle-finding algorithm, pp. 226–227.</ref>
The following Python code shows how this technique works in more detail. <syntaxhighlight lang="python"> def brent(f, x0) -> (int, int):
"""Brent's cycle detection algorithm.""" # main phase: search successive powers of two power = lam = 1 tortoise = x0 hare = f(x0) # f(x0) is the element/node next to x0. # this assumes there is a cycle; otherwise this loop won't terminate while tortoise != hare: if power == lam: # time to start a new power of two? tortoise = hare power *= 2 lam = 0 hare = f(hare) lam += 1
# Find the position of the first repetition of length λ tortoise = hare = x0 for i in range(lam): hare = f(hare) # The distance between the hare and tortoise is now λ.
# Next, the hare and tortoise move at same speed until they agree mu = 0 while tortoise != hare: tortoise = f(tortoise) hare = f(hare) mu += 1 return lam, mu
</syntaxhighlight>
Like the tortoise and hare algorithm, this is a pointer algorithm that uses Template:Math tests and function evaluations and Template:Math storage space. It is not difficult to show that the number of function evaluations can never be higher than for Floyd's algorithm. Brent claims that, on average, his cycle finding algorithm runs around 36% more quickly than Floyd's and that it speeds up the Pollard rho algorithm by around 24%. He also performs an average case analysis for a randomized version of the algorithm in which the sequence of indices traced by the slower of the two pointers is not the powers of two themselves, but rather a randomized multiple of the powers of two. Although his main intended application was in integer factorization algorithms, Brent also discusses applications in testing pseudorandom number generators.<ref name="brent"/>
Gosper's algorithmEdit
R. W. Gosper's algorithm<ref name="gosper-impl">{{#invoke:citation/CS1|citation |CitationClass=web }}</ref><ref name="hakmem-132">{{#invoke:citation/CS1|citation |CitationClass=web }}</ref> finds the period <math>\lambda</math>, and the lower and upper bound of the starting point, <math>\mu_l</math> and <math>\mu_u</math>, of the first cycle. The difference between the lower and upper bound is of the same order as the period, i.e. <math>\mu_l + \lambda \approx \mu_h</math>.
The algorithm maintains an array of tortoises <math>T_j</math>. For each <math>x_i</math>:
- For each <math>0 \le j \le \log_2 i,</math> compare <math>x_i</math> to <math>T_j</math>.
- If <math>x_i = T_j</math>, a cycle has been detected, of length <math>\lambda = (i - 2^j) \bmod 2^{j+1} + 1.</math>
- If no match is found, set <math>T_k \leftarrow x_i</math>, where <math>k</math> is the number of trailing zeros in the binary representation of <math>i+1</math>. I.e. the greatest power of 2 which divides <math>i+1</math>.
If it is inconvenient to vary the number of comparisons as <math>i</math> increases, you may initialize all of the <math>T_j = x_0</math>, but must then return <math>\lambda = i</math> if <math>x_i = T_j</math> while <math>i < 2^j</math>.
AdvantagesEdit
The main features of Gosper's algorithm are that it is economical in space, very economical in evaluations of the generator function, and always finds the exact cycle length (never a multiple). The cost is a large number of equality comparisons. It could be roughly described as a concurrent version of Brent's algorithm. While Brent's algorithm uses a single tortoise, repositioned every time the hare passes a power of two, Gosper's algorithm uses several tortoises (several previous values are saved), which are roughly exponentially spaced. According to the note in HAKMEM item 132,<ref name="hakmem-132" /> this algorithm will detect repetition before the third occurrence of any value, i.e. the cycle will be iterated at most twice. HAKMEM also states that it is sufficient to store <math>\lceil\log_2\lambda\rceil</math> previous values; however, this only offers a saving if we know a priori that <math>\lambda</math> is significantly smaller than <math>\mu</math>. The standard implementations<ref name="gosper-impl"/> store <math>\lceil\log_2 (\mu + 2\lambda)\rceil</math> values. For example, assume the function values are 32-bit integers, so <math>\mu + \lambda \le 2^{32}</math> and <math>\mu + 2\lambda \le 2^{33}.</math> Then Gosper's algorithm will find the cycle after less than <math>\mu + 2\lambda</math> function evaluations (in fact, the most possible is <math>3\cdot 2^{31} - 1</math>), while consuming the space of 33 values (each value being a 32-bit integer).
ComplexityEdit
Upon the <math>i</math>-th evaluation of the generator function, the algorithm compares the generated value with <math>\log_2 i</math> previous values; observe that <math>i</math> goes up to at least <math>\mu + \lambda</math> and at most <math>\mu + 2\lambda</math>. Therefore, the time complexity of this algorithm is <math>O((\mu + \lambda) \cdot \log (\mu + \lambda))</math>. Since it stores <math>\log_2 (\mu + 2\lambda)</math> values, its space complexity is <math>\Theta(\log (\mu + \lambda))</math>. This is under the usual transdichotomous model, assumed throughout this article, in which the size of the function values is constant. Without this assumption, we know it requires <math>\Omega(\log (\mu + \lambda))</math> space to store <math>\mu + \lambda</math> distinct values, so the overall space complexity is <math>\Omega(\log^2 (\mu + \lambda)).</math>
Time–space tradeoffsEdit
A number of authors have studied techniques for cycle detection that use more memory than Floyd's and Brent's methods, but detect cycles more quickly. In general these methods store several previously-computed sequence values, and test whether each new value equals one of the previously-computed values. In order to do so quickly, they typically use a hash table or similar data structure for storing the previously-computed values, and therefore are not pointer algorithms: in particular, they usually cannot be applied to Pollard's rho algorithm. Where these methods differ is in how they determine which values to store. Following Nivasch,<ref name="nivasch">Template:Citation.</ref> we survey these techniques briefly.
- Brent<ref name="brent"/> already describes variations of his technique in which the indices of saved sequence values are powers of a number Template:Mvar other than two. By choosing Template:Mvar to be a number close to one, and storing the sequence values at indices that are near a sequence of consecutive powers of Template:Mvar, a cycle detection algorithm can use a number of function evaluations that is within an arbitrarily small factor of the optimum Template:Math.<ref>Template:Citation.</ref><ref name="teske">Template:Citation.</ref>
- Sedgewick, Szymanski, and Yao<ref>Template:Citation.</ref> provide a method that uses Template:Mvar memory cells and requires in the worst case only <math>(\lambda+\mu)(1+cM^{-1/2})</math> function evaluations, for some constant Template:Mvar, which they show to be optimal. The technique involves maintaining a numerical parameter Template:Mvar, storing in a table only those positions in the sequence that are multiples of Template:Mvar, and clearing the table and doubling Template:Mvar whenever too many values have been stored.
- Several authors have described distinguished point methods that store function values in a table based on a criterion involving the values, rather than (as in the method of Sedgewick et al.) based on their positions. For instance, values equal to zero modulo some value Template:Mvar might be stored.<ref>Template:Citation.</ref><ref name="qd">Template:Citation.</ref> More simply, Nivasch<ref name="nivasch"/> credits D. P. Woodruff with the suggestion of storing a random sample of previously seen values, making an appropriate random choice at each step so that the sample remains random.
- Nivasch<ref name="nivasch"/> describes an algorithm that does not use a fixed amount of memory, but for which the expected amount of memory used (under the assumption that the input function is random) is logarithmic in the sequence length. An item is stored in the memory table, with this technique, when no later item has a smaller value. As Nivasch shows, the items with this technique can be maintained using a stack data structure, and each successive sequence value need be compared only to the top of the stack. The algorithm terminates when the repeated sequence element with smallest value is found. Running the same algorithm with multiple stacks, using random permutations of the values to reorder the values within each stack, allows a time–space tradeoff similar to the previous algorithms. However, even the version of this algorithm with a single stack is not a pointer algorithm, due to the comparisons needed to determine which of two values is smaller.
Any cycle detection algorithm that stores at most Template:Mvar values from the input sequence must perform at least <math>(\lambda+\mu)\left(1+\frac{1}{M-1}\right)</math> function evaluations.<ref name="fich">Template:Citation.</ref><ref>Template:Citation.</ref>
ApplicationsEdit
Cycle detection has been used in many applications.
- Determining the cycle length of a pseudorandom number generator is one measure of its strength. This is the application cited by Knuth in describing Floyd's method.<ref name="knuth"/> Brent<ref name="brent"/> describes the results of testing a linear congruential generator in this fashion; its period turned out to be significantly smaller than advertised. For more complex generators, the sequence of values in which the cycle is to be found may not represent the output of the generator, but rather its internal state.
- Several number-theoretic algorithms are based on cycle detection, including Pollard's rho algorithm for integer factorization<ref>Template:Citation.</ref> and his related kangaroo algorithm for the discrete logarithm problem.<ref>Template:Citation.</ref>
- In cryptographic applications, the ability to find two distinct values xμ−1 and xλ+μ−1 mapped by some cryptographic function ƒ to the same value xμ may indicate a weakness in ƒ. For instance, Quisquater and Delescaille<ref name="qd"/> apply cycle detection algorithms in the search for a message and a pair of Data Encryption Standard keys that map that message to the same encrypted value; Kaliski, Rivest, and Sherman<ref name="krs">Template:Citation.</ref> also use cycle detection algorithms to attack DES. The technique may also be used to find a collision in a cryptographic hash function.<ref>Template:Harvtxt, Section 7.5, Collisions in hash functions, pp. 242–245.</ref>
- Cycle detection may be helpful as a way of discovering infinite loops in certain types of computer programs.<ref>Template:Citation.</ref>
- Periodic configurations in cellular automaton simulations may be found by applying cycle detection algorithms to the sequence of automaton states.<ref name="nivasch"/>
- Shape analysis of linked list data structures is a technique for verifying the correctness of an algorithm using those structures. If a node in the list incorrectly points to an earlier node in the same list, the structure will form a cycle that can be detected by these algorithms.<ref>Template:Citation.</ref> In Common Lisp, the S-expression printer, under control of the
*print-circle*
variable, detects circular list structure and prints it compactly. - Teske<ref name="teske"/> describes applications in computational group theory: determining the structure of an Abelian group from a set of its generators. The cryptographic algorithms of Kaliski et al.<ref name="krs"/> may also be viewed as attempting to infer the structure of an unknown group.
- Template:Harvtxt briefly mentions an application to computer simulation of celestial mechanics, which she attributes to William Kahan. In this application, cycle detection in the phase space of an orbital system may be used to determine whether the system is periodic to within the accuracy of the simulation.<ref name="fich"/>
- In Mandelbrot Set fractal generation some performance techniques are used to speed up the image generation. One of them is called "period checking", which consists of finding the cycles in a point orbit. This article describes the "period checking" technique. You can find another explanation here. Some cycle detection algorithms have to be implemented in order to implement this technique.
ReferencesEdit
External linksEdit
- Gabriel Nivasch, The Cycle Detection Problem and the Stack Algorithm
- Tortoise and Hare, Portland Pattern Repository
- Floyd's Cycle Detection Algorithm (The Tortoise and the Hare)
- Brent's Cycle Detection Algorithm (The Teleporting Turtle)