Path to adopt 32-bit implementations for `{KD, Ball}Tree`

## Motivation
Having and using 32-bit implementations of `{KD, Ball}Tree` allows for better preservation of dtype, lower memory footprint, and more consistent Cython code.

## Strategy
Work has already been started in #25914 to add the code for the new 32-bit implementations. These will not be directly used yet, and the PR instead focuses on the actual creation of the new classes. Consequently, `{KD, Ball}Tree` are bound to `{KD, Ball}Tree64` for consistency and backwards compatibility.

Following this PR, we will need to begin an API deprecation to move users away from constructing trees directly, and instead using a factory method (similar to `DistanceMetric.get_metric`). Then, later, we can separate `{KD, Ball}Tree` from `{KD, Ball}Tree64` so that we have a singular dispatcher `{KD, Ball}Tree` and two type-specialized implementations. 

The deprecation process involves:
1. Introducing `get_tree(...)` to `{KD, Ball}Tree64` with the same signature of `BinaryTree.__init__`, which is in charge of constructing the type-specialized trees directly.
2. Adding a `FutureWarning` to `BinaryTree.__init__` to begin deprecation, and suppressing the warning using a context manager in `{KD, Ball}Tree64.get_tree` to enforce it as the "correct" way to construct the trees.
3. **Complete the deprecation** by introducing a separate `{KD, Ball}Tree` into the hierarchy which comes with the same `get_tree` method and fully remove the `FutureWarning` from `{KD, Ball}Tree{32, 64}.__init__` since they should no longer be directly constructed, as well as getting rid of the `{KD, Ball}Tree{32, 64}.get_tree` method since it should only exist in `{KD, Ball}Tree`.

At the end, the expected API is:
```python
from sklearn.neighbors import KDTree

# Data
X = ...

# tree is thus an instance of KDTree{32, 64} based on X.dtype
tree = KDTree.get_tree(X, ...)
```

cc: @thomasjpfan @jjerphan @OmarManzoor

COMMENT:
> I still prefer to avoid the extra indirection of having a `_tree` attribute if possible.

What are the reasons to avoid the extra indirection? If the reason is boiler-plate code, we could define `__getattr__` to redirect all calls to the component (this would remove inspectability and notably docstring, which we do not want). If the reason is dispatch cost, I think this would be negligible for most calls, but a cost would be present.

I considered this solution acceptable since some estimators and transformers in scikit-learn already forward calls to components (for instance, this is the case for `Ridge{Classifier,}{CV,}`).

> What about defining `{KD, Ball}Tree.__new__()` so that it directly produces the type-specialized implementation? 

I like the use of `__new__`, but it returns instances that leak implementations details; as you have shown:

```python
>>> assert isinstance(tree64, Tree64)
>>> assert isinstance(tree32, Tree32)
>>> tree64
<__main__.Tree64 at 0x7fc3b1f54a90>
>>> tree32
<__main__.Tree32 at 0x7fc3b1f54df0>
```

I think we must prevent public API from leaking such implementation details to users.

I would wait for a few other maintainers to give their points of view before starting an implementation. What do you think?

Labels: API, Needs Decision, RFC