Study Vault Search notes, aliases and full text
Graph
TopicDBISAI-written · not yet proofread

List Intersection and Search

How sorted lists (inverted lists, sorted join columns) are intersected and merged: the linear-time zipper, binary search and galloping (exponential) search intersection, and when to use which.

Concepts#

  1. List Intersectionthe operation and its algorithms: zipper (intersect/merge), binary search and galloping search intersection, multi-list intersection start here
  2. Exponential Searchgalloping search for one value in a sorted array in O(log⁡d)O(\log d); the building block of the fastest intersection

How it fits together#

One principle, three uses. The zipper walks two sorted lists with one pointer each and advances the pointer at the smaller element. The same loop serves:

UseListsOutput on equal elementsLecture
AND keyword queryinverted listsemit the ID (intersection)DBIS_VL01 > Query Processing
OR keyword query / rankinginverted lists with scoresemit once, sum the scores (merge)DBIS_VL02 > Ranking: Basic Idea
Merge join in SQLsorted join columns (value, row index)emit all combinations of rows with that valueDBIS_VL05 > Merge join

From linear to sublinear. The zipper touches every element of both lists. If k≪nk \ll n, most of the long list is skipped anyway, so it is cheaper to search each of the kk elements in the long list:

AlgorithmTimeSearch per element of the short list
ZipperO(k+n)O(k + n)none (linear scan)
Binary search intersectionO(klog⁡n)O(k \log n)binary search over the whole long list
Galloping search intersectionO(klog⁡(1+n/k))O(k \log(1 + n/k))Exponential Search from the previous position, then binary search

Galloping is asymptotically never worse than the zipper (kln⁡(1+n/k)≤nk \ln(1 + n/k) \leq n, from ex≥1+xe^x \geq 1 + x) and never worse than binary search (n/k≤nn/k \leq n). In practice the zipper has the smaller constant factor, so: zipper if the lengths are similar, galloping if they differ by more than a factor of about 10 (List Intersection > Practical choice).

More than two lists. Intersect pairwise, shortest lists first, so the intermediate results stay short; by Zipf’s Law list lengths vary enormously. Alternatively a kk-way intersect/merge with a priority queue in O(log⁡k⋅∑i∣Li∣)O(\log k \cdot \sum_i |L_i|).

Key Formulas#

  • Zipper: O(k+n)O(k + n); binary search intersection: O(klog⁡n)O(k \log n); galloping: O(klog⁡(1+n/k))O(k \log(1 + n/k)) — List Intersection > Formula
  • Galloping search for xx at position dd: O(log⁡d)O(\log d); number of doubling jumps ⌈log⁡2(i+1)⌉\lceil \log_2(i + 1) \rceil — Exponential Search > Properties

Exercises & Exam Questions#

SuccessSolution
def intersect(list1, list2):
    if len(list1) > len(list2):
        list1, list2 = list2, list1
    result = []
    for x in list1:
        left, right = 0, len(list2)
        while left < right:
            mid = int((right - left)/2) + left
            if list2[mid] > x:
                right = mid
            elif list2[mid] < x:
                left = mid + 1
            else:
                result.append(x)
                break
    return result
  • Swap so that list1 is the shorter list (length kk).
  • For each element in the shorter list, binary search in the longer list (length nn): O(log⁡n)O(\log n) each, O(k⋅log⁡n)O(k \cdot \log n) in total.
SuccessSolution
  1. Consider ln⁡\ln instead of log⁡\log; the difference is only a constant factor.
  2. ex≥1+xe^x \geq 1 + x implies ln⁡(1+x)≤x\ln(1 + x) \leq x for all x>−1x > -1.
  3. With x=n/kx = n/k: ln⁡(1+n/k)≤n/k\ln(1 + n/k) \leq n/k.
  4. Therefore k⋅ln⁡(1+n/k)≤k⋅n/k=n≤k+nk \cdot \ln(1 + n/k) \leq k \cdot n/k = n \leq k + n.
  5. Hence O(k⋅log⁡(1+n/k))⊆O(k+n)O(k \cdot \log(1 + n/k)) \subseteq O(k + n). ■\blacksquare
SuccessSolution

