Lably
Skip to content

Fractional cascading

  • 12 min read
  • 4 chapters

Related on Lably

Through Computer science, Binary search and Directed graph

Example

As a simple example of fractional cascading, consider the following problem. We are given as input a collection of k ordered lists Li of numbers, such that the total length ∑i|Li| of all lists is n, and must process them so that we can perform binary searches for a query value q in each of the k lists. For instance, with k=4 and n=17,

L1 = 24, 64, 65, 80, 93
L2 = 23, 25, 26
L3 = 13, 44, 62, 66
L4 = 11, 35, 46, 79, 81

The simplest solution to this searching problem is just to store each list separately. If we do so, the space requirement is O(n), but the time to perform a query is O(klog⁡(n/k)), as we must perform a separate binary search in each of k lists. The worst case for querying this structure occurs when each of the k lists has equal size n/k, so each of the k binary searches involved in a query takes time O(log⁡(n/k)).

A second solution allows faster queries at the expense of more space: we may merge all the k lists into a single big list L, and associate with each item x of L a list of the results of searching for x in each of the smaller lists Li. If we describe an element of this merged list as x[a,b,c,d] where x is the numerical value and a, b, c, and d are the positions (the first number has position 0) of the next element at least as large as x in each of the original input lists (or the position after the end of the list if no such element exists), then we would have

L = 11[0,0,0,0], 13[0,0,0,1], 23[0,0,1,1], 24[0,1,1,1], 25[1,1,1,1], 26[1,2,1,1],
35[1,3,1,1], 44[1,3,1,2], 46[1,3,2,2], 62[1,3,2,3], 64[1,3,3,3], 65[2,3,3,3],
66[3,3,3,3], 79[3,3,4,3], 80[3,3,4,4], 81[4,3,4,4], 93[4,3,4,5]

This merged solution allows a query in time O(k+log⁡n): simply search for q in L and then report the results stored at the item x found by this search. For instance, if q=50, searching for q in L finds the item 62[1,3,2,3], from which we return the results L1[1]=64, L2[3] (a flag value indicating that q is past the end of L2), L3[2]=62, and L4[3]=79. However, this solution pays a high penalty in space complexity: it uses space O(kn) as each of the n items in L must store a list of k search results.

Fractional cascading allows this same searching problem to be solved with time and space bounds meeting the best of both worlds: query time O(k+log⁡n), and space O(n). The fractional cascading solution is to store a new sequence of lists Mi. The final list in this sequence, Mk, is equal to Lk; each earlier list Mi is formed by merging Li with every second item from Mi+1. With each item x in this merged list, we store two numbers: the position resulting from searching for x in Li and the position resulting from searching for x in Mi+1. For the data above, this would give us the following lists:

M1 = 24[0, 1], 25[1, 1], 35[1, 3], 64[1, 5], 65[2, 5], 79[3, 5], 80[3, 6], 93[4, 6]
M2 = 23[0, 1], 25[1, 1], 26[2, 1], 35[3, 1], 62[3, 3], 79[3, 5]
M3 = 13[0, 1], 35[1, 1], 44[1, 2], 62[2, 3], 66[3, 3], 79[4, 3]
M4 = 11[0, 0], 35[1, 0], 46[2, 0], 79[3, 0], 81[4, 0]

Suppose we wish to perform a query in this structure, for q=50. We first do a standard binary search for q in M1, finding the value 64[1,5]. The "1" in 64[1,5], tells us that the search for q in L1 should return L1[1]=64. The "5" in 64[1,5] tells us that the approximate location of q in M2 is position 5. More precisely, binary searching for q in M2 would return either the value 79[3,5] at position 5, or the value 62[3,3] one place earlier. By comparing q to 62, and observing that it is smaller, we determine that the correct search result in M2 is 62[3,3]. The first "3" in 62[3,3] tells us that the search for q in L2 should return L2[3], a flag value meaning that q is past the end of list L2. The second "3" in 62[3,3] tells us that the approximate location of q in M3 is position 3. More precisely, binary searching for q in M3 would return either the value 62[2,3] at position 3, or the value 44[1,2] one place earlier. A comparison of q with the smaller value 44 shows us that the correct search result in M3 is 62[2,3]. The "2" in 62[2,3] tells us that the search for q in L3 should return L3[2]=62, and the "3" in 62[2,3] tells us that the result of searching for q in M4 is either 79[3,0] at position 3 or 46[2,0] at position 2; comparing q with 46 shows that the correct result is 79[3,0] and that the result of searching for q in L4 is L4[3]=79. Thus, we have found q in each of our four lists, by doing a binary search in the single list M1 followed by a single comparison in each of the successive lists.

More generally, for any data structure of this type, we perform a query by doing a binary search for q in M1, and determining from the resulting value the position of q in L1. Then, for each i>1, we use the known position of q in Mi to find its position in Mi+1. The value associated with the position of q in Mi points to a position in Mi+1 that is either the correct result of the binary search for q in Mi+1 or is a single step away from that correct result, so stepping from i to i+1 requires only a single comparison. Thus, the total time for a query is O(k+log⁡n)

In our example, the fractionally cascaded lists have a total of 25 elements, less than twice that of the original input. In general, the size of Mi in this data structure is at most |Li|+12|Li+1|+14|Li+2|+⋯+12j|Li+j|+⋯, as may easily be proven by induction. Therefore, the total size of the data structure is at most ∑|Mi|=∑|Li|(1+12+14+⋯)≤2n=O(n), as may be seen by regrouping the contributions to the total size coming from the same input list Li together with each other.

Sign in to keep reading

3 more chapters are free with a Lably account.

More in Theoretical computer science