MAINT, RFC Simplify the Cython code in `sklearn/tree/` by splitting the "Splitter" and "Partitioner" code

### Summary of the problem
There are quite a number of GH issues with the label `tree` (https://github.com/scikit-learn/scikit-learn/issues?page=2&q=is%3Aopen+is%3Aissue+label%3Amodule%3Atree).

However, the code is a bit hard to approach as there's so many moving parts. For example, see: https://github.com/scikit-learn/scikit-learn/issues/18448#issuecomment-702598316, where users are intimidated by the Cython codebase. A large part of the complexity comes in the splitter, which contains the most algorithmic logic. I think with the work done by @thomasjpfan recently, we were able to pull apart the idea of a "partitioner" and "splitter" in the trees. 

This problem also arises because https://github.com/scikit-learn/scikit-learn/pull/29437/files#diff-e2cca285e1e883ab1d427120dfa974c1ba83eb6e2f5d5f416bbd99717ca5f5fc makes the code-diff very large inside `_splitter.pyx` file making it also harder to review and understand what changes affect the "splitter" and what changes affect the "partitioner".

### Proposed change
I further propose to split these into separate files, so it is easier to maintain, read and determine what is affecting what. In addition, we can decrease the code complexity by creating an abstract base class for the `DensePartitioner` and `SparsePartitioner`.

See the following [PR](https://github.com/scikit-learn/scikit-learn/pull/29458) for an example of what would change. The `_splitter.pyx` file would decrease by over 800 LOC, while it gets moved to `_partitioner.pyx`.

COMMENT:
>> we can decrease the code complexity by creating an abstract base class for the DensePartitioner and SparsePartitioner

> I think this something that would lead to regression and the current workaround is actually a workaround abstract class.

Yes IIUC that was what was described here: https://github.com/scikit-learn/scikit-learn/pull/25306. However, I'm wondering if the vtable lookup is still incurred if we just have an abstract base class, `@final` decorators on the actual SparsePartitioner, DensePartitoner and fusedtype of the partitioners used in Splitter.

Just to summarize the state of affairs. If the following is the `_partitioner.pyx` file:

```
# _partitioner.pyx
cdef class AbstractPartitioner
     cdef void abstract_methods(self):

@final
cdef class SparsePartitioner(AbstractPartitioner):
     # actual implementation
     cdef inline void abstract_methods(self):

@final
cdef class DensePartitioner(AbstractPartitioner):
     # actual implementation
     cdef inline void abstract_methods(self):
```

then this would get hit w/ vtable lookup(?)

```
# _splitterv1.pyx
cdef node_split(AbstractPartitioner partitioner, …)
      # do stuff
      partitioner.abstract_methods()
```
    
However, I'm wondering if the following `_splitterv2` is hit since the fused type should allow the compiler to know the type during compilation?

```
# _splitterv2.pyx
ctypedef fused Partitioner:
     SparsePartitioner
     DensePartitioner

cdef node_split(Partitioner partitioner, …)
      # do stuff
      partitioner.abstract_methods()
```

Forgive my understanding of vtable lookup if this is naive/incorrect.

Tho its in C++, my guess is this SO post is somewhat related in terms of hitting a vtable lookup when compiling(?): https://stackoverflow.com/questions/64808615/is-a-vtable-lookup-performed-when-indirectly-calling-a-virtual-method-in-a-non-o.

Labels: module:tree, cython