Union-Find (or Disjoint Set) is a data structure used to manage disjoint sets of elements through two primary operations: find(x) and union(A, B). The find(x) operation determines which set the element xx belongs to, while the union(A, B, C) operation merges the sets AA and BB into a single set CC. In this article, we will explore Robert Endre Tarjan’s work on determining the upper bound of the time complexity for these Union-Find operations.


Who this article is for:

  • You already know and have implemented the union-find data structure and want to understand its time complexity.
  • You want to learn about the inverse Ackermann function, α(m,n)\alpha(m, n), in the context of union-find time complexity.
  • None of the above, but you are willing to use Google or ChatGPT to learn as you read through the article, and you’re not intimidated by math equations.

Who this article is NOT for:

  • You’re looking for an introduction to the union-find data structure. In this case first read about it from cp-algorithms and then return to this article for the time complexity.
  • You’ve already read the referenced paper in the reference section.
  • You’re seeking the lower bound of the union-find time complexity.
  • You want to gain first-hand experience by reading the paper directly.

Note:

The idea discussed in this article is a slight variation of the algorithm presented in the paper “Efficiency of a Good But Not Linear Set Union Algorithm” by Robert Endre Tarjan. I highly recommend reading the original paper in the reference section.

In the paper, the size heuristic (also known as union by size) is used. However, in this article, we explore another variation known as “union by depth” of the trees.


Introduction

Union Find (or Disjoint Set) is a data structure to manipulate disjoint set of elements using the following operations.

  1. find(x): This function is used to determine which set the element xx belongs to. It locates the node containing xx and follows pointers up to the root of the corresponding tree to identify the name of the set. The collapsing rule may be applied to flatten the tree structure, optimizing future queries.

  2. union(A, B, C): This function is used to combine the sets AA and BB into a new set named CC. It locates the roots of the sets AA and BB, makes one root a child of the other, and names the new root as CC. The choice of which root becomes the child can be made either randomly or by applying the weighted union rule, where the smaller tree is attached under the larger tree to keep the resulting structure balanced.

Collapsing Rule (aka. path compression)

After a find operation, make all the vertices traversed during the operation direct children of the root of the tree. (Refer to below figure)

Fig 1:Collapsing rule
Fig 1:Collapsing rule

Weighted Union Rule (“union by depth” for the context of this article):

If depth of the tree formed by set A is greater than that of set B, make B son of A. Otherwise make A son of B.

Fig 2: Weighted union rule
Fig 2: Weighted union rule

Note: The depth of the tree rooted at r1 is less than that of the tree rooted at r2. Therefore, r1 is made a child of r2. In the original paper, the size-based variation was discussed.

The Claim

Suppose we are given nn elements, and we want to perform mnm \geq n find operations and n1n-1 union operations. Let t(m,n)t(m, n) denote the maximum time required for a sequence of mm intermixed find operations and nn union operations.

  • By only using collapsing rule, we can achieve the following upper bound, for some positive constant kk. t(m,n)kmmax(1,log(n2/m)log(2m/n)) t(m,n) \leq k \cdot m \cdot max \Big(1, \frac{log(n^2/m)}{log(2m/n)} \Big)
  • By using both collapsing rule and weighted union rule, we can achieve the following upper bound, for some positive constant kk and a variation of inverse Ackermann function α(m,n)\alpha(m,n). t(m,n)kmα(m,n) t(m,n) \leq k \cdot m \cdot \alpha(m,n)

An Upper Bound (Proof)

Let’s build a tree (called TT) by performing all n1n-1 union operations first. Assign a rank to each vertex, equal to the height of the tree. This rank will remain unchanged in the future. (Refer to the figure below.)

Fig 3: Building a tree using all the union operations.
Fig 3: Building a tree using all the union operations.

Let the given sequence of operations are as follows:

1
2
3
4
5
6
7
8
9
union(N2, N5)
union(N3, N6)
union(N6, N7)
union(N6, N8)
find(N8) <-- We are here
union(N1, N2)
union(N1, N3)
union(N2, N4)
... // other find operations

