CSCI 260 Fall 2026: Lab 2: Union Find: Heuristics for
faster performance
To submit through Git:
- makefile, .cpp files, .h file for making testappQU
- Do not include the executable, or .o files, or the gen program
- Do not include your testfile e.g. input.10.25
- The program created by the make command implements
union-by-rank with path-compression.
- The program created by the make command
will ask for a file of inputs, and the file
will be of the form outlined in Lab 1.
- The program will produce no prompts and no outputs
except the output produced when debugprint is called, and then
the outputs should look as described at the bottom
of this page.
Task: You are to modify your Union-Find implementation from Lab 1 to use
union-by-rank and
path compression.
These two algorithmic improvements together consititute the quick-union version of union-find. The
executable should be called testappQU ("Quick-Union") to
differentiate it from your initial testappUF.
- Use git to make the Lab2 directory.
-
Copy all the .cpp files, .h files, gen, the makefile, and some test files from the Lab1 directory to the Lab2 directory.
-
cd into the Lab2 directory and change the name of the unionfind.cpp file
to unionfindQU.cpp
-
edit the makefile so that the executable is now called testappQU.
If you are
using gedit, make sure the setting for tabs (at bottom of window) is
not on "use spaces" when you edit your makefile.
Your new makefile could look like this:
testappQU: testappUF.o unionfindQU.o
  g++ -o testappQU testappUF.o unionfindQU.o
testappUF.o: unionfind.h testappUF.cpp
  g++ -c testappUF.cpp
unionfindQU.o: unionfind.h unionfindQU.cpp
  g++ -c unionfindQU.cpp
If you are new to makefiles, read up on them a little in your browser; this tutorial
may be a good place to start.
The thing to remember is that makefiles are very sensitive to
the types of white space that are used -- tab must appear before any
command (the first line of each couplet above is a list the dependencies; the second line is a
command). A makefile is a place to
write all the directions for how to make an executable out of
code files in the current directory. To invoke the makefile
in a given directory, you just invoke the command "make" while in
that directory.
Notice that both unionfind.cpp and unionfindQU.cpp use the same
header file. That is because they present the same interface. Also, there
is no need to change the test program.
How to code the new implementation:
-
Union-by-Rank
At initialization, every element is in a singleton set and has rank 0.
When two sets are merged (unioned), if they are of different rank,
the greater rank root is the new root. If rank is a representation of
height, does the height need to be updated?
No, the height has not changed, so the rank need not be updated.
It is no longer necessary to ensure that the name of the set is the
set of the first argument in the union (merge) command.
When ranks are different, we name the set by the root of the tree
with greatest rank, and we make the lower rank tree be a child of the
higher rank tree.
However, if the rank is a tie,
the set associated with the first argument will be the one that is set to the root of the tree, and therefore gives its name to the new, larger, tree.
When two sets are merged that have the same rank, then the new
merged tree will actually have greater height.
Its new rank is the old rank plus one; update this
in the 'rank' array. By
convention, let the first parameter be the new root if they are tied in rank.
Use the array "rank" that is in the unionfind.h header file.
-
Path-compression
Each time you do a find(i), compress the path from the node i, so that
every node on the path from i to i's root now has as its parent node
the root of the tree. The find operations performed in the "union" procedure
should also involve path-compression.
-
Debugprint
Alter "debugprint" so that it first outputs the parent array as is, then does a sequential set print -- i.e., prints out output such as:
[3] 0 3 5 7
[6] 4 6 9
[8] 1 2 8 10
That is, it prints out, in increasing order of set-name, the name of the
set in square brackets, then all the elements in that set in increasing order.
You may have done this as the optional part of Lab 1. If so, the same
subroutine (function) will not necessarily work here. Why not? However,
if you have programmed your "find" properly to implement path compression, then all that needs to be
done in your debugprint is, after you
have printed out the contents of the parent array, execute the
following loop before you do the set print:
for (int k=0; k
find(k);
Then you execute the loop that you may have used in Lab1; that is,
execute a loop over all the indices from 0 to n-1, and check if parent[i]==i, in which case you would loop through the
indices from 0 to n-1 checking to find the elements in the same set as i. Why does it work now, but if you left out the above "find" loop, it wouldn't work?
Your algorithm should take no more than O(n2) * time to execute a find operation.
The output it produces should look like this.
Note that all the files end in "d", for debugprint. This is for testing,
and should be left in your executable. However, once you have debugged
the programs and if you are interested in comparing testappUF and testappQU for speed, you
should remove the "d" from the end of each file; debugprint is not
part of our running time calculations of interest.