Notes
Notes
Yadu Vasudev
yadu@[Link]
1 Amortized Analysis 5
1.1 Dynamic arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.1.1 Aggregate analysis . . . . . . . . . . . . . . . . . . . . . . . . 6
1.1.2 Accounting method . . . . . . . . . . . . . . . . . . . . . . . . 7
1.1.3 Potential method . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.1.4 Dynamic arrays with insertions and deletions . . . . . . . 8
2 Randomization 11
2.1 Basic discrete probability . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.2 Random variables and expectation . . . . . . . . . . . . . . . . . . . 13
2.3 Randomized Quicksort . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.3.1 A direct analysis . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.3.2 A tighter bound . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3 Dictionaries 19
3.1 Hash tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.1.1 Hashing with chaining . . . . . . . . . . . . . . . . . . . . . . 20
3.1.2 Universal hash families . . . . . . . . . . . . . . . . . . . . . . 22
3.1.3 Multiplicative hashing . . . . . . . . . . . . . . . . . . . . . . 23
3.1.4 Perfect hashing . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.1.5 Open addressing . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.2 Skip Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.2.1 Analysis of the running time . . . . . . . . . . . . . . . . . . 32
3.3 Binary Search Trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.3.1 Balanced BSTs . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.3.2 AVL trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.3.3 Scapegoat trees . . . . . . . . . . . . . . . . . . . . . . . . . . 42
3.3.4 Randomized BSTs and treaps . . . . . . . . . . . . . . . . . . 45
3.3.5 Splay trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
4 Priority Queues 50
4.1 Binary minheap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
4.2 Min-max heaps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
4.3 Mergeable heaps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
4.3.1 Randomized mergeable heaps . . . . . . . . . . . . . . . . . 55
4.3.2 Skew heaps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
4.3.3 Binomial heaps . . . . . . . . . . . . . . . . . . . . . . . . . . 60
4.3.4 Fibonacci heaps . . . . . . . . . . . . . . . . . . . . . . . . . . 63
3
5 Disjoint sets 68
5.1 List-based implementation . . . . . . . . . . . . . . . . . . . . . . . . 68
5.2 Tree-based implementation . . . . . . . . . . . . . . . . . . . . . . . 69
5.2.1 Union-by-rank . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
5.2.2 Union-by-rank with path compression . . . . . . . . . . . . 71
These notes are collated from multiple sources in an attempt to give a consis-
tent exposition of the material intended for the course. They have not been
proofread and contain technical errors, typographical mistakes, inconsistent
notation and possibly many more such goof-ups. Use these notes to comple-
ment the lectures. I would be grateful if you point out errors in these notes
and give feedback. You can use this Github page for the same.
It is traditional for the author to magnanimously accept the blame for whatever
deficiencies remain. I don’t. Any errors, deficiencies, or problems in this book
are somebody else’s fault, but I would appreciate knowing about them so as to
determine who is to blame.
- Steven Skienna, The Algorithm Design Manual
1 Amortized Analysis
We will use standard arrays for the vector class, but we will resize the array
as the vector grows and shrinks. This will let us obtain an O (1) running
time for at(i), but we will lose out on push_back and pop_back. Instead of
getting O (1)-worst case running time, we will get something weaker. In fact,
a single push_back or pop_back will incur a worst-case O ( n) running time
(where n is the size of the array). Nonetheless, we will show that the sum of
the running times of m push_back operations will only take O ( m). Thus, the
amortized cost of one operation is O (1).
Amortized cost refers to the average cost of a single operation, where the
average is over a worst-case sequence of some m operations. Note that this
is different from the average-case analysis that you might have seen for, say
quicksort, where the input is considered to be randomly ordered.
6 Amortized Analysis
n
X log
Xn
ti = n + 2 j = O ( n).
i =1 j =1
In the accounting method, we try to charge each opertion a cost that may be
potentially higher than the actual cost of the operation. The additional cost
is thought of as some credit that can be used for a later costly operation - in
the case of a dynamic array this would be the resizing operation.
Suppose that at a particular stage in the algorithm the size n is exactly
half of the capacity of the array - this is the case when a new array has been
created. Now, for each insertion, we charge 3 units - one for its insertion, one
unit to be used for a future copy of this element, and one unit for copying
one of the n elements that were copied. Thus the cost of an insertion is 3
units, which is O (1). We will now show that after inserting n elements, we
have enough credit stored such that the copying can be taken care of with
these credits.
Notice that when n more elments are inserted, then the total credits saved
is 2n. Now when a new element is about to be inserted, we will use up these
2n credits for the copying of the 2n elements in the array. Thus the cost of
the insertion is just 3 units for the new element that will be inserted in the
resized array. Hence the amortized cost of any insertion is O (1).
bt i = t i + Φ( i ) − Φ( i − 1),
where t i is the actual cost of the i th insertion (including the resizing). Now,
the goal is to define the potential function Φ( i ) in such a way that during
an insertion that includes a resizing, the potential Φ( i ) decreases to a suf-
ficiently small value compared to Φ( i − 1), and consequently bt i is small. In
other words, there is a large amount of stored credit/potential (given by
Φ( i − 1)) that can take care of the high t i value.
8 Amortized Analysis
• Insertion of the i th element does not cause a resizing: In this case we have
Φ( i ) = Φ( i − 1) + 2 since only the size increases by 1 and the capacity
remains unchanged. Thus bt i = t i + Φ( i ) − Φ( i − 1) = 1 + 2 = 3.
• Insertion of the i th element causes a resizing: Let A′ be the old array that
was resized to create the new array A. We have [Link] = 2 · A′ .capacity.
Also since the i th insertion triggered a resizing, we must have A′ .size =
A′ .capacity. Thus we have
One way would be to double the size of the array as before during inser-
tions. During deletions, if the size is at most one-fourth of the total capacity,
we will resize it so that the capacity is double of the size. This way we will
always maintain the following invariant across insertions and deletions.
1
[Link] ≤ [Link] ≤ [Link].
4
[Link]
The fraction ℓ(A) defined as [Link] is defined as the load on the array A.
The invariant says that the load is always at least 1/4.
We will analyze this using both the accounting method and the potential
method. In the accounting method, for every insertion of an element into
an array when the size is at least half of the capacity, we will add extra 2
units of credit to be used for copying just like in the case of insertion-only
arrays. For the case when 14 [Link] ≤ [Link] ≤ 12 [Link], we will only
charge for the insertion operation. Similarly, when we delete an element and
1 1
4 [Link] ≤ [Link] ≤ 2 [Link], we will charge an extra 1 unit of credit
to be used up while resizing the array when 14 [Link] ≥ [Link]. Thus the
amortized cost is O (1) and the cost of resizing is taken care of by the extra
credits given.
If we were to define a suitable potential function Φ, we will try to make
sure that right after a resizing the potential should be zero. Similarly, when
[Link] = [Link], the Φ( i ) should be [Link]. Similarly, whenever [Link] =
1
4 [Link], the potenital Φ( i ) should again be [Link] since we need it to
copy all the elements to the resized array. With this in mind, we can define
our potential corresponding to the array A at some point i in time as follows:
(
2 · [Link] − [Link] ℓ(A) ≥ 1/2
Φ( i ) =
1
2 · [Link] − [Link] ℓ(A) < 1/2.
Clearly Φ( i ) ≥ 0 for all i . Observe that both the conditions stated earlier
are satisfied by this definition of the potential function. Analogous to the
case of insert, we have multiple cases to verify now for both insertion and
deletion. Let Ai denote the array afte the i th operation.
• Insert operation when ℓ(Ai ) = 1: This means that the array is full and the
insert operation causes a resizing. Thus, we have the following:
t i +1 = Ai .size + 1
Φ( i ) = Ai .size
Φ( i + 1) = 2(Ai .size + 1) − 2 · Ai .capacity.
t i +1 = 1,
Φ( i ) = 2 · Ai .size − Ai .capacity,
Φi +1 = 2 · (Ai .size + 1) − Ai .capacity
10 Amortized Analysis
Thus bt i +1 = 3.
• Insert operation when ℓ(Ai ) < 1/2, but ℓ(Ai +1 ) ≥ 1/2: In this case, we
again have
t i +1 = 1,
1
Φ( i ) = · Ai .capacity − Ai .size,
2
Φ( i + 1) = 2 · (Ai .size + 1) − Ai .capacity
1
bt i +1 = 1 + 2 · (Ai .size + 1) − Ai .capacity − · Ai .capacity + Ai .size
2
3
= 3 + 3 · Ai .size − · Ai .capacity
2
< 3, since ℓ(Ai ) < 1/2.
t i +1 = 1,
1
Φ( i ) = · Ai .capacity − Ai .size,
2
1
Φ( i + 1) = · Ai .capacity − Ai .size − 1.
2
Thus bt i +1 = t i +1 + Φ( i + 1) − Φ( i ) = 0.
We will recall basic discrete probability, the notion of random variables and
expectation that will be required in the probabilistic analysis later on. The
basic object of interest in probability is a probability space P that consists
of a tuple (Ω, Pr) where Ω is the sample space that is discrete in our case,
and a probability function Pr : Ω → [0, 1] that satisfies the property that
ω∈Ω Pr[ω] = 1.
P
Pr[ E1 ∧ E2 ]
Pr[ E1 |E2 ] = .
Pr[ E2 ]
E2 = {( i, j ) | 4 ≤ i ≤ 6, 1 ≤ j ≤ 6}.
Hence, we have
We could also calculate Pr[ E1 ] using the fact E1 can be thought of as fol-
lows: Roll two dice D1 and D2 independently. For D1 , A1 is the event of D1
turning up with an even number, and A2 is the event of D2 turning up with
an even number. Now, Pr[ E1 ] = Pr[A1 ∧ A2 ]. Sinc the events A1 and A2 are
independent, we have Pr[A1 ∧ A2 ] = Pr[A1 ] Pr[A2 ] = 63 · 36 = 14 .
Two events E1 and E2 are said to be disjoint if E1 ∩ E2 = ;. I.e. the sets
that correspond to the events do not intersect. While analyzing the probabil-
ity of random experiments, we will often need to look at various events, their
unions and intersections. Here are a few useful identities.
• Disjoint union: If the events Ei are pairwise disjoint, i.e. Ei ∩ E j = ; for all
i ̸= j , then it follows from the inclusion-exclusion principle that
k
k
_ X
Pr Ei = Pr [ Ei ] .
i =1 i =1
where the sum is over the range of the random variable X . Here are a few
properties of expectation that will useful in analyzing random variables.
write
X
E[ X ] = i · Pr[ X = i ],
i≥1
X
= Pr[ X ≥ i ].
i≥0
• Linearity of expectation: Let X 1 and X 2 be two random variables. Then, Perhaps the most important identity
about expectation that you should keep
in mind. This straightforward identity is
E[ X 1 + X 2 ] = E[ X 1 ] + E[ X 2 ].
extremely useful.
This identity holds for any two random variables, not necessarily indepen-
dent.
Consider the following simple game between two players R and M. They
have an unbiased coin. R tosses the coin and if it is heads then R gets |1.
Let us calculate the expected amount that R wins if they play the game for
100 rounds. Here the sample space Ω is set of all sequence of length 100
consisting of the characters H and T . The random variable X that captures
the amount that R wins his a function from this Ω to the set {1, 2, . . . , 100}.
We are interested in calculating E[ X ].
To compute E[ X ], we can decompose X as a sum of indicator random
variables and use the linearity of expectation. Let I j denote the indicator
random variable that is 1 if the j th toss of the coin returns a head. From
P100
before, we know that E[ I j ] = 1/2. The random variable X = j =1 I j . By
linearity of expectation,
100
X
E[ X ] = E[ I j ] = 50.
j =1
The partitioning using the pivot can be performed in O ( n)-time using the
Partition algorithm given as Algorithm 2.1.
The quicksort algorithm recursively sorts the arrays on either side of the
position returned by Partition. The running time of this algorithm depends
on the choice of the pivot. If the element in position high partitions the array
A into almost similar-sized sub-arrays, then quicksort finishes the sorting
quickly. On the other hand, if the partition induced by high is skewed, then
the algorithm will perform poorly. The running time of the algorithm is
proportional to the number of comparisons performed by Partition. Let
T ( n) denote the number of comparisons performed by Quicksort on an
array of size n. We can express T ( n) as ...with base case T (0) = 1.
T ( n) ≤ max {T ( i − 1) + T ( n − i ) + O ( n)}.
i∈{1,n−1}
fixed. It could vary from Θ ( n log n) if the random choices that the algorithm
made were all very good, to Θ ( n2 ) if the random choices all gave skewed
partitions. Thus the running time of the algorithm is a random variable
whose distribution is determined by the random choices of the algorithm.
Analyzing the algorithm amounts to analyzing this random variable under
the worst-case input.
Remember that the random choices are inherent to the algorithm. We
do not make any assumption on the distribution that generates the input.
Thus the analysis of a randomized algorithm is a worst-case analysis. We
will argue that for every input the expected running time is small. The ex-
pectation or average here is on the random choices of the algorithm, not on
any assumption on the input. For the randomized version of quicksort,
we will denote by T ( n), the expected running time of the algorithm under
worst-case inputs.
To understand the intuition behind why a random choice of the pivot can
make the algorithm perform better, let us understand when the choice of
the pivot is good. Let A = ( a1 , a2 , . . . , an ) be the input array, and let B =
( b1 , b2 , . . . , bn ) denote the sorted version of A. Suppose that the pivot chosen
by the algorithm partitions the array A into two parts each such that the
smallest part has size at least n/10, and the largest has size at most 9n/10.
If this holds, then the recurrence of the running time becomes
Since the pivot is chosen uniformly at random, the probability that the i th
smallest element in the array is chosen is 1/ n. If the pivot chosen is the i th
smallest, then the subproblems have size n and n − i .
The running time T ( n) is a random variable, and we are interested in the
quantity E[ T ( n)]. To write this expectation, let pi denote the probability
that the random choice of the pivot returns the i th smallest element in the
array and let Ei denote the corresponding event. Then we have
n n
X X 1
E[ T ( n)] = p i E[ T ( n) | E i ] ≤ E[Θ ( n) + T ( i ) + T ( n − i )]
i =1 i =1
n
n
1X
≤ Θ ( n) + (E[ T ( i )] + E[ T ( n − i )])
n i =1
n−1 n−1
2X 2X
E[ T ( n)] = Θ ( n) + E[ T ( i )] ≤ kn + E[ T ( i )].
n i =0 n i =1
and we need to verify that T ( n) ≤ cn log n. To that end, we first look at the
following sum
n−1
X n/2
X n
X
i log i ≤ i log i + i log i
i =1 i =1 n/2
n/2 n n/ 2−1
nX X X
≤ log i + log n i− i
2 i =1 i =1 i =1
n2 n 3n2
≤ log + log n
8 2 8
n2 n2
≤ log n − .
2 8
Substituting this in the recurrence, we have
2 cn2 cn2
E[ T ( n)] ≤ kn + log n −
n 2 8
cn
≤ kn + cn log n −
4
≤ cn log n, when c > 4k.
We will now see a different analysis of the running time of quicksort that
uses the properties of random variables that we saw earlier. Recall that the
number of comparisons performed by quicksort (which is proportional to the
running time of the algorithm) is a random variable - let’s denote it by X . We
are interested in computing E[ X ].
Instead of analyzing X directly, we will look at indicator random variables
X i j (for i < j ) that is set to 1 if bi and b j are compared at any point in the
run of randomized quicksort. We can express the random variable X as
follows:
X
X= Xi j.
i< j
18 Randomization
2
E[ X i j ] = Pr[ X i j = 1] = .
j−i+1
The first term in the RHS is the harmonic sum and is equal to ln n + Θ (1).
This gives the value of the expectation as
E[ X ] = 2n ln n + Θ ( n).
3 Dictionaries
• Delete( k ) - delete the pair with the key k from the dictionary.
We will look at data structures that can support all the operations of a
Dictionary as efficiently as possible. We will see how randomization can
be used to give implementations that perform well on the average (over
the randomness of the algorithm) for worst-case inputs. We will see binary
search trees with good amortized bounds as well.
A hash table consists of an array Arr of size t that is indexed using the key
values. We have a function hash such that hash( k ) returns a number be-
tween 1 and t . The key is then inserted into the location Arr[hash( k )]. Typ-
ically the universe U from which the keys are obtained is much larger than
the size of the array t . Thus, irrespective of the choice of the hash func-
tion, there will exist at least (in fact, many) two keys k1 and k2 such that
hash( k1 ) = hash( k2 ). This is known as a collision. A good design should
choose the hash function carefully, and take care of the collisions in an effec-
tive manner.
We will look at how to take care of collisions, and the choice of hash
functions. We start with a simple method of handling collisions, known as
chaining.
20 Dictionaries
• Search( k ): Compute h( k ). Scan the linked list pointed to from Arr[h( k )].
If k is found, return the corresponding value v .
We would like all the operations to be
Notice that the running time of all three operations depend on the length O (1), but this would be impossible to
achieve in the worst-case. We will settle
of the linked list. The length of the list depends on the choice of the hash for something weaker, but practically
function. very good.
Typically the universe U of keys is such that that |U | ≫ t , and hence
irrespective of the choice of the hash functions, there will be multiple keys in
the universe that map to the same hash value. Even though |U | is large, the
actual subset of the universe that will be hashed is a number n = Θ ( t ). The
only problem is that we do not know the n elements in the universe that will
be hashed.
If we were to choose the hash function h : U → [ t ] beforehand, it is
always possible to adversarially choose n elements such that there are a large
number of collisions. If the choice of the n elements are adversarial, we will
need randomness in the choice of the hash function to achieve good bounds
on the running time of inserts, searches, and deletes.
One ideal scenario is that the hash function h is chosen uniformly at ran-
dom from the set of all functions from U to [ t ]. This is not practical, since
there are |t||U | many functions, and storing any such function will require
|U | log t space. Instead, we could have just maintained an array of size |U |, Consider the case where the universe
U is the set of all IP addresses. Here
and used the trivial hash function h( x ) = x . This would avoid all collisions,
the universe has size 2128 , whereas a
at the cost of using space proportional to the size of the universe |U |. data structure that is used to store the
Nonetheless, let us consider the case of a random hash function as a IPs that access a server is considerable
smaller than this number.
warm-up. To set it up formally, we have a set H = {h : U → [ t ]} (the set
of all functions). A random hash function is obtained by choosing a func-
tion uniformly at random from H . Thus, the probability that a particular
function h is obtained is 1/|H |.
Another way to view is that for each x ∈ U , the value of h( x ) is chosen
uniformly at random from the set [ t ]. Thus, for a random hash function we
can say the following.
1
Pr [h( x ) = i ] = .
h∈ r H t
Suppose we have a set S with n elements that we want to hash into the
table of size t , then we can bound the search time for any x ∈ U as follows.
21 Dictionaries
Since the hash function is random, the search time is a random variable, and
we will look at the expected cost of the search operation. Observe that the
insert and delete operations take asymptotically the same time.
Let x ∈ U be any elements. For every y ∈ S , the probability that h( x ) =
h( y ) can be bounded as follows.
X
Pr [h( x ) = h( y )] = Pr[h( x ) = i ∧ h( y ) = i ]
h∈ r H
i∈[ t ]
Now, the search time for the element x ∈ U is the random variable I =
y∈S I y . Using the linearity of expectations, we can say that E[ I ] = |S|/ t .
P
Before we go into discussing about hash families that are not fully random,
we will look at how large the hash table should be so that the number of
collisions for any element is only O (1) in the worst-case when a purely ran-
dom hash function is used. The discussion in the last section showed that is
t = O (|S|), the expected search time is Θ (1).
For a set S ⊆ U , a purely random hash function h mapping S to [ t ] can
be thought as a m = |S| balls being thrown uniformly, and independently at
random into t bins. We are interested computing the probability that no bin
has more than one ball in it. In other words, every position of the hash table
has at most one element of the universe hashed into it.
Let Ei be the event that the i th element in S did not cause a collision given
that the first i − 1 elements did not cause collision. We are interested in
giving an upper bound on the event E = E 1 ∪ E 2 ∪ · · · ∪ E m . The event E
covers the case that there is at least one collision when hashing m elements
into a hash table of size t . By the union bound that we saw earlier, we can
write the probability for E as
m
X
Pr[ E ] ≤ Pr[ E i ].
i =1
22 Dictionaries
Since the first i − 1 elements did not cause collision, they all hashed into
different position on the table. For i to not cause a collision, i should be
hashed to a position different from these i − 1 positions. Since there are t
positions on the hash table, this probability can be expressed as
i −1
Pr[ E i ] = .
t
Putting all this together, we have
m m−1
X i −1 1X
Pr[ E ] ≤ = i
i =1
t t i =1
m( m − 1) m2
= ≤
2t 2t
If t = m2 , the Pr[ E ] is small (< 1/2). Hence, w.h.p there are no collisions
and the worst-case search time is O (1).
This bound of t = Θ ( m2 ) is in fact tight in the sense that if t is smaller
than m2 , then with probability at least 1/2, two elements in S will hash into
the same position in the table. Let’s see why this is the case. As earlier, we This is known as the birthday problem.
will look at the event Ei that the i th element does not cause a collision given
that the first i − 1 elements did not cause a collision. Thus, the probability
of the event E ′ that every elements is hashed into a separate position in the
hash table is bounded by
1 2 m−1
Pr[ E ′ ] = 1 − 1− ··· 1−
t t t
Using the bound that 1 − x ≤ e−x , we can upper bound this probability as
Thus, if t ≤ m2 /4, then Pr[ E ′ ] ≤ e−2 < 1/2. Hence for t < m2 /4, with
probability at least 1/2, there are at least two elements that hash into the
same position on the hash table.
While random hash functions behave well in expectation, we saw how they
are impractical. We need to trade-off on true randomness for practicality,
yet try to achieve similar bounds on the running time for Insert, Search,
and Delete. One of the requirements for good bounds on the running time
of these operations is what is defined using the notion of universal hash
families.1 1
J Lawrence Carter and Mark N Weg-
man. “Universal classes of hash func-
tions”. In: Journal of Computer and
Definition 3.1 (Universal hash families). A set H of functions from U to [ t ] System Sciences 18.2 (1979), pp. 143–
is said to a universal hash family if for every x ̸= y ∈ U , 154.
1
Pr [h( x ) = h( y )] ≤
h∈ r H t
23 Dictionaries
A few remarks are in order here. Note that the set of all functions from
U to [ t ] is universal. The universality follows from the property that for
a random function sampled from the set of all function, the image for any
element x ∈ U is equally likely to be any number in [ t ]. This may not There is a notion of strong universal-
ity that also implies the uniformity
be true for every universal hash family. Uniformity on its own is also not
property. Many of the constructions of
very useful for hashing. Consider the family T to be the set of all constant universal hash families also give strong
functions - i.e. T = {hi | i ∈ [ t ]}, where hi ( x ) = i for every x ∈ U . You can universility.
1
Pr [h( x ) = i ] = .
h∈ r H t
Exercise 3.1. Verify that the expected running-time for Search, Insert, and
Delete is Θ (1) when the hash function is sampled from a universal hash
family.
We will now see a family of universal hash functions that can be described
succinctly, and computed efficiently. This family requires choosing a large
prime number, larger than the size of the universe U . We will then see a
slight variant of this idea that avoids the need of a large prime number but
only gives near-universality. It will not be hard to verify that this notion of
near-universality (that we will define later) is sufficient for the running-time
bounds that we need.
Let p be a prime number such that p > |U |. The family of hash functions is
defined using two parameters, a ∈ {1, . . . , p − 1} and b ∈ {0, 1, . . . , p − 1} as
follows:
We will the following lemma about prime numbers to prove the theorem.
Lemma 3.3. Let p be a prime number. For every a ∈ {1, . . . , p − 1}, there exists a
unique x ∈ {1, . . . , p − 1} such that a x ≡ 1(mod p ).
Proof. First, we will show that for a ∈ {1, 2, . . . , p − 1}, there does not exist an
x ∈ {1, 2, . . . , p − 1} such that a x ≡ 0(mod p ). This is because if p divides ax ,
then p must divide either a or x since p is a prime. But, this is not possible
since a, x < p.
Now, we will show that for two different values x ̸= x ′ ∈ {1, 2, . . . , p − 1},
we cannot have a x ≡ a x ′ (mod p ). Together with the first statement we
proved, this means that the set {a x (mod p ) | x ∈ {1, 2, . . . , p − 1}} has p − 1
elements, and hence there is a unique x such that ax ≡ 1(mod p ).
Assume that a x ≡ a x ′ (mod p ). Then, we have a ( x − x ′ ) ≡ 0(mod p ).
Once again this means that p must divide a or x − x ′ . Since a < p, it is
impossible that p divides a. Since 1 ≤ x, x ′ ≤ p − 1, |x − x ′ | < p − 2 and hence
p does not divide x − x ′ . Thus, it must be the case that x = x ′ .
a x + b ≡ r (mod p )
a y + b ≡ s (mod p ).
Using Lemma 3.3, we can say that this system has a unique solution for a
and b, given by a = ( r − s )( x − y )−1 and b = ( r y − xs )( y − x )−1 . Thus, we
can say that
1
Pr [( ax + b ) mod p = r ∧ ( a y + b ) mod p = s ] = .
a,b p ( p − 1)
2
Pr [h( x ) = h( y )] ≤ .
h∈ r H t
a x (mod 2w )
ha ( x ) =
2w−ℓ
Theorem 3.5. Let W denote the set of odd integers in {0, 1, . . . , 2w − 1}. For
any x ̸= y ∈ {0, 1, . . . , 2w − 1},
2
Pr [ha ( x ) = ha ( y )] ≤ .
a∈ r W 2ℓ
w
Before we start the proof let’s try to understand the function ha ( x ). Since a
a and x are w-bit numbers, a x is a 2w-bit number. Thus, ax (mod 2w ) gives
the last (least significant) w bits of the product ax . Now, dividing this by x
2w−ℓ gives the first ℓ bits of these w bits.
If x ̸= y are such that ha ( x ) = ha ( y ), then clearly these ℓ bits are ha ( x ) ax
identical. Let’s assume (w.l.o.g) that x < y . Now, depending on whether ℓ w−ℓ
a x (mod 2w ) ≤ a y (mod 2w ) or not, the first ℓ bits of the least significant Figure 3.1: Computing ha ( x ) for an
odd a ∈ {0, 1, . . . , 2w − 1}.
w bits of a ( x − y )(mod 2w ) are either 1ℓ or 0ℓ , respectively. Therefore, if
ha ( x ) = ha ( y ), then ha ( x − y ) = 0 or ha ( x − y ) = 2ℓ − 1 = t − 1. Hence we
have
To proceed further and analyze the R.H.S of this equation, we need the
following lemma.
Lemma 3.6. Let x, z ∈ W be any two integers. There exists exactly one a ∈ W
such that ax (mod 2w ) = z .
1
Pr [ a x (mod 2w ) = z ] = .
a∈ r W 2w−1
Proof of Theorem 3.5. Let x ̸= y and assume (w.l.o.g) that x < y . Thus,
x − y (mod 2w ) = q2 r , where q is an odd integer. From Lemma 3.6, we know
that Pra∈r W [ aq (mod 2w ) = z ] = 1/2w−1 for every z ∈ W . Thus, we can think
of aq (mod 2w ) as a w-bit number whose last bit is 1 and the first w − 1 bits
are chosen uniformly at random. Hence aq2 r (mod 2w ) (which is equivalent
to aq (mod 2w ) · 2 r (mod 2w )) has the last r bits all 0, the next bit a 1, and the
remaining w − r − 1 bits chosen uniformly at random. Now, when we look
at ha ( x − y ), we need to look at the first ℓ bits of this w-bit number. We will
look at different cases depending on the relative sizes of r and ℓ.
Pr [ha ( x − y ) = 0] = Pr [ha ( x − y ) = t − 1] = 0.
a∈W a∈ r W
1 1
Pr [ha ( x − y ) = 0] = Pr [ha ( x − y ) = t − 1] = = .
a∈W a∈ r W 2ℓ t
Pr [ha ( x − y ) = 0] = 0
a∈W
1 2 2
Pr [ha ( x − y ) = t − 1] = = = .
a∈ r W 2ℓ−1 2 ℓ t
Lemma 3.7. Suppose that we hash a set S of size n into a hash table of size t
using a universal hash family H , then with probability at least 1/2, the total
number of collisions across all the positions in the table is at most O ( n2 / t ).
1
Pr [h( i ) = h( j )] ≤ .
h∈ r H t
Let X i j denote the indicator random variable that 1 if i and j collide when h
is sampled uniformly at random from H . Thus the total number of collisions
P
is given by the random variable X = i̸= j X i j . Therefore we have
n 1 n2
X
E[ X ] = E[ X i j ] ≤ ≤ .
2 t 2t
i̸= j
sampling if the total number of collisions while hashing the set S is more
than n. Since the probability of finding an h with collisions at most n is at
least 1/2, we can find such an h in O (1)-time w.h.p. For each i ∈ [ t ], let ni be
the number of elements x ∈ S such that h( x ) = i . From the choice of h we
can say that
t
X n i
≤ n.
i =1
2
Now, for each i , we choose hash function hi from a universal hash family
that maps these ni elements into a hash table of size n2i . From the earlier
calculation, we know that with probability at least 1/2, a random hash func-
tion from a universal family will satisfy this property. Thus, for each i , we
can find such a hash function in time O (1). We use h as the primary hash
function, and use hi as the secondary hash function to take care of collisions
in the primary table. The collisions in hi s can be taken care of using linked
lists. Once the hash functions are chosen and S stored, we can answer each
query of “Is x ∈ S ?” in O (1) worst-case time. Furthermore, the total space
taken (outside the space for storing the hash functions) is at most
t t
X X n i
n+ n2i ≤ n + 4 = Θ ( n).
i =1 i =1
2
• A value ⊥ that denotes that the location in the hash table is empty.
29 Dictionaries
The size of the hash table will be such that at least half the positions will
be ⊥. Thus, we maintain an additional counter of the number of elements
present in the table as well as the number of tombstone ⊤. Once the num-
ber of ⊥ drops below half the total table size, the entire contents of the hash
tbale (except the tombstones) are rehashed into a new table. The ADT opera-
tions are performed as follows.
The running time of all three operations depend on the time for searching
a key k in the hash table. The cost of rehashing will not be considered for
now. Even though the cost of one rehashing could be as high as O ( n) if there
are n elements present in the table, this is not performed often and hence the
amortized cost per operation is small. We will look at amortized analysis at
a later stage, and for now assume that the total number of elements inserted
into the hash table across its entire history is less than half of the table size t .
To analyze the running time, we will define the notion of a run. A run
is a maximal contiguous sequence of positions in the hash table that are
all occupied by elements of U or by tombstones ⊤. If we are searching for
x ∈ U and h( x ) is part of a run of length k, then the running time for the
search could be O ( k ). The following lemma shows that if the hash table size
is large enough, then there are unlikely to be long runs.
Lemma 3.8. Let n be the total number of elements that are inserted into a hash
table of size t = 2n. For any i , the probability that there is a run of lenght k
starting at i is at most c k for c < 1.
nn kk
v
1 t n
pi,k ≤ p ( t − k )n−k
k
2π k ( n − k ) k ( n − k ) n−k tn
1 2n − k n−k
v
1 t
n
pi,k ≤p
2π k ( n − k ) 2n n − k
v n−k
1 t n 1 2n − k
=p
2π k ( n − k ) 2k 2( n − k )
v n−k
1 t n 1 k
=p 1+
2π k ( n − k ) 2k 2( n − k )
We can now bound the running time for a search operation for a key x .
Let i = h( x ) be the position on the hash table mapped by h for x . If there is
a run containing i of length k, then the cost of the search operations is O ( k ).
We will bound the expected value of k.
Lemma 3.9. Let x ∈ U be any element in the universe. Let r x be the length of
the run containing h( x ) in the hash table of size t . Then E[ r x ] = O (1).
We will now look at another data structure for dictionaries that has the
additional property that the data is stored as an ordered lists. Even though See the Wikipedia page for various
applications skips lists have been used
this can also be achieved using balanced BSTs (which we will see later), skip
for.
lists are much more simpler to implement and maintain. They have poorer
31 Dictionaries
[Link][ i ] points to the next node in L i . The height of the skip list is the maxi-
mum height among all the nodes in the skip list. The head node of the skip
list has no key value, but has a pointer to the first node of each of the lists
L0 , L1 , . . . , Lh , where h is the height of the skip list. Before we talk about how
the lists L1 , L2 , . . . , L r are created, we will describe the method to implement
Search, Insert and Delete.
The search starts from the head node at level equal to the height of
the skip list. If the next pointer points to a value less than the key that is
searched, we take that pointer to move forward. Otherwise, we moved down
to the list below it. When the outer while-loop exits, then we are in L0 ,
and the key of the node that we are in is the largest k′ in the skip list such
that k′ < k. So, we check if the next element in L0 is the key k or not. The
pseudo-code is given as Algorithm 3.
Notice that the running time depends both on the number of pointers that
we have to follow towards the key value, as well as the height of the skip list.
So we want to have a situation where the height is not too large, and we can
manage to skip a lot of nodes in one go at higher levels. Deterministically
deciding the height of the various nodes would make insetion very expensive
because if we insert in the middle we might have to change the levels of a lot
other nodes as well.
To describe the procedure to insert an element into the skip list, we will
assume that there is a function getHeight() that returns a number. We will
not worry about how this function generates the height for now. The idea
for insertion is similar to search wherein we keep moving horizontally (fol-
lowing the next pointer in the same list) or vertically (going to a list at the
32 Dictionaries
lower level) while keeping track of the nodes that we are visiting during the
process. Once we reach the key largest key k′ < k, where k is the key we
are inserting, we will use the getHeight function to obtain the height ℓ of the
node for key k, and insert k into the lists L0 , L1 , . . . , Lℓ while backtracking
through the nodes we visited while searching.
The bounds on the running time will depend on the way the various lists
are arranged. As we said earlier, a naive deterministic method of creating
the lists can lead to potentially bad running times for insertion and deletion.
We will turn to randomization for this.
ber of heads that turn up before the first tails. This gives us the following
statement about the height of any element in the skip list.
Lemma 3.10. Let k be any key and hk be the height of the key in the skip list.
Then, E[hk ] = 2.
Proof. Let E be the event that the first toss of the coin is a heads, then we
can write the expectation of hk as follows.
Therefore, E[hk ] = 2.
We will now bound the number of nodes at any level of the skip list.
n
Lemma 3.11. For any r ≥ 0, E[|L r |] = 2r where n is the number of elements in
the skip list.
Proof. Let X i denote the indicator random variable that is 1 when the ele-
ment i in the list L0 is present in L r . The expectation E[ X i ] = Pr[ X i = 1] =
1/2 r .
Pn
Now, |L r | = i =1 X i and hence E[|L r |] = n/2 r .
While the expected heigh of individual elements is Θ (1), the height of the
skip list is Θ (log n). We show that next.
Lemma 3.12. If h is the height of a skip list with n elements, then E[h] ≤
log n + 2.
Proof. Let X r denote the indicator random variable that is 1 when |L r | > 0.
P
We can say that h = r≥0 X r .
Pn
Clearly, Pr[|L r | > 0] ≤ 1. Since E[|L r |] = i =1 i · Pr[|L r | = i ], we have
Pr[|L r | > 0] ≤ E[|L r |] = n/2 r . Thus, E[ X r ] = Pr[|L r | > 0] ≤ min{1, n/2 r }.
Therefore, we have
log n−1
X X n
E[h] ≤ 1+ ≤ log n + 2
r =0
2r
r≥log n
Exercise 3.4. If h is the height of the skip list, show that Pr[h ≥ 2 log n] ≤
1 / n2 .
We will now bound the running time for searching an element in the skip
list. Note that this is also the bound for inserting into a skip list and deleting
an element from the skip list.
Lemma 3.13. The expected search time for any element in a skip list with n
elements is at most 2 log n + O (1).
34 Dictionaries
Proof. Suppose that the height of the skip list is h. The search starts at the
head node at height h. It then moves either right (if the next element at that
level is smaller than the key that we are searching for) or down. The search
ends in L0 at the largest element k′ such that k′ < k. We will analyze this
search path from k′ going towards the head node in Lh .
If the node with key k′ has height ℓ, then the last step in the search would
have been a down move. In particular, if the search reaches a node at level Verify this by looking at the pseudocode
for Search
i , and the height of the node is greater than i , then the previous step in the
Search procedure would have been down move. Alternately, if a node u has
height ℓ, then the Search must first enter the node at Lℓ .
Since each node in level L i moves to L i +1 with probability 1/2, we can
think of this reverse path as a random walk starting from k′ , where with
probability 1/2, the walk moves up a step, and with probability 1/2 the
walk moves left a step. Thus the expected search time is upper bounded by
the expected number of steps by the random walk to reach the head node at
Lh .
For a level i , let Ti be the number of steps in the reverse path that stays in
L i before it moves up. We can easily see that E[ Ti ] = 2 for every i . Hence,
for the expected number of steps for the reverse path to reach a node at
height ℓ is 2ℓ. Since the expected height of the skip list is log n + 2, the
expected number of steps to reach a node at maximum height is at most
2 log n + 4. At this point, the reverse path must move left until it reaches the
head node. The expected number of nodes at the maximum neight is at most
1, and this bounds the expected length of the reverse path (and hence of the
search path) to be 2 log n + O (1).
In the last two sections, we dealt with randomized data structures for dic-
tionaries. We will now look at Binary Search Trees (BST) whose operations
are deterministic. We will look at a variant of the BST that has good amor-
tized complexity for the Search, Insert, and Delete. We will start with the
basic definitions of a BST, and then see the BST that achieve good amortized
bounds.
A BST is a binary tree where each node u in the tree has an associated key
[Link], and the tree satisfies the BST property given below.
For every node u with left child uℓ and right child u r , the key values of all the
nodes in the subtree rooted at uℓ is at most [Link], and the key values of all the
nodes in the subtree rooted at u r is at least [Link].
Exercise 3.5. Write down the pseudo-code for the Search, Insert, and Delete
operations on a BST.
Observe that if the height of the BST is h, then all three operations take
O (h) time. In the worst case, a BST with n elements could have height n,
and thus the time for insertion, deletion and searching could be O ( n). This
is the case, if the key values that are inserted are in the ascending order (or
descending order), creating a tree that is just a path.
One way to avoid this behaviour is to keep the binary tree balanced. This
would make the search operations easy, but create overheads for the inser-
tion and deletion operations. There are multiple ways in which balanced
BSTs are maintained, and we will look at one specific example of a balanced
BST.
Lemma 3.15. A BST with n keys that is AVL-balanced has height O (log n). A more precise analysis will show that
the height is at most 1.44 log n.
36 Dictionaries
n ≥ nh = 1 + nh−1 + nh−2
Clearly, nh−1 > nh−2 , and hence we have nh ≥ 2nh−2 . Unrolling this . . . ignoring some floors, ceilings and
corner cases.
recurrence, we get that nh ≥ 2i nh−2i . Thus, nh ≥ 2h/2 , and hence h =
O (log n).
Another notion of balance (and one that will be useful for the BST that we
study later) is weight balance that is defined as follows.
. . . α ≤ 1/2 for this definition to make
Definition 3.16 (α-weight balance). A BST T is said to be α-weight balanced sense.
if for every node u ∈ T with left child u1 and right child u2 , n(u1 ) ≥ α · n(u) and
n(u2 ) ≥ α · n(u).
Lemma 3.17. An α-weight balanced BST with n nodes has height at most
log n
log(1/(1−α))
.
Proof. Consider the longest path in the BST T starting from the root u. Since
the tree is α-weight balanced, we have n(u1 ), n(u2 ) ≤ (1 − α) · n(u) where
u1 and u2 are the left and right children of u. At every step of the longest
path from u, the number of nodes in the subtree is at most (1 − α) of what
was present before it. Thus if we are at a node u′ after i steps in this path,
then n(u′ ) ≤ (1 − α)i n(u). Thus, at the final step, we have 1 ≤ (1 − α)h n(u).
log n
Therefore, h ≤ log(1/(1−α)) .
As mentioned above an AVL tree is a binary search tree that maintain the
AVL height balance (Definition 3.14) after every operation. From Lemma 3.15,
we know that this will ensure that the search operation will take O (log n)
time in the worst-case. We will first see how an insertion can be performed
on an AVL tree.
37 Dictionaries
Insertions
Suppose that we have an AVL tree T and we insert an element x . Each node We will interchageably use x to denote
both the key and value for now.
in the AVL tree can maintain an additional variable to store the height of
the subtree rooted at that node. The first phase of the insertion is similar to
what we do for a normal BST. We will search the BST to find the leaf node
where x is going to be inserted. After insertion, we will retrace the path
while updating the height variable of each node in the path from x to the
root. The only nodes that may possibly have a violation of the AVL balance
condition are those on the path from x t o the root. Hence, if there are no
violations on any of these nodes, then the insertion stops at this point.
4
50
3
50 3 25 75 1
2 25 75 1 2 12 37 0 90 0
1 12 37 0 90 0 1 7
0 7 0 5
Figure 3.2: Insertion of element 5 into
an AVL tree. The number inside the
The more interesting case is when there is a node u in the path from x to node is the key and the number next to
the root such that the absolute value of the difference between the height the node (in red) is the height. After
the insertion of 10, the nodes 12, 25,
of its two children is more than 1. Figure 3.2 shows how the height balance
and 50 all violate the height balance
condition is violated at multiple nodes on a path after an insertion. condition.
Let u be the first node that violates the balance condition and let v and w
be its two children with v being the root of the subtree where x is inserted.
Furthermore, let z be the child of v in whose subtree x was inserted. What
we know is that |h( v ) − h( w)| > 1. The AVL tree rebalances itself by doing
certain “rotations” of the nodes of the trees. The number and type of rota-
tions that it performs depends on whether v and z are left/right childs of
their parents. We will look at four cases, two of which are symmetric to the u
other two. We will call these cases as zig-zig, zig-zag, zag-zig, and zag-zag.
For instance, in the example in Figure 3.2, the insertion creates an instance v w
of zig-zig: The first node u where the height is not balanced is the node
12, and the node v where the new node is inserted is its left child, and the z y
• zig-zig case: Figure 3.3 describes this case where the node that is inserted
z u
is denoted by x .
In this case v is the left child of u and x is in the left subtree of v , rooted y w
x
at z . In this case, we perform a rotation of the edge connecting v with the
root u. Thus, we have v connected to the parent of u. The node u is now
the right child of u and w is the right child of u. The right subtree of v in Figure 3.3: The zig-zig rotation per-
formed when an element x is inserted
the original tree now becomes the left subtree of u, and the subtree rooted into an AVL tree that causes a violation
of the height balance condition. The
first node where the height balance is
violated is the node u. The rotation is
performed about the red edge in the
figure.
38 Dictionaries
3
50
2 25 75 1
1 7 37 0 90 0
0 5 12 0
Figure 3.4: Rotation after insertion of
5 from the example in Figure 3.2. Now
The rotation that is performed on the edge connecting u and v can be all the nodes are height balanced.
done by changing the pointers of the nodes associated with the tree.
Observe that these pointer operations can be performed in O (1) time. The
corresponding operations when z is the right child of v and v is the right
child of u is the zag-zag case, and you can verify that it is symmetric to
the case described above. The following exercise will convince you that
these operations take O (1)-time.
Let us now prove that this one zig-zig rotation brings back the height
balance for the entireBST.
Claim 3.18. If the insertion of x creates a zig-zig rotation, then the tree T is
height balanced after the rotation.
Proof. Let h(·) denote the height of a node before the insertion and h′ (·)
denote the height of a node after the insertion of x . We will denote the
root of the right subtree of v before the rotation (refer to Figure 3.3) by
y . Since the insertion of x created a zig-zig rotation, the height of v must
have increased after the insertion. Therefore, h′ ( v ) = h( v ) + 1 since the
height can increase by at most 1 after an insertion.
This insertion created a height inbalance in the node u. Therefore, it must
have been the case that h( v ) = h( w) + 1 since otherwise even if the height
of v increases after the insertion, the absolute value of the difference
would have remained at most 1. Also, h(u) = h( v ) + 1 = h( w) + 2.
Let us now compare h(z ) and h( y ). If h(z ) = h( y ) − 1, then h( v ) =
h( y ) + 1. Therefore, even if h′ (z ) = h(z ) + 1, the height h′ ( v ) would
have remained unchanged as h( y ) + 1. Also, if h(z ) = h( y ) + 1, then
h′ (z ) = h(z ) + 1 would create a height inbalance at node y . But, we know
that u is the first node in the path from x to the root that has a height
inbalance. Therefore, it must be the case that h(z ) = h( y ). Combined
with the observation in the previous paragraph, this means that h(z ) =
h( y ) = h( w) = h( v ) − 1 and h(u) = h( v ) + 1.
39 Dictionaries
The analysis shows that in the zig-zig/zag-zag case, the tree can be rebal-
anced with just one rotation. Hence the time of insertion in this case is
O (log n).
• zig-zag case: Figure 3.5 shows the zig-zag case where x is the new node
that is inserted.
u u
v w y w
z y v y2
y1 y2 z y1
x x
v u
z y1 y2 w
x
Figure 3.5: The insertion of the ele-
ment x creates a height inbalance and u
There are two rotations that we perform in the zig-zag/zag-zig case. Since is the first node in the path to the root
each operation takes O (1) time, and the search takes O (log n) time, we that violates the height balance. This is
corrected by performing two rotations
can say that the insertion takes O (log n) time, provided we can show that as shown here. The red edge is the edge
the tree is height-balanced at each node. The proof is analogous to the about which the rotation is performed.
Claim 3.19. If the insertion of x creates a zig-zag case, then the height is
rebalanced after performing the two rotations described above. Try proving this fact before reading the
proof.
Proof. As before, assume that h(·) is the height before the insertion of
x and h′ (·) is the height after the insertion of x and the two rotations.
Since u is the first node that violates the balance condition, it must be
the case that h( v ) = h( w) + 1 and that the insertion of x increased the
40 Dictionaries
Now, let us look at the tree after the completion of the two rotations.
From the observations above, we can see that h( y2 ) = h( w) − 1 and hence
h′ (u) = h( w) + 1. Therefore, u is now height balanced. Similarly, h(z ) =
h( y1 ) + 1 = h′ ( y1 ), and therefore v is height balanced and h′ ( v ) = h(z ) +
1. Since h(z ) = h( w), we can conclude that h′ ( v ) = h′ (u) and hence y
is also height balanced. Finally, h′ ( y ) = h′ ( v ) + 1 = h( w) + 2 = h(u).
Therfore, the subtree rooted at y after the insertion and rotations has the
same height as the tree rooted at u before the insertion of x . Hence the
final tree is also height balanced at all the other nodes.
Exercise 3.7. Describe the insertion algorithm for the zag-zag and zag-zig
cases, and verify that the rotations create a height balanced tree.
Deletions
Suppose that a node x is deleted from an AVL tree. We will first perform the
same deletion algorithm as in the case of a normal binary search tree. Let
u be the first node in the path containing x that violates the height balance
property. Observe that u could be a descendent of x since we replace x
with its inorder successor when x has both its children present. So it is
possible that one of the ancestors of the inorder successor is the node u -
see Figure 3.6. In any case, we can find the node u in O (log n) time. We will
describe the rotations performed by the algorithm using an example, before
stating the general case.
Let v and w denote the left and right children of u, respectively. Since
|h( v ) − h( w)| > 1, we will consider the child that has greater height and
try to balance on that side. Since rotations can only reduce heights, we are
better off trying to reduce the height of the side that is taller! Once again
41 Dictionaries
50 60
25 75 25 75
10 40 60 80 10 40 80
5 15 30 45 90 5 15 30 45 90
12 12
60 25
25 80 10 60
10 40 75 90 5 15 40 80
5 15 30 45 12 30 45 75 90
12 11
Figure 3.7: The zag-zag rotation about
the edge between 75 and 80 creates
In the general case, let u be a node that violates the height balance con- a new unbalanced node that is shown
dition. Let v and w be its left and right child, respectively. Assume that in red. The edge over which the next
rotation is to be performed is also
|h( v ) − h( w)| > 1; the other case is symmetric. Since the height inbal- shown in red.
ance came about due to a deletion of one node, it must be the case that
h( v ) − h( w) = 2. If y and z are the left and right children of v , respectively,
then if h( y ) ≥ h(z ) then this becomes a zig-zig case. When h( y ) < h(z ), we
get the zig-zag case. Rotations do not affect the height balance of any nodes
apart from the ancestors of the node u. Therefore, at most O (log n) rotations
are sufficient to rebalance the entire tree after a deletion.
42 Dictionaries
Exercise 3.8. Describe how deletion is performed on an AVL tree, and prove
that zig-zig, zig-zag, zag-zag, and zag-zig rotations rebalances the tree at
node u.
Scapegoat⁴ , ⁵ trees are BSTs that have the property that the height of the BST 4
Arne Andersson. “General balanced
trees”. In: Journal of Algorithms 30.1
is O (log n) always. The BST will not necessarily be balanced all the time,
(1999), pp. 1–18.
but we do lazy rebalancings to maintain the property that the height is at 5
Igal Galperin and Ronald L Rivest.
most O (log n). The rebalancing is done by completely or partially rebuilding “Scapegoat trees”. In: Proceedings of the
fourth annual ACM-SIAM Symposium on
a subtree to create a perfectly balanced BST. The root of the subtree that Discrete algorithms. 1993, pp. 165–174.
is completely rebuilt is referred to as the scapegoat. We will start with the
following easy exercise that we will use throughtout this section.
We will say that a BST is perfectly balanced if for every node u having left
child u1 and right child u2 , we have |n(u1 ) − n(u2 )| ≤ 1.
Exercise 3.9. Given an arbitrary BST with n keys, give an O ( n)-time algo-
rithm to construct a perfectly balanced BST on those n keys.
Suppose that you have a BST with n nodes that is 13 -weight balanced, and
we have only search and delete operations to be performed on this BST.
It is possible to create a worst-case sequence of deletions so that the after
deleting some αn nodes, the time for searching shoots up to O ( n). Is there
anything better that you can do if you knew beforehand that only search and
delete operations are to be performed?
Suppose that whenever we have a delete operation on a key k, we mark
the corresponding node u with a tombstone ⊤ to indicate that the key has
been deleted. If we do this, we can still perform the search operation us-
ing the same algorithm as in the BST, but the running time of search will
be proportional to the number of elements in the BST plus the number of
tombstones.
Let t be the number of tombstones and n the number of actual elements
in the BST. We will always make sure that n ≥ t . If after performing a dele-
tion, we have n < t (there are more tombstones than actual elements), we
will perform a total rebuild of the BST to create a perfectly balanced BST
(using the algorithm in the exercise) that takes Θ ( n)-time. Thus, the worst-
case running time for deletion is O ( n), but we will show that the amortized
running-time is only O (log n).
43 Dictionaries
Suppose that we look at the BST right after a complete rebuild. Say the
BST has n′ elements. Now, the next total rebuild happens after n′ /2 dele-
tions. Each of these deletions takes O (log n) time since it only requires
searching for the element and marking it with a tombstone. Thus, the to-
tal time to perform n deletions can be obtained as follows:
• O (log n) time for each search operation - which gives O ( n log n) time over
all n deletions.
• Time for a total rebuild is proportional to the size of the BST, and since
the rebuilds happen when the number of nodes in the BST is halved, we
have the total time for rebuilds as 2n + 4n + . . . + 1 = O ( n).
Thus the total time to perform n deletions is O ( n log n) and hence the
amortized cost per deletion is O (log n).
Now, let us look at the case when we have insertions alone. The analysis and
the bounds will not change a lot if we have deletions as well, since the num-
ber of tombstones t ≤ n always. We saw earlier that if a BST is 1/3-weight
log n
balanced, then its height is at most log(3/2) = log3/2 n. In the case of scape-
goat trees, we will only insist that the height h of the tree always satisfies
the condition that h ≤ log3/2 n; we will not insist on any weight balancing.
We will perform partial rebuilds of the BST if this height condition is vio-
lated during an insertion. The partial rebuild is done on a subtree rooted at
a scapegoat node that is defined below. “...The goat shall bear on itself all
their iniquities to a barren region;
and the goat shall be set free in the
Definition 3.20 (α-scapegoat). Let T be a BST with n elements. A node u is
wilderness”
said to be an α-scapegoat if it has a child v such that n( v ) > α · n(u). -Leviticus 16:21-22
Lemma 3.21. Let T be a BST on n vertices and let u be a leaf at depth h >
log3/2 n. Then there exists a 23 -scapegoat on the path from u to the root r of T .
The Insert procedure on a scapegoat tree initially follows the same steps
as insertion into a BST. During the insertion, the height is calculated. If it
has increased to a value greater than log3/2 n, then we traverse the search
path from the inserted node towards the root while calculating the size of
the subtree rooted at each of the nodes in the path. By Lemma 3.21, we
know that there is a 2/3-scapegoat node u on this path. Once we find u, we
rebuild the tree rooted at u to a perfectly balanced balanced BST - this takes
time O ( n(u)). . . . which could be O ( n) if the scapegoat
is the root of the tree.
44 Dictionaries
Exercise 3.10. Write down the pseudo-code for insertion into a scapegoat
tree. You will need a subroutine call to the algorithm that constructs a per-
fectly balanced BST from an arbitrary BST.
Lemma 3.22. The total cost of n insertions into a scapegoat tree is O ( n log n). Every search on an n-node scapegoat
tree is O (log n) in the worst-case.
Proof. We will use the accounting method to calculate the amortized com-
plexity of the operations. For every insertion of a key k into the scapegoat
tree, we will give three units of credit to each of the nodes in the search path
for the key k that is done before the insertion. Thus the total credits used
per insertion is the actual cost of the insertion plus the log3/2 n credits given
across all the nodes in the path.
Consider the an insertion of a key k that results in the height property
getting violated. Also, let u be the 2/3-scapegoat on the path from the node
where k is inserted to the root r . Let v be the child of u such that n( v ) >
2 ′
3 n(u), and let v be the sibling of v in the tree.
We also know that n(u) = n( v ) + n( v ′ ) + 1. If n( v ) > 32 n(u), then this
means that n( v ′ ) < 31 n(u) − 1. Consequently, we have
1
n( v ) − n( v ′ ) > n(u) + 1.
3
Observe that right after the last rebalancing was done on the subtree
containing u, it must have been the case that |n( v ) − n( v ′ )| ≤ 1. Thus for
n( v ) − n( v ′ ) to be more that 13 n(u) + 1, there must have been > 31 n(u) in-
sertions in the subtree rooted at u. Consequently, u has credits worth at least
n(u) with itself obtained during those insertions. Since the time for the re-
build is O ( n(u)), these credits can be used for the rebuild operation. Thus,
the amortized cost per insertion is O (log n).
The modifications in the algorithms and their analysis in the case of inser-
tions and deletions is minimal. We will not have to take care of the fact that
the scapegoat tree contains nodes with keys as well as tombstones. The α-
weight balances and α-scapegoats will be defined based on the total size of
the tree includes nodes containing keys and scapegoats.
Let m be the number of nodes with key values, and let t be the number
of tombstones in the scapegoat tree. Thus the size of the tree n = m + t .
We will maintain the invariant that m ≥ t . The only way that m reduces is
when there is a deletion operation on the tree. At this point, we do a global
rebuild. The height property maintained by the scapegoat tree will now be
that h ≤ log3/2 n (includes both actual keys and tombstones).
45 Dictionaries
• x = ai for some i : If j < i , then a j is in the search path of x iff the first el-
ement from the set {a j , a j +1 , . . . , ai } in the sequence is a j . This is because Recall the analysis of quicksort us-
ing linearity of expectations in Sec-
if for any k > j , ak appears before a j , then a j lies in the left subtree of ak
tion 2.3.2.
and ai lies in the right subtree of ak , and hence a j will never be reached
while searching for x . Similarly, if j > i , then a j lies in the search path of
x iff the first element from the set {ai , ai +1 , . . . , a j } in the sequence is a j .
The case when i = j is trivial since E[ X j ] = 1. Thus for this case we can
write the expectation as
(
1
i− j +1 if j ≤ i
E[ X j ] =
1
j−i +1 if j ≥ i + 1
• ai < x < ai +1 for some i : A similar argument like in the case before gives
46 Dictionaries
the expectation as
(
1
j−i +1 if j ≤ i
E[ X j ] =
1
j−i if j ≥ i + 1
From the expressions above, we can upper bound the value of E[ X ] for
searching a key x (where ai ≤ x < ai +1 ) as
X 1 X 1
E[ X ] ≤ +
j≤i
i− j+1 j>i
j−i
i n−i
X 1 X1
= + ≤ H i + H n−i ≤ 2H n .
k k
k =1 k =1
This is not useful on its own from the point of view of dynamic insertions
and deletions since the analysis assumes the sequence of keys beforehand.
We will now see a randomized data structures that achieves these bounds.
The problem with using the algorithm from the previous discussion was
that it required the knowledge of the set K of keys beforehand, whereas in
typical scenarios what we have is a sequence of insertions, searches and
deletions. Thus, we will not be able to make sure that the insertions are done
in a uniformly random order of the key values. To get around this, a clever
data structure leverages the ideas of BSTs and heaps. The treap⁶ is a tree 6
Raimund Seidel and Cecilia R Aragon.
“Randomized search trees”. In: Algo-
whose nodes contain a key value and a priority. The tree is BST w.r.t the key
rithmica 16.4 (1996), pp. 464–497.
values, and satisfies the heap property w.r.t the priority values. We will show
that the analysis, and the guarantees of this data structure matches what we
obtained earlier if the priority values are distinct values chosen at random.
Since the treap is a BST w.r.t to the key values, we can perform the search
operation just like in the case of a BST. Let us now look at the insert proce-
dure in a treap. Consider a key value x , that has been assigned a priority p
uniformly at random. We can assume that the total number of keys inserted
across the entire sequence of operations is n. Thus, if we choose a priority
value as a random 4 log n-bit number, then with probablity at least 1 − 1/ n2
all the priority values will be distinct.
Now, we will first insert the pair ( x, p ) in the BST using with the key value
x . This insertion will be done using the BST insertion procedure. Thus, the
pair ( x, p ) will be a leaf node in the treap. We may not be done at this stage
since the parent ( y, p′ ) of ( x, p ) might have a priority p′ > p and this will
47 Dictionaries
p′
destroy the heap property. To restore the heap property of the treap, we will
perform rotations on the treap. There are two possible rotations depending
on whether the child node is a left or right child of its parent. Figure 3.8 p
total time to complete the insertion is at most the search depth of the key x .
How does the random priorities help in bound the search time for a key
x ? Consider the treap formed by inserting the pairs ( a1 , p1 ), ( a2 , p2 ), . . . , ( an , pn ).
Since the treap satisfies the heap property w.r.t to the pi s, the root of the Figure 3.8: The first tree has priority
treap consists of the pair ( ai , pi ) for the least priority pi . Now, all the key value p′ at the root and p < p′ destroy-
ing the heap property. Since p is a left
values a j < ai is in the left subtree and all the key values a j < ai are in the child of p′ , a right rotation is performed
right subtree. making p′ the right child of p, and the
right subtree of p becoming the left
Consider the sequence of keys aπ(1) , aπ(2) , . . . , aπ(n) such that pπ(1) <
subtree of p′ . This maintains the BST
pπ(2) < · · · < pπ(n) . If the keys were inserted in a BST in this order, then the property w.r.t the key values.
shape of the BST would be precisely that of the treap that was constructed as
described above. This is because the treap would contain aπ(1) as the root,
and this would be same when aπ(1) was first in the sequence of keys being
inserted. Now all the keys greater(/smaller) than aπ(1) will be in right(/left)
subtree of aπ(1) . Inductively, the root of the left subtree will be the key with
the smallest priority among them. Thus, the random priorities play the role
of the random ordering that we analyzed in the previous discussion.
Let us look at the case of deletion of a key value x with priority p. If x
is already a leaf, then it can be deleted maintaining both the BST property
on the keys and the heap property on the priorities. If x is an internal node
with children y and z with priorities p′ and p′′ , choose the p̂ = min{p′ , p′′ },
and rotate the treap around that node. Keep doing this process until x be-
comes a leaf. Notice that ( x, p ) will be the only node breaking the heap
property, and it can be deleted once it becomes the leaf. The bound on the
running time follows from observing that the sequence of rotations per-
formed during the delete operation is the opposite sequence of the opera-
tions when ( x, p ) is inserted into the treap.
Exercise 3.11. Write the pseudocode for the Insert and Delete operations.
Theorem 3.24. For a treap T with n elements where the priorities are cho-
sen uniformly at random, the Search, Insert, and Delete operations can be
performed in expected O (log n)-time.
A splay tree is a self-balancing search tree, much like the scapegoat tree, that
we saw earlier. It was described first by Sleator and Tarjan,⁷ and supports all 7
Daniel Dominic Sleator and Robert
Endre Tarjan. “Self-adjusting binary
search trees”. In: Journal of the ACM
(JACM) 32.3 (1985), pp. 652–686.
48 Dictionaries
the BST operations in O (log n) amortized time. Unlike the scapegoat tree,
the maximum height of a splay tree can be as bad as O ( n). The basic op-
eration in a splay tree is the splay operation, that moves the most recently
accessed element to the root of the tree via a series of rotations. With splay-
ing, the most recently accessed elements are near the root of the BST, and
hence can be accessed quickly.
The splaying operation is performed by a sequence of rotations. Any
element x in the tree can be moved to the root by a sequence of rotations.
Instead of doing these sequences of rotations only on the node x , the splay
tree performs a sequence of two rotations on x and its parent (unless the
parent is already the root). We will classify the rotations as zig, zag, zig-zag,
zag-zig, zig-zig, zag-zag. The operations zig and zag are symmetric, so are
zig-zag and zag-zig, and zig-zig and zag-zag.
We will see each of these operations with examples.
1. The zig operation: This operation is perfomed on a node x , that is the left
child of its parent p ( x ) where p ( x ) is the root of the tree.
p( x ) x
x p( x )
g(x) x
p( x ) p( x ) p( x )
x x g(x) g(x)
g(x) g(x)
p( x ) x x
x p( x ) p( x ) g(x)
Each of these operations takes O (1) time as they consist of at most two
rotations. The total time for splaying is O ( d x ) where d x is the depth of the
node x in the search tree.
4 Priority Queues
A priority queue is an ADT that stores a set of key values each of which has
an associated priority value. For the ease of presentations, we will assume
that the key values themselves are priorities. A priority queue Q supports the
following operations.
A simple data structure that implements the priority queue operations de-
fined above is the binary minheap. We will briefly recall the operations and
time-complexity of the binary minheap before we look at more complicated
data structures.
A binary minheap is a binary tree that satisfies the following two condi-
tions.
• The binary tree corresponding to the minheap is a full binary tree - i.e. all
the levels except the last level are complete.
• For each node u in the minheap, the key [Link] is less than the keys values
of its children.
Exercise 4.1. Verify that a binary minheap with n elements has height log n.
51 Priority Queues
Let H be a minheap of size n, and let k be a new key that is being inserted
into the heap. We start by adding k into the position H [ n + 1], and bubbling We will assume that the array in which
the heap has sufficient space for in-
up the element k to mainatain the heap property. Observe that k is inserted
sertions, and will not worry about the
as a child of the element in H [( n + 1)/2]. If k < H [( n + 1)/2], then k is problem of resizing.
swapped with that element. We then keep continuing until k cannot be
bubbled up any further. Thus insertion of a key into a minheap has time
complexity equal to the height of the heap, which is O (log n).
The other key operation on a minheap is the trickle down operation which
is performed when the minimum key is extracted from the heap. Recall that
the minimum element in a minheap is present in H [1]. To remove this el-
ement, the element H [ n] is copied to H [1] and the size of heap is reduced
to n − 1. The element H [1] is swapped with the smallest element in the set
{H [1], H [2], H [3]}. If H [1] is the smallest element in the set, then the extrac-
tion operation is finished. Else, the element is trickled down by performing
the swaps repeatedly. Once again, the time complexity of extraction is at
most the height of the heap, and hence O (log n).
The DecreaseKey operation is performed by first reducing the key value to
′
k and then bubbling it up until the heap property is satisfied. This operation
has a time complexity of O (log n) in the worst-case.
Exercise 4.2. Write down the pseudo-code for the priority queue operations
when the queue is implemented as a binary minheap.
Lemma 4.1. The number of nodes in a minheap (of total size n) at height h is
at most n/2h .
52 Priority Queues
A small modification of the minheap structure gives you the benefit of both
maxheap and minheap. This is the min-max heap1 and it supports Insert, 1
Michael D Atkinson et al. “Min-max
heaps and generalized priority queues”.
ExtractMin, and ExtractMax in O (log n)-time.
In: Communications of the ACM 29.10
The levels of a min-max heap alternate between max-levels and min- (1986), pp. 996–1000.
levels, starting with the level zero, which is a min-level. For a node u in a 3
min-level, the key value is the smallest among all its descendants. Similarly,
for a node u in the max-level, the key value is the largest among all its de- 40 20
scendants.
In a min-max heap, the smallest element is at the root of the heap, and 7 10 9 12
the largest element is one of its two children. In the figure given to the right 12 20 11
the minimum element is 3 (at the root), and the maximum element is 40 Figure 4.1: A min-max heap of height
(the left child of the root). Before we describe the insertion and extraction 3. The yellow colored levels are the
algorithms, we will explain how the bubbling up and trickling down opera- min-levels and the green colored levels
are the max-levels.
tions are performed. They are quite similar to the operations for an ordinary
50
minheap; we will have to keep the levels also in mind while performing this
operation on a min-max heap.
The pseudo-code for this trickle down operation is given below. This is the 40 20
7
//indices of children and grandchildren
S ← {2i, 2i + 1, 4i, 4i + 1, 4i + 2, 4i + 3}
40 20
if ∀i ∈ S , H [ j ] = null then return
m ← arg min j∈S H [ j ] //index of the smallest element in S
50 10 9 12
if H [ i ] is the smallest then return
Swap key values of H [ i ] and H [ m] 12 20 11
TrickleDownMin( m)
40 10 9 12
12 20 11
Consider the case where we have an element at the root (which is a min-
7
level) where the heap property is not satisfied. Assume that the subtree
rooted at the children of the root are max-min heaps (i.e. the root level is a
max-level). We first check if the key at the root is indeed the smallest. Since 50 20
the subtrees of the root all satisfy the heap property, and since we are at a
12 10 9 12
min-level, we need to check for the smallest value among the grandchildren
(if they exist) of the node; if there are not grandchildren, we have to check 40 20 11
with the children. If the smallest value is at position i , then the key at the Figure 4.2: Trickle down operation
root is swapped with the element at position i . After this swap it is possible starting from a min-level
53 Priority Queues
that the element in position i is larger than its parent, thus destroying the
heap property. We check this and swap the elements accordingly. At this
7
point a node that was originally in the max-level may reach a min-level. So,
we recursively apply the trickle down procedure starting from that level. The
50 20
sequence of operations are illustrated in Figure 4.2.
9
Exercise 4.3. Write down the pseudo-code for TrickleDownMax( i ) when the 12 10 12
element i is in a max-level. 40 20 11 60
7
Exercise 4.4. Verify that the running-time for TrickleDownMin and TrickleDownMax
is O (log n).
60 20
With the trickle down operation, we can obtain an algorithm that cre-
12 10 9 12
ates a min-max heap from an arbitrary array H with n elements in O ( n).
40 20 11 50
For each position j starting from n/2, we apply the TrickleDownMin( j ) or
TrickleDownMax( j ) depending on whether j is in a min-level or max-level, Figure 4.3: Insertion of 60 in the min-
max heap - the last level is a max-level.
respectively. The same analysis as in the case of binary minheap gives the The element 60 is compared with its
final running-time bound. The cost of ExtractMin is also O (log n) since we parent (10). Since it is larger, the Bub-
bleUpMax(11) is called. This compares
copy H [ n] to H [1] and then perform TrickleDownMin(1) on the heap of size
60 with 50 and is swapped. Now 60
n − 1. From the construction of the min-max heap, we know that the maxi- is in position 2, and the bubbling up
mum element in the heap is in positions 2 or 3. For an ExtractMax, we copy process stops.
TrickleDownMax from that position. Hence, we can perform both the opera-
tions in O (log n) without using any auxilliary heaps. 50 20
To describe insertions in a min-max heap, we have to describe the algo-
rithm to bubble up an element in the heap. We will assume that the subtree 12 10 9 12
rooted at i satisfies the heap property, but that this may not be true for the 40 20 11 5
It is not hard to verify that the bubbling up operation takes O (log n) time,
analogous to the trickling down procedure. Let us now look at the De-
creaseKey operation. We will have multiple cases to look at depending on
whether the key we are changing is in a min-level or a max-level.
An easy case is a DecreaseKey operation on a key k at position i in a min-
level. Notice that decreasing a key value in a position in a min-level keeps
the heap property for the subtrees rooted at i and their descendants. Thus
we only need to take care of changes along the path from i to the root of
the heap. Furthermore, since the decreased key value was in a min-level, we
need not worry about the nodes in max-levels since they will remain to be
higher than the new key value. Hence, we need to bubble up the element
from position i using the BubbleUpMin procedure.
Now consider the DecreaseKey operation on a key k at position i that is
in a max-level. Suppose that the new key value is k′ < k. It is now possible
that k′ < H [ i /2], the parent of position i . The parent of i is in a min-level,
and hence the subtree rooted at i /2 may no longer satisfy the heap property.
Similarly, k′ could be smaller than the grandchildren of i . This would mean
that the subtree rooted at i may also not satisfy the heap property. If that is
the case, then we should swap H [ i ] and H [ i /2], and then bubble up from i /2
and trickle down from i . If k′ ≥ H [ i /2], then we would only need to trickle
down from i . Filling in these details is left as an exercise.
this operations.
tree. A random walk on a binary tree starts at a root and chooses left or
right at every node, with probability 1/2. The random walk continues until
the walk falls off the tree - i.e. we reach a node that is null. The length of
the random walk is the number of steps taken by the random walk before it
falls off, and is a random variable. The following lemma about the expected
length of a random walk will give us the running-time bound for Merge.
Lemma 4.2. Let ℓ be the length of a random walk on a binary tree T with n
nodes, starting at the root r . Then, E[ℓ] = O (log n).
Proof. Firstly, add dummy nodes to every node in T that has less than 2
children, so that every node except the leaves has exactly two children. Thus
every leaf in the new tree is a dummy node. The random walk ends when
it has reached a dummy node. Recall that a binary tree with n nodes has at
most n + 1 leaves, and therefore in this case has at most Θ ( n) dummy nodes.
Let u1 , u2 , . . . , u r be these leaf nodes, that are at depths d1 , d2 , . . . , d r .
The probability of reaching the node ui is therefore 1/2di . Since the ran-
Pr
dom walk ends in one of these nodes, we have i =1 1di = 1.
2
The expected length ℓ of the random walk can be expresses as follows. At this point you can complete the proof
by observing that the expression for
r r the expectation is the formula for the
X di X 1
E[ℓ] = di
= log 2 entropy of a distribution with support
i =1
2di i =1
2di Θ ( n) and hence is always O (log n).
r
X 2di
≤ log , because log is a concave function
i =1
2di
= O (log n).
The insert operation is performed by merging the heap, with the heap
containing just the key which is to be inserted. Similarly, ExtractMin is per-
formed by mergin the left and right subtrees of the root. The DecreaseKey
operation can similarly be done by removing the subtree rooted at the key
we are changing and merging it with the original heap. Thus, all operations
are performed using O (1) calls to the Merge operation with some small over-
head. This lets us keep the bounds on the running-time of the priority queue
operations at O (log n).
main operation in the skew heap is a merge operation that recursively tries
to merge two heaps H1 and H2 . The merge operation is performed by patch-
ing up the right-most path of both the trees H1 and H2 , and then swapping
the left and right subtrees of all the nodes on this path. Before looking at the
pseudocode and analyzing the algorithm, we will see an example using Fig-
ure 4.5. This figure shows two skew heaps. Note that the binary trees satisfy
the min-heap property, but are not full or balanced binary trees.
50 10 19 12
13 20 40 30 14
16 25
W.l.o.g assume that the key value of the root in H1 is smaller than the key
value of the root in H2 . The merge procedure, moves the subtree rooted at
the left child of the root of H1 to the right and recursively merges the subtree
rooted at the right child of H1 with the tree H2 . The first step step in the
recursive process is show in Figure 4.6.
5 + 10
19 12 13 20
40 30 14 16 25
The final heap formed after all the recursive calls in the merge procedure
is shown in Figure 4.7.
Notice that the heap is skewed with a long leftmost path. But this means
that many subsequent merges will have a shorter time complexity since
each merge is done on the rightmost path followed by a switch of the left
and right children along the path. This gives some intuition as to why we
can expect small amortized cost for the merge operation. The pseudcode
for the merge operation is given as Algorithm 4.5. You will notice that the
pseudocode is similar to the randomized merging that we did earlier. The
difference is that instead of choosing a random child to recursively merge,
we always choose the right child and swap the left and right children as well.
58 Priority Queues
5 50
10 19
12 13 40
14 30 16
20
25
We will now analyze the amortized cost of the merge operation using the
potential method. To that end, we need a few definitions of heavy and light
nodes. We will denote by n(u), the number of nodes in the subtree rooted
at u (including u). A node u is said to be heavy if n(u) > n([Link])/2.
Otherwise u is said to be light. From the definition, it is clear that each node
u has at most one heavy child.
Lemma 4.3. In any binary tree, for every node u and a descendent v of u, there
are at most log n many light nodes in the path from u to v .
Proof. For every light node u′ in the path n(u′ ) ≤ n(u′ .parent)/2. Thus, if
there are k light nodes in the path from u to v , n( v ) ≤ n(u)/2k . Hence we
have
n(u)
k ≤ log .
n( v )
We will call a node u right-heavy if it is a heavy node and is the right child
of [Link]. Let Ti be the skew heap after the i th operation, we will define
59 Priority Queues
the potential as
Φ( i ) = |{u ∈ Ti | u is right-heavy}|.
The intuition behind this definition of the potential function is that the
rightmost path that is being merged between the two trees contain at most
log n light nodes. All the remaining nodes that are visited during Merge are
heavy, and they become left children (due to the swap). Thus, they can pay
from their potential towards the merging. In effect, the Merge operation
needs to pay only for the light nodes, and the potential from the heavy nodes
is used for the rest.
Consider the merging of two heaps H1 and H2 such that H1 has a right-
most path of length n1 and H2 has a righ-most of length n2 . The actual cost
of the merge operation as described in Algorithm 4.5 is n1 + n2 . The total
number of light nodes in this right-most path across the two heaps is at most
log( n1 ) + log( n2 ) ≤ 2 log n − 1, where n = n1 + n2 . Let s1 and s2 be the
number of heavy nodes in the right-most path of H1 and H2 , respectively,
that were visited during the merge operation. Since all these nodes are in
the right-most path, they are right-heavy. But after the Merge process, they
becomes left children of their parents. Similarly, the siblings of light nodes
on the right-most path are potentially heavy. Thus, after the Merge, they
become right children of their parents and can become right-heavy. Since
there are at log n many light nodes in the rightmost path, we have
Φ( i − 1) − Φ( i ) ≥ s1 + s2 − log n.
ĉi = ci + Φ( i ) − Φ( i − 1)
≤ (s1 + s2 + 2 log n − 1) + (log n − s1 − s2 )
= 3 log n − 1.
A slight variant of the merge procedure, where the merging on the right-
most path is performed bottom-up gives amortized bounds for O (1) for Insert
and ExtractMin. But even this implementation gives O (log n) amortized
bound for DecreaseKey operation. Many graph algorithms that use priority
queues require DecreaseKey operations. We will now see an implementation
of mergeable heaps that perform DecreaseKey in O (1) amortized cost. These
60 Priority Queues
The variant of mergeable that we are going to see now are called binomial
heaps.3 These data structures are slightly clunkier than before as each node 3
Jean Vuillemin. “A data structure
for manipulating priority queues”.
to store multiple pointers and auxilliary information.
In: Communications of the ACM 21.4
A binomial heap is a collection of binomial trees, each of which satisfies (1978), pp. 309–315.
the heap property - the root of the tree contains the smallest key among all
the keys in that tree. A binomial tree of order k, denoted by Bk , is a tree with
2k nodes, obtained by pointing the root of a binomial tree of order k − 1 to
the root of another binomial tree of order k − 1. See Figure 4.8 for binomial
trees of orders 0 to 3.
Proof. The proof follows from induction on k. You can verify that the state-
ment holds for B0 and B1 . Consider an arbitrary level ℓ of Bk . Since Bk was
formed by taking the union of two Bk−1 s, the nodes in level ℓ are all the
nodes in level ℓ − 1 in one of the Bk−1 s and all the nodes in level ℓ of the
other Bk−1 . Thus the number of nodes in level ℓ of Bk is (k−1 k−1
ℓ−1 ) + ( ℓ ) (by
induction hypothesis). Hence, the number of nodes at level ℓ of Bk is (kℓ ).
of the next lower order and next higher order - i.e. the children of a node
7 3 6 4
are connected via a doubly linked list. It also has a parent pointer that will
be useful during a DecreaseKey operation. We will also maintain a pointer 8 8
to the node with the lowest key value so that GetMin can be implements in
1
O (1) time.
The key operation that we will look at is the Merge. The other operations 2 7 3
of the priority queue are implemented using multiple calls to Merge.
6 4 8
Let’s start with how we will merge two binomial trees T1 and T2 , both
of order k. This is illustrated in Figure 4.9. Let r1 and r2 be the roots of T1
and T2 , respectively, and let r1 .key < r2 .key. We will first update the sibling
pointer of r2 to the node pointed to by the current child pointer of r1 , and
similarly update the sibling pointer of that node. Next, we will update the
child pointer of r1 to point to r2 . Thus, we can perform the merging of two
binomial trees of the same order in O (1)-time.
The Merge operation of a binomial heap is done by repeated merging of
the binomial trees that constitute these heaps. Consider the following two
heaps that we will call H1 and H2 .
2 7 1 6
9 6 3 9 7 9
8 10 10 9
12
5 6
7 7 9
2 5 1
7 7 6 6 6 3
9 9 7 9 7 9 8
10 10 9 8
12
taining the smallest element from the heap. Suppose that is was of order k. 7 7
Then the children of the root themselves are a binomial heap of size 2k−1 .
9
We will then merge this heap with the former. We will then traverse the
root list to maintain the smallest element in the merge list. This overhead 6 6 6 3
which is O (log n) helps us perform GetMin in O (1)-time. For the heap in Fig- 9 7 9 7 9 8
ure 4.11, if we perform ExtractMin, then the node with value 1 is removed,
10 10 9 8
and its children form a new heap. This new heap is merged to get the final
12
heap after ExtractMin. This is illustrated in Figure 4.12.
The DecreaseKey operation can be performed by the standard bubbling up 5 2
operation on the binomial tree that contains the key that is decreased. In a 6 6 7 7 6 3
binomial heap on n keys, the largest binomial tree has size at most log n, and
9 7 9 7 9 9 8
height at most log n. Thus the DecreaseKey operation can also be perform in
10 10 9 8
time O (log n).
12
We will now see how to make the Merge operation lazy. We will see that this
makes both Insert and Merge run in O (1)-time worst-case. Unfortunately, the
ExtractMin will become O ( n) in the worst-case. We will show that we can
also manage to do it O (log n) amortized cost.
The idea here is that we will forgo the property that there can be at most
63 Priority Queues
one binomial tree of a particular order in the heap. This will let us perform
Insert and Merge in O (1)-time. We will then clean up the binomial heap af-
ter an ExtractMin operation. The Insert and Merge procedure is to merely
add the new binomial trees into the existing list of binomial trees. This
amounts to updating a couple of pointers and can be done in O (1)-time.
For instance, if we perform n insertions starting from the empty heap, the
lazy binomial heap will be just be a linked list of n elements.
The clean-up that is done during ExtractMin is as follows: The first step is
to delete the minimum element, and merge its children into the main heap.
The pointer to the smallest element can be maintained in O (1)-time during
Insert, Merge and DecreaseKey operations. If there are n elements in this Maintain a global pointer for each heap,
and update it by comparing. Verify that
lazy heap, maintain ℓ = log n pointers P1 , P2 , . . . , Pℓ such that each Pi is either
this is indeed possible.
null or points to the root of a binomial tree of order i . Initially all the Pi are
null. While traversing the root list of the lazy heap, for each tree in the heap
of order i , if Pi is null then point Pi to the root of the tree. Otherwise, merge
the tree pointed to by Pi and the current tree of order i and check with the
tree pointed to by Pi +1 . Continue this process until we find an empty P j ,
j > i . At the end of this process, we have a binomial heap whose trees are
pointed to by P1 , P2 , . . . , Pℓ .
We will analyze the amortized cost of ExtractMin using the accounting
method. For each insertion, we will give an additional O (1) credit to the
element that is inserted. This will be used for a later merging operation with
another binomial tree of the same order. During ExtractMin, all the children
of the minimum element are added in the root list of the heap, and each of
these elements are O (1) units of credit. Thus, we spend O (log n) time for the
initial deletion step of ExtractMin. We would now like to show that we have
enough credit to take care of the clean-up operation.
After the deletion operation, we have a heap that consists of binomial
trees of order up to log n. Furthermore, each of the roots of these trees have
O (1) units of credit that they can use for merging. For merging two tree T1
and T2 of order i such that root of T1 becomes the new root, we will use the
credit available in the root of T2 to perform this O (1) operation. After a node
becomes a child of the root, it is never involved in any of the further merge
operations of that tree. Thus, we can perform the clean-up operation to get
a binomial heap such that the roots of each of its constituent binomial trees
have O (1) units of credits with itself for future operations.
We will now modify the lazy binomial heaps in the previous section to obtain
a new data structure that will also let us perform the DecreaseKey operation
in amortized O (1)-time. The DecreaseKey operation is important in various
shortest path algorithms, as we will see later, and using this data structure
will give the best asymptotic running-time bounds for those algorithms.
A Fibonacci heap⁴ consists of a collection of trees that satisfy the heap 4
Michael L Fredman and Robert Endre
Tarjan. “Fibonacci heaps and their
property. The trees need no longer be binomial trees. The order of a tree
uses in improved network optimization
will now be defined as the number of children of its root. Merging two such algorithms”. In: Journal of the ACM
(JACM) 34.3 (1987), pp. 596–615.
64 Priority Queues
trees of order k will create a new tree of order k + 1. This is similar to the
merging of binomial trees, and is an O (1) operation. The ExtractMin op-
eration remains as in the case of lazy binomial heaps. We will change the
DecreaseKey operation to a lazy version, and obtain the O (1)-amortized 2 5 1
running-time bound. 7 7 6 6 6 3
the operation does not destroy the heap propery, then we can proceed with 10 10 9 8
the operation and stop in O (1). On the other hand, if the decrease of the
12
key leads to the violation of the heap property, we will splice that subtree 2 5 5 1
from its parent and add it as a new tree in the heap. In fact, this would let us 7 7 10 10 6 6 6 3
The problem with this lazy method is that we might end up with trees of 9 8
order k that contains only k + 1 vertices. The requirement that the largest Figure 4.13: When the DecreaseKey
operation is performed on the green
order for any tree in a heap with n nodes should be log n will be violated.
node and its value is reduced to 5, the
This was what made the clean-up during ExtractMin work in O (log n)- node is spliced and added to the root
amortized time. To avoid this issue, we will perform what is known as a cas- list. It’s parent node (with value 6) is
marked. The tree of order 4 (rooted at
cading cut, that splices a node if at least two of its children has been spliced the element 1) in the heap is now no
from it due to DecreaseKey operations. longer a binomial tree.
Now, each node u stores a boolean value [Link] that is set if one of its
children has been spliced. If a marked node has another child spliced from
it, then u splices itself from its parent and adds itself to the root list and set
[Link] to false. The parent of u may continue this process upwards, and it
stops when an unmarked node or the root is reached.
There are two things that we need to prove here.
1. Even though the cuts are cascading, the amortized cost of DecreaseKey is
O (1).
2. The maximum degree of any tree in the heap is O (log n) - this would
be required for the ExtractMin to have an amortized running time of
O (log n).
Proof. We will once again use the accounting method. First, observe that
splicing an element and adding it into the root list is an O (1) operation. We
will additionally pay O (1) units of credit to this node which has become the
root (for further clean-up operations), and pay O (1) credit to its parent if
it is unmarked. The credit given to the unmarked node is for the cost that
it has to bear during a cascading cut. Hence, only the node whose value is
decreased and the final unmarked node on the trail of the cascading cut
need to be paid for. The costs of removing marked nodes in the path and
adding it into the root list has been paid for when the node was marked.
Therefore, the amortized cost of DecreaseKey is O (1).
The proof of the second property will also justify the name Fibonacci
heaps that we gave for this lazier version of mergeable heaps. To that end,
65 Priority Queues
we start with the following lemma about the nodes in the trees constituting a
Fibonacci heap.
Lemma 4.7. Let u be any node of order r in a Fibonacci heap, and let v1 , v2 , . . . , vr
be its r children in the order in which they were merged with u ( v1 being the
first). Then for every i ≥ 2, order of vi is at least i − 2.
Proof. First note that s0 = 1 and s1 = 2 since a node with zero children has
size 1 and a node with 1 child has size 2. We will prove that sk satisfies the
following recurrence for k ≥ 2.
k
X
sk ≥ 2 + si−2 .
i =2
Using the claims stated above, we will prove that sk ≥ Fk+2 to com-
plete the proof of the lemma. This will also be proved using induction on
k. Clearly s0 = 1 ≥ F2 = 1 and s1 = 2 ≥ F3 = 2 proving the base case of the
induction. By the induction hypothesis, si ≥ Fi +2 for all i < k. Thus we can
66 Priority Queues
As a simple corollary of the lemma we can see that if the maximum or-
der of any tree in a Fibonacci heap with n elements is k, then n ≥ sk ≥ φ k .
Therefore k = O (log n). This means that the amortized complexity of Ex-
tractMin is O (log n) and follows from the analysis of the lazy binomial heap
that we did in the last section.
Lemma 4.11. Let S ⊆ V be such that for every u ∈ S , d (u) = d ∗ (u). For every
v ∈ V − S , set d ( v ) = minu∈S {d (u) + w(u, v )}. Consider the vertex x ∈ V − S
such that x = arg min v∈V −S {d ( v )}. Then d ( x ) = d ∗ ( x ).
Proof. The proof will crucially use the fact that w( e ) ≥ 0 for every edge
e ∈ E . We will prove by contradiction. Suppose that d ( x ) > d ∗ ( x ).
Consider the shortest path from s to x . Let ( v, x ′ ) ∈ E be the first edge in
that path such that v ∈ S and x ′ ∈ V − S . Let d ( x ′ , x ) refer to the shortest
path from x to x ′ . Then d ∗ ( x ) = d ( v ) + w( v, x ′ ) + d ( x ′ , x ).
For x , let u ∈ S be the vertex for which d (u) + w(u, x ) is minimized. From
the choice of x , we know that d (u) + w(u, x ) ≤ d ( v ) + w( v, x ′ ). But this
would mean that
The underlying algorithm iteratively builds the set starting from {s} all
the way to V . Lemma 4.11 gives a way to find the next element to be added
to S at each step of the algorithm. We will now discuss the ideas involved in
making this algorithm efficient using suitable data structures which will help
in obtaining the x given by Lemma 4.11 at every step.
We need a data structure to maintain the set V − S from which the vertex
x that satisfies the condition of Lemma 4.11 can be easily extracted. Priority
queues seem a natural choice where the nodes in the queue correspond to
the vertices in V , and the priorities correspond to the d ( v ) values. Obtaining
the x in Lemma 4.11 will correspond to an ExtractMin operation on the
priority queue.
The priority queue is initially constructed by adding all vertices in V , with
d (s ) = 0 and d ( v ) = ∞ for all v ∈ V − {s}. The set S = ; initially. The
first ExtractMin operation will extract s from the priority queue and update
the value of d ( v ) for the other vertices. Notice that only the vertices that are
neighbors of vertices in S are updated. At an intermediate stage, when a
new vertex u is extracted from the priority queue and added to S , we need to
update the d ( v ) values for only those vertices v that are neighbors of u. The
other values remain unchanged. This operation performed by the algorithm
is a relaxation of the edges going out of u.
1. Each vertex is inserted into the priority queue at the beginning of the
algorithm and extracted exactly once. Thus, the total time for these oper-
ations is O (|V | log |V |) in the worst-case.
• Union(u, v ) - return the id of the set formed by taking the union of the sets
containing u and v .
A simple way to implement disjoint sets would be to use a linked list for each
of the sets. For a list L , the element at the head of the list [Link] will be
the identifier of the set. Thus, adding an element in the set will be an O (1)
operation in the worst-case.
The union operation Union(u, v ) will first need the Find operation on u
and v to find the sets that they belong to. If they belong to the set, then the
union operation does not change the sets. Thus the complexity of union is at
least as large as the complexity of the Find operation. For a linked list, the
Find operation can potentially take O ( n)-time since we may have to traverse
the entire linked list.
Now for each element u in the set L , we could add additional information
- [Link] that points [Link]. This way we can perform Find in O (1) time.
But this creates a new problem: Each time we perform a union, we need to
modify [Link] for each element in the set that we are merging.
Exercise 5.1. Write the pseudocode for the two version of disjoint sets using
linked lists.
One heuristic that we can think of at this stage is to attach the smaller set
to the larger set during a union operation. This way the number of updates
of the head pointer is the minimum of the two set sizes. This will still not
avoid the worst-case O ( n) complexity, but we will see that the amortized
cost of the operations is better. We will use [Link] to store the size of the set
69 Disjoint sets
Lemma 5.1. If a disjoint set data structure is implemented using linked lists,
then the total cost of n MakeSet operations and m operations of Find andUnion
is at most O ( m + n log n).
An alternate way to represent sets is to use trees instead of lists. The nodes
in the tree correspond to the elements of the set, and each node has a
pointer to its parent. The id of the root node is the id of the set. Since the
number of childrenfor each node is unbounded, children pointers are not
maintained in most implementations.
The MakeSet operation creates a single node tree with parent pointer
pointing to itself. This takes O (1)-time. The Find operation follows the We will identitfy the root of the tree as
the node u such that [Link] = u.
parent pointers until the root, and returns the id of the root. The union
operation Union(u, v ) performs Find(u) and Find( v ), and connects the parent
of the root of one of the trees to point to the root of the other tree. The
worst-case running time of Find and Union depends on the depth of tree
representing the set. This can be as bad as O ( n) if the Union operation is
performed poorly - for instance, the tree might end up being a linked list
due to the unions. We will see two ways to implement the union operations
- one that will give good worst-case bounds, and another that will give good
amortized bounds.
70 Disjoint sets
5.2.1 Union-by-rank
We will start be defining the rank of each node u in the tree. This will be
done inductively.
(
0 if {u} is a singleton set
rank(u) =
1 + maxi∈{1,2,...,k} {rank( vi )} if v1 , v2 , . . ., vk are the children of u
In other words, the rank of a node u is the height of the node in the tree.
We will not refer to it as height because in the next section, we will modify
the algorithm in a way such that the rank will no longer be equivalent to the
height. Since the worst-case time complexity for Find(u) will the depth of
the tree containing u, we would like to keep the rank of the trees to be as
small as possible. To do this, we will add the root node with lower rank to
the root node with the larger rank.
Suppose that r1 = Find(u) and r2 = Find( v ) are the roots of the trees
containing u and v . If rank( r1 ) < rank( r2 ), then we set r1 .parent = r2 . We
do the opposite if rank( r1 ) > rank( r2 ). On the other hand, if rank( r1 ) =
rank( r2 ), then we arbitrarily map one to the other - say r1 .parent = r2
and we will increment rank( r2 ) by 1. Note that the trees constructed us-
ing union-by-rank need not necessarily be binary trees. The branching factor
could be arbitrarily large. See Figure 5.1 for an example.
Proposition 5.2. For any node u that is not the root, rank(u) < rank([Link]).
Lemma 5.3. Let u be a node in the tree with rank r . Then the number of nodes
in the subtree rooted at u is at least 2 r .
Proof. Let us look at the step when u becomes a node of rank r . This must
have happened due to the union of the tree rooted at u (when it had rank
r − 1) with another tree of rank r − 1. Inductively each of the trees had at
least 2 r−1 nodes, and hence the union has at least 2 r nodes. Since a node
never looses nodes from its subtree, this bound continues to hold later as
well.
Since each node with rank r has at least 2 r nodes in its subtree, and the
total number of nodes is n, the following observation can be easily verified.
Proposition 5.4. In a disjoint sets data structure with n elements in total, the
number of nodes with rank r is at most n/2 r .
71 Disjoint sets
Thus, we can see that the largest rank possible for any node in the data
structure is at most log n. Consequently, the path from any node to the root
of the tree containing it is at most log n. Hence we have the following result
on the worst-case time-complexity for union-by-rank.
Theorem 5.5. For a tree implementation of the disjoint set data structure
that uses union-by-rank, the worst-case time-complexity of Find and Union is
O (log n) for a set with n elements.
7 5 6 9
7 8 5 6
First note that with path compression, the rank of a node is no longer
same as the height of the node in the tree containing it. In Figure 5.2, after
the Find(8) operation, the height of the node 4 is 1, even though rank(4) =
[Link], a few of the properties from the previous section carry over
for the case of path compression as well. It remains true that for all nodes u
in the tree except for the root, we have rank(u) < rank([Link]). Lemma 5.3
is no longer true for all nodes in a tree, but if the root node u has rank r ,
then the tree contains at least 2 r nodes. The reason the statement does not
hold for internal nodes is that child nodes may now be directly connected
to the root following a path compression operation. Note that once a node
becomes an internal node, its rank remains unchanged. Thus it must be the
case that the number of nodes of rank r is at most n/2 r , just like in the case
when we performed union-by-rank without path compression.
We will now show that the amortized complexity of Find and Union oper-
ations is almost a constant - it will be O (log∗ n), where log∗ is the iterated
logarithm function. During each Find operation on a node u, the value of
72 Disjoint sets
[Link] increases. The largest rank for any node in the tree is log n, as we
have already seen earlier. The Find operations start becoming costly once
a node stops being the root of a tree. We will use the accounting method
to assign a certain amount of credit to a node once it becomes an internal
node (due to a Union operation), and show that there will be sufficient cred-
its available to perform Find operations cost-effectively. We will not worry
about the Union operation since their cost is bounded by the cost of the Find
operations.
Recall that each Find operation involves a traversal of a path from a node
u to the root of the tree containing u. Our accounting strategy will pay for
some of the edges in the path, and the remaining will be counted as the
amount paid in that particular step. Each node u of rank k will be credited
with 2k units when it becomes an internal node during a Union operation.
This will be sufficient to pay for traversing the edge from u to [Link] at
most 2k times. After one traversal from u to [Link] happens during a Find
operation, the new parent has a larger rank than the old parent of u. Thus,
after paying the 2k units we can be sure that [Link] has rank at least 2k .
Hence, the cost that Find has to pay outside of the amount which has been
credited will be the number of edges from nodes of rank k to nodes of rank
2k . The number of such edges is in fact at most log∗ n. Let us carefully finish
this analysis.
First, we will bound the total amount that we pay as credit across all
operations. This must be added to the final cost of the operations. We will
first divide the ranks into buckets with a bucket consisting of ranks from
{k + 1, . . . , 2k } starting with k = 0. For instance, the buckets would be
{1}, {2}, {3, 4}, {5, 6, . . . , 16}, {17, 18, . . . , 65536}, {65537, 65538, . . . , 265536 }, . . .
Now, there are at most log∗ n buckets - this follows from the construction
of the buckets and the definition of the iterated logarithm function. Thus
the total amount credited across the entire sequence of operations is at most
n log∗ n.
For each Find operation, we will count the cost in two parts - one is the
cost the operation pays and the other is the part that will be taken from the
credited amount. Every edge that is traversed in the path to the root that
connects nodes with ranks in two different buckets will be paid by the Find
operation. Since there are at most log∗ n such edges, this cost that is paid
is at most log∗ n. For every other edge from a node u to [Link], the cost is
paid by u from the amount which was credited to it.
But why is the credit given to u sufficient for all such traversals? To see
this, let us assume that rank(u) lies in the bucket {k + 1, . . . , 2k }. Since the
rank of [Link] increases after each traversal of the edge between u and
73 Disjoint sets
Theorem 5.6. The amortized complexity of Find operation when using union-
by-rank with path compression on sets containing n elements is O (log∗ n).
Kruskal’s algorithm
Let S ⊆ V be any subset of the vertex set, and let S = V − S . The minimum
weight edge e ∈ E (S, V − S ) is part of the MST.
Kruskal’s algorithm for MST first sorts the edges according to edge weights.
It then iterates over the list, adding an edge into the MST if the end points of
the edge do not lie in the same connected component. The correctness of the
algorithm follows from the greedy-choice property since each edge that is
added is the minimum weight edge crossing the corresponding cut and must
be in the MST.
To implement this algorithm, we start by sorting the edges according to
their weights. This incurs a time complexity of O (|E| log |E|). Subsequently
we can maintain a collection of sets corresponding to the connected com-
ponents in the spanning forest as the edges are added to it. Initially the
collection consists of the singleton sets {v} for each v ∈ V . Now for each
edge {u, v} ∈ E , we perform Find(u) and Find( v ) to check if there are in
the same component. If not, the edge {u, v} is added to the MST and the
Union(u, v ) is performed. Using the tree representation of disjoint sets to-
gether with union-by-rank with path compression, the MST can be computed
in time O (|E| log∗ |V |). Thus, the total running time of Kruskal’s algorithm is
O (|E| log |E| + |E| log∗ |V |).
6 Data Structures for Range Queries
The next set of lectures will deal with data structures that answer range
queries. These