To compute the find operation at line number 5 using tree TT, we follow the path from node N8N_8 to its furthest ancestor, but only using the edges formed by union operations prior to this find operation (operations from line 1 to 4). We apply the collapsing rule to flatten the tree during this process. Let’s call this a partial-find. (Refer to the figure below.)

Fig 4: Edges adjust using collapsing rule
Fig 4: Edges adjust using collapsing rule

Note: To get an upper bound on t(m,n)t(m, n) by only using collapsing rule, we just need to bound sum of path lengths of all such partial findings on the tree TT.

Using only Collapsing Rule

Let’s attempt to bound the number of edges traversed during all partial find operations on the tree TT. To do this, we define a few classes containing sequences of positive integers for some positive constants bb and zz, as follows:

Classnum1num2num3num4
Class 000112233
Class 100bb2b2b3b3b
Class 200b2b^22b22b^23b23b^2
................
Class i00bib^i2bi2b^i3bi3b^i
................
Class z00bzb^z2bz2b^z3bz3b^z

Let’s assign classes to the edges xyx \rightarrow y using following rules.

  1. Choose the minimum class number i{1,2,..,z}i \in \{1, 2, .., z\}, if rank(x)rank(x) and rank(y)rank(y) can be fit in between two consecutive values.
  2. If some edges can not be put to any class from rule 1, put them in class z+1z+1.

For all the edges, we encounter during the mm partial-find, can be divided into following three categories. (refer figure below)

  1. Final edge from each class. (blue colour)
  2. Non-final edge belongs to class 0 till class z. (black colour)
  3. Non-final edge belongs to class z+1. (red colour)
Fig 5: Various class of edges, after $m$ partial-find. c(x) means, edge belongs to class x.
Fig 5: Various class of edges, after $m$ partial-find. c(x) means, edge belongs to class x.

