A sufficient representation invariant is:
The abstraction function isThe last two conditions ensure each representative is a genuine member of its own block and that the abstract function is idempotent function. Cells beyond
used are unconstrained and ignored. When used == 0, these conditions hold vacuously and the abstraction function yields the empty map. This is an explicit-key version of quick-find, rather than a parent-pointer disjoint-set forest.Insert a singleton mapping, retrieve a stored representative, or relabel every occurrence of the second block's old representative:
def makeSet(x: Int): Unit = {
require(isInMap(x) == -1, "element already present")
require(used < capacity, "capacity exhausted")
elements(used) = x
representatives(used) = x
used += 1
}
def find(x: Int): Int = {
val i = isInMap(x)
require(i >= 0, "unknown element")
representatives(i)
}
def union(x: Int, y: Int): Unit = {
val keep = find(x)
val replace = find(y)
if (keep != replace) {
var i = 0
while (i < used) {
if (representatives(i) == replace) representatives(i) = keep
i += 1
}
}
}For
makeSet, the new key is absent, its representative is itself, and old mappings are unchanged. find is a read-only linear search. In union, snapshot both representatives before changing any cell: recomputing the representative of y during the scan could change the criterion halfway through. The loop invariant says that every processed index already has its specified final representative and every unprocessed index still has its old representative. The cached labels make this assertion stable.At exit the abstract postcondition in part (a) holds. The surviving representative was already a fixed point and belongs to its old block; every member of the replaced block, including its old representative key, now maps to it. Thus idempotence and key uniqueness are preserved. Duplicate insertions, absent-element operations and capacity overflow fail before changing the map. Each operation is in the worst case, except that a same-block union can skip the relabel scan after its lookups. Storage is . These bounds follow from the chosen parallel-array quick-find representation; no parent-pointer optimization is being assumed.
When merging two blocks in quick-find, let and be fixed saved labels. Replacing every old occurrence of by preserves all other blocks. A scan loop invariant says processed cells already have their specified final labels while unprocessed cells still have their old labels. Recomputing during this mutation can change the replacement label after the cell for is processed, causing remaining old- cells to be missed. Saving both labels makes the update criterion stable and the invariant provable.