If the lists differ in length by more than a factor of, say, 10 (n/k>10n/k > 10), use galloping search; otherwise use the zipper. Galloping search is asymptotically always better or equal, but it is a more complex algorithm with a larger constant factor. Only if k≪nk \ll n does the asymptotic advantage pay off; otherwise the simpler zipper is faster in practice (as observed in the exercises). (The official solution writes “k/nk/n less than 10”, meaning n/k>10n/k > 10.)

SuccessSolution

The number of jumps is k=⌈log⁡2(i+1)⌉k = \lceil \log_2(i+1) \rceil.

  1. After kk jumps the position checked is 0+1+2+…+2k−1=2k−10 + 1 + 2 + \ldots + 2^{k-1} = 2^k - 1, and the search stops there, so 2k−1≥i  ⟺  k≥log⁡2(i+1)2^k - 1 \geq i \iff k \geq \log_2(i+1).
  2. The position before that is 2k−1−12^{k-1} - 1, where it did not stop, so 2k−1−1<i  ⟺  k−1<log⁡2(i+1)2^{k-1} - 1 < i \iff k - 1 < \log_2(i+1).
  3. Hence k=⌈log⁡2(i+1)⌉k = \lceil \log_2(i+1) \rceil.
SuccessSolution

Exponential phase: check positions ji=2i−1j_i = 2^i - 1 until A[jk]≥xA[j_k] \geq x; then binary search in [jk−1,jk][j_{k-1}, j_k].

  1. Since jk−1<dj_{k-1} < d: 2k−1−1<d2^{k-1} - 1 < d, hence 2k−1≤d2^{k-1} \leq d and k≤log⁡2d+1k \leq \log_2 d + 1. The exponential phase takes O(k)=O(log⁡d)O(k) = O(\log d) steps.
  2. Hence jk≤2log⁡2d+1−1<2dj_k \leq 2^{\log_2 d + 1} - 1 < 2d. The binary search halves the interval in every step: O(log⁡(2d))=O(log⁡d)O(\log(2d)) = O(\log d) steps.
  3. Together: O(log⁡d)O(\log d). ■\blacksquare
SuccessSolution
def exponential_search(A, x):
    i = 0
    while A[i] < x:
        i = min(max(2 * i, 1), len(A) - 1)
    return i

Start at index 0 and double ii (max(2*i, 1) advances from 0 to 1), clamped to len(A) - 1. The loop runs O(log⁡(1+j))O(\log(1 + j)) times because ii doubles in each iteration; it stops at the first probed i≥ji \geq j, and the previous probe i/2<ji/2 < j gives i<2ji < 2j.

SuccessSolution
def merge(A, B):
    i = j = 0
    res = []
    while i < len(A) or j < len(B):
        if j == len(B) or (i < len(A) and A[i] <= B[j]):
            res.append(A[i])
            i += 1
        else:
            res.append(B[j])
            j += 1
    return res

Two-pointer merge (zipper): at each step take the smaller of A[i] and B[j] (or whichever list has elements left). Every element is processed once: O(∣A∣+∣B∣)O(|A| + |B|).

Occurrence in past exams#

ExamTaskPointsSubtopics
DBIS Exam WS1819 (not published)Task 120binary search intersection (code), galloping vs. zipper proof, practical choice
DBIS Exam WS1920 (not published)Task 1.310exact number of jumps of exponential search
DBIS Exam WS2122 (not published)Task 1.410galloping search in O(log⁡d)O(\log d) (proof)
DBIS Exam WS2223 (not published)Task 1.15implement exponential search
DBIS Exam WS2324 (not published)Task 1.210merge two sorted lists (code)

Not in WS 13/14 – WS 17/18 or the WS 23/24 retake. Pattern: one 5–10 point subtask (code or proof) inside a larger indexing task; galloping/exponential search was never lectured in WS 25/26, the zipper and merging were.

Lectures#

  • DBIS_VL01 — query processing: zipper intersection of two inverted lists, merging, k>2k > 2 lists
  • DBIS_VL05 — merge join of sorted join columns via the zipper algorithm

Search

Type In
↑ ↓ move ↵ open ⇥ next section Ctrl↵ results page Prefix # to search text only