Bounding the edges

  1. Final edges. (Let’s denote the count as c1c_1)

    For each partial-find, we can encounter at most z+1z + 1 final edges, since we only have z+1z + 1 classes. Therefore, for mm partial-find operations, the total number of final edges is bounded by (z+1)m(z + 1) \cdot m.

  2. Non-final edges belong to class 0 till class z. (Let’s denote the count as c2c_2)

    Each such edge comes from a vertex (bounded by nn). Let’s say such an edge xyx \rightarrow y coming out of vertex xx, belongs to two consecutive value, pp and qq, of class i (represented as two red circle, under column class i of Figure 6). Since this is a non final edge in the partial-find, there could be another edge uvu \rightarrow v, belongs to class i, in between pp and qq. After applying the collapsing rule, xyx \rightarrow y' (the green edge) in Figure 6 is formed and xyx \rightarrow y is removed. With this operation, the class of xyx \rightarrow y' either remains the same as xyx \rightarrow y or increases, since the rank difference (between yy' and xx) can only increase due to the collapsing rule.

    Let’s see the case where the class stays the same. In Figure 6, xyx \rightarrow y and uvu \rightarrow v present between two consecutive values of class i. After applying collapsing rule finally xx will be attached to yy' via the green edge. Let’s say after applying the rule, the class of the edge xyx \rightarrow y' which is coming out of xx does not change. Since uvu \rightarrow v and xyx \rightarrow y are present in class ii, they could not have been assigned to class i1i-1 by the definition of how classes are assigned to edges. This implies that there must be at least one value from class i1i-1 between r(u)r(u) and r(v)r(v), as well as between r(x)r(x) and r(y)r(y), as illustrated in Figure 6 (red circle under class i1i-1). Hence we have at least two values from class i1i-1 in between the ranks of xx and yy'.

    Fig 6: Applying collapsing rule. Red circle being numbers from the table for the class. Green edge was not present in original m partial-find, but is formed after applying collapsing rule.
    Fig 6: Applying collapsing rule. Red circle being numbers from the table for the class. Green edge was not present in original m partial-find, but is formed after applying collapsing rule.

    Observe that between two consecutive values of class ii, there can be at most b1b-1 values in class i1i-1, based on the way we have defined the sequence number for each class. After applying collapsing rule one time to the edge coming from xx, we have at least two values from class i1i-1 in between xyx \rightarrow y'. So after applying such operation bb times, the edge have to move to the next class. We can move at most zz times up in the class, so at most bzb \cdot z times edge traversal for one vertex. For nn vertices, it would be nbzn \cdot b \cdot z.

  3. Non final edge belongs to class z+1z+1. (Let’s denote the count as c3c_3)

    Let’s calculate total number of values in class zz, where the value is less than or equal to the max rank nn. Total number of such values is n/bzn/b^z

    k.bznkn/bz k.b^z \leq n \Rightarrow k \leq n/b^z

    Therefore, for an edge from a vertex in class z+1z+1, we can apply the collapsing rule at most n/bz+1n/b^z + 1 times. Since, there are nn vertices, total number of such application would be (n/bz+1)n(n/b^z + 1) \cdot n which equals to n2/bz+nn^2/b^z + n.

Total number of edges traversed (equation-1)

=c1+c2+c3 = c_1 + c_2 + c_3

=(z+1)m+bzn+n2bz+n = (z+1)m + bzn + \frac{n^2}{b^z} + n

We can pick suitable value for bb and zz to minimise the number of edge traversed. Let’s pick the following values.

b=2mnz=max(1,log(n2/m)log(b)) \begin{aligned} &b = \frac{2m}{n} \\ &z = max \Big(1, \frac{log(n^2/m)}{log(b)}\Big) \end{aligned}

By putting the values of bb and zz in equation-1, we get the following result, for a suitable constant kk.

(z+1)m+bzn+n2bz+n (z+1)m + bzn + \frac{n^2}{b^z} + n

=3mz+m+n+n2bz = 3mz + m + n + \frac{n^2}{b^z}

kmz+n2/max(b,blog(n2/m)/logb) \leq kmz + n^2 / max(b, b^{{log(n^2/m)}/logb})

kmz+n2/max(b,2log(n2/m)) \leq kmz + n^2 / max(b, 2^{log(n^2/m)})

kmz+n2/max(2m/n,n2/m) \leq kmz + n^2 / max(2m/n, n^2/m)

kmz \leq kmz

kmmax(1,log(n2/m)log(2m/n)) \leq k \cdot m \cdot max\Big(1, \frac{log(n^2/m)}{log(2m/n)}\Big)

Using weighted union rule with collapsing rule

We will again build the tree similar to what we have discussed here, but now we will use weighted union rule, while building the tree. Observe that, If rank(x)=krank(x) = k, then number of vertices in the tree rooted at xx is at least 2k2^k. This implies there are no more than n/2kn/2^k vertices of rank kk in the tree.

Let’s redefine the classes (for the below table, ii represents the class ii). Instead of using simple geometric progression as we did previously, we will use a variation of Ackermann function.

The Ackermann function A(i,j)A(i, j) is defined as follows.

1
2
3
4
A(0, x) = 2x
A(i, 0) = 0 for i≥1
A(i,2) = 2 for i ≥ 1
A(i, x) = A(i-1, A(i, x-1)) for i ≥ 1, x ≥ 2
i\x01234x
00224466882x2x
1022448816162x2^x
20224416162162^{16}222...2^{2^{2^{...}}} x-times
3022442162^{16}222...2^{2^{2^{...}}} 2162^{16}-times
402244222...2^{2^{2^{...}}} 2162^{16}-times
........

Let’s take the first zz sequence from the above table and try to assign the class to the edges in the same way we did when only applying the collapsing rule.

Bounding the edges

To calculate the total time complexity, we will use the same approach as the previous edge bounding technique.

  1. Final edges (Let’s denote the count as c4c_4)

    Since there are at most z+1z+1 final classes, there are atmost z+1z+1 final edges present. For mm queries, c4c_4 is bound by (z+1)m(z+1) \cdot m.

  2. Non-final edges belong to class 0 till class z (Let’s denote the count as c5c_5)

    We will follow the same argument as before. But, since the sequence of the table has changed, the number of element in the previous class (class i1i-1) has also changed. Observe that, now between two values of class ii, let’s say A(i,j)A(i, j) and A(i,j+1)A(i, j+1), there are at least A(i,j)A(i, j) values from the class i1i-1.

    The number of node xx, whose rank r(x)r(x) lies between A(i,j)A(i, j) and A(i,j+1)A(i, j+1) (Remember that, rank are related to height of the tree)

    r=A(i,j)A(i,j+1)n2rr=A(i,j)n2r2n2A(i,j) \leq \sum_{r=A(i, j)}^{A(i, j+1)} \frac{n}{2^r} \leq \sum_{r=A(i, j)}^{\infty} \frac{n}{2^r} \leq \frac{2n}{2^{A(i, j)}}

    For each nodes from class ii, we can perform at most A(i,j)1A(i, j) - 1 operations while keeping the edge in the same class, since there are at most A(i,j)A(i, j) values in class i1i-1. So for all the nodes in class ii, we can bound the operation by the following summation to move all the nodes from ithi^{th} class to i+1th{i+1}^{th} class.

    j=0(2n2A(i,j)A(i,j)) \sum_{j=0}^{\infty} \Big( \frac{2n}{2^{A(i, j)}} \cdot A(i, j) \Big)

    To move all the edges from class 11 to z+1z+1, we need to perform this operation zz times. Hence all such operations can be bound by the following summation for some constant kk and kk'.

    i=0z(j=02nA(i,j)2A(i,j)) \sum_{i=0}^{z} \Big( \sum_{j=0}^{\infty} \frac{2n \cdot A(i, j)}{2^{A(i, j)}} \Big) =2ni=0zj=0A(i,j)2A(i,j) = 2n \sum_{i=0}^{z} \sum_{j=0}^{\infty} \frac{ A(i, j)}{2^{A(i, j)}} 2ni=0zk \leq 2n \sum_{i=0}^{z} k' =2kzn = 2 \cdot k' \cdot z \cdot n kzn \leq k \cdot z \cdot n

    =O(zn) = O(z \cdot n)
  3. Non final edge belongs to class z+1 (Let’s denote the count as c6c_6)

    The total number of operations we can perform on class z+1z+1 can be bound by bounding the number of elements in class zz.

    The max rank in the tree is bound by log(n)log(n), since the height of the tree can be at most log(n)log(n) by following the weighted union rule.

    Let’s define inverse to the above Ackermann function α(m,n)\alpha(m, n)

    α(m,n)=min{i1A(i,m/n)>log2(n)} \alpha(m,n) = min\{i \geq 1 | A(i, m/n) > log_2(n)\}

    Max rank in the class zz is log(n)log(n). Now if we choose z=α(m,n)z=\alpha(m,n), then we can observe that we can have at most m/nm/n element in the class zz. This is obvious from the way we define the inverse ackermann function. So for nn nodes, we can do at most nmn=mn \cdot \frac{m}{n} = m operations.

Total time, by considering z=α(m,n)z = \alpha(m, n) is (for some constant kk)

=c4+c5+c6 = c_4 + c_5 + c_6

(z+1)m+zn+m \leq (z+1) \cdot m + z \cdot n + m

kmz \leq k \cdot m \cdot z

kmα(m,n) \leq k \cdot m \cdot \alpha(m, n)

Therefore, the total time is bound by O(mα(m,n))O(m \cdot \alpha(m, n)). Hence amortized time bound per operation is O(α(m,n))O(\alpha(m, n)). And since A(3,4)A(3, 4) is such a huge number, as we see from the above Ackermann function calculation table, for all practical purpose α(m,n)3\alpha(m, n) \leq 3.


Reference / Credits

  1. [Research Paper] Efficiency of a Good But Not Linear Set Union Algorithm
  2. [Youtube] A&DS. Time Complexity of Union-Find (inverse Ackermann function)