I haven't understood where it's useful.
Let's say you
xi in ascending order with
i=1..N in a list or a tree or a heap, and you want to construct a perfect binary search tree out of them. (One example is when you fully balance a binary search tree.) Then,
k(
i,
N) gives you the index in an array-based binary tree, where element
xi is located at. It turns out that even though the formula for
k(
i,
N) looks ugly, current processors can do it faster than the amortized average latency in random memory accesses. So, it is basically as cheap as a scatter-gather copy of the tree; you get the binary search tree construction for free.
Analogously, if you do have a perfect binary tree stored in an array, and you want to access the
ith element, it is at index
k(
i,
N). Or, if you have an element at index
k, it is
i(
k,
N)'th in the tree; no need to backtrack to root or anything like that.
In my case, I wanted to use something I call
chunked binary search trees (although I think mathematicians use
partitioned instead). The idea is that because our computers (both caches and storage media) store data in chunks (
pages or
segments), to efficiently locate sorted data, we store the most often used elements in the very first chunk. Consider a binary search among
N elements: every search checks the element at
N/2. On average, almost half the searches check the element at 3
N/4, and almost half the element at
N/4. We store these in the very first tree, so that all searches use the very first tree; almost all searches use either the second or third tree; almost all searches use the fourth, fifth, sixth, or seventh tree, and so on. See how the probability of access is 2
1-L, where
L is the level of the subtree? We end up with a binary search tree of binary search trees, and the level of the tree directly reflects the probability of that particular tree needed for an uniformly random search. Each tree is up to one chunk in size, and they are stored in the
"storage order". (The contents of each tree can be in linear order,
"in-order".)
With this scheme,
the number of chunks needed to find the element with the desired value is minimized. It is perfect for read-only memory-mapping large data sets, as you can let the kernel manage which pages of the mapping are in memory, and which will be read from storage when next needed; it will respond dynamically to memory pressure, so you'll get the maximum benefit of available memory with automatic resident set shrinking when other tasks need the RAM.
While the formulae for
i(
k,
N) and
k(
i,
N) look ugly, they are trivial for current (desktop and server) processors; they contain hardware instructions for the operations, and can evaluate the expressions in just a few cycles. The main problem with this scheme is that I have not yet found an efficient enough way to describe it to other programmers; and the C code that implements it, tends to utterly confound and exasperate anyone who does not understand the underlying idea first.