This is an alternative implementation that does not use union-find. When compared to your implementation (with the tweak by xskxzr), my implementation has both advantages and disadvantages.
Advantages:
It has a real $O(1)$ GET_VALUE
instead of an amortized nearly-$O(1)$ one
It could implement SET_TO_0
, or even SET_TO(i, n)
for any n
(with FIND
still returning the first non-zero value)
Disadvantages:
First, notice that you have one "set" operation (SET_TO_1
) and two "get" operations (GET_VALUE
and FIND
). So you can have two structures that can handle SET_TO_1
: one per get operation.
For SET_TO_1
and FIND, a balanced binary search tree that contains the elements whose value is 0 (or 1 if you want to think a bit more and not have an $O(n)$ initialization) give SET_TO_1
and FIND
in $O(\log n)$.
So now we only need to handle SET_TO_1
and GET_VALUE
. For which an array clearly works.
In conclusion, your structure is a balanced binary search tree T and an array A. The tree is initialized as containing all the elements, and the array as filled with zeroes.
SET_TO_1(i)
does T.remove(i)
and A[i] := 1
GET(i)
returns A[i]
FIND(i)
returns T.myfind(i)
(where myfind
find the smallest element j
in the tree such that i <= j
)
myfind
could be implemented as follow:
function myfind(tree, index) {
var node = tree;
while (not isLeaf(node)) {
if (index == node.value) {
return index;
} else if (index < node.value) {
if (node.left != null) {
node = node.left;
}
} else /* if (index > node.value) */ {
if (node.right != null) {
node = node.right;
}
}
}
// We now are at a leaf such that there is no value of the tree between index and node.value
// Because of this, if index <= node.value, we are done
if (index <= node.value) {
return node.value;
}
// If index > node.value, we want to find the node right after the current node in an infix ordering of the tree
// To do this, while we are the right child of our parent, we go up
while (node == node.parent.right) {// node.parent can't be null, by (1)
node = node.parent;
}
// here, node == node.parent.left and the leaf we started at is node.right.right.[...].right so the leaf is the last node in the infix traversal of node.parent.left, and so the next node is node.parent.
return node.parent.value;
}
(1)
: The only way for node.parent
to become null at some point is that the leaf we started at is root.right.[...].right
. We know that its value will be n-1
, and that all other values will be smaller. In particular, if the index
we have to myfind
wasn't absurd, it was such that index <= n-1
and so we returned at line 20.
The method of building two data structures for a signature and combining them can be used in many contexts: if you have two implementations of an interface with complexities (and a complexity of $+\infty$ to mean that the method is not implemented), you can combine them by taking both underlying structures, making "set" operations act on both (so that their complexity is the sum of the complexities of the two previous implementations) and the "get" operations use the fastest implementation of the two (so that the complexity is the min of the two complexities of the two previous implementations).