Just to clarify, getroot(a) returns the root (or label) of the set that a is a member of. Initially all of the elements belong to their own singleton set, so the root of each set is just that node. When sets are joined together, each set becomes represented as a tree, where root[a] essentially points to the parent of a. join(a,b) merges the set containing a with the set containing b, by making the root of a a child of the root of b.
The algorithm is super efficient because every time you query for which set a node belongs to, it traverses the path from that node all the way up to the root of the tree. Each node that is visited along this path gets its parent pointer replaced with a pointer directly to the root, thereby "collapsing" the tree so it's very flat.
For example, say you had a tree like this:
1

2

3

4

5
Now suppose you call getroot(4). Then the new tree becomes:
12

+3

+45
So now all future operations become faster, because all of the nodes are pushed really close to the root.