Previously, we discussed an efficient algorithm for partitioning an array. Suppose we want to create a ternary partition, so all entries less than a given value or range come first, all entries equal to a given value or within the given range come second, and all entries greater than the given value or range come last.
For the purpose of this project, we will focus on a ternary partition with respect to a given value.
For example, given the following twenty values
int array[20]{ 4, 5, 5, 9, 9, 2, 0, 0, 0, 5, 3, 6, 7, 3, 9, 2, 9, 3, 4, 5 };
a ternary partition relative to 5 would be
{4, 2, 0, 0, 0, 3, 3, 2, 3, 4, 5, 5, 5, 5, 9, 9, 6, 7, 9, 9}
This is of course, not unique, and another solution would be to simply sort the twenty values:
{0, 0, 0, 2, 2, 3, 3, 3, 4, 4, 5, 5, 5, 5, 6, 7, 9, 9, 9, 9}
Sorting is, however, more expensive. Can you modify the previous partitioning algorithm to come up with some way of doing a ternary partition?
One counter-intuitive solution is as follows:
We will move objects in a similar manner as we did in the previous partitioning algorithm; however, we will do the opposite of what may seem to be most useful: we will copy entries that are equal to the value to either the front or the back of the array.
We will use four indices:
Initially, the first two are 0 and the last two are capacity - 1. At the end of the partitioning phase, it will be true that index_less == index_greater, and that is the location of the 'hole' into which the first that was copied out from array[0] will be placed.
Finally, we will add 1 to the last two entries so that
Once this is achieved, we only need move the entries equal to the value back to the center, and we can use an unordered range swap, as the moved entries only need to satisfy the condition and their relative order does not matter. Once this is done, then:
For example, if 0 is the value, then for the array
{0, -1, 4, -2, 3, 9, 0, -5, -1, 6, -8, 8, 6, 0, 7, 0, -4, 9, -7, 7}
the preliminary partitioned would be
{0, 0, -4, -2, -7, -1, -8, -5, -1, 0, 6, 8, 6, 7, 7, 9, 3, 9, 4, 0}
and when the zeros are moved, we are left with
{-5, -1, -4, -2, -7, -1, -8, 0, 0, 0, 0, 8, 6, 7, 7, 9, 3, 9, 4, 6}
Similarly, the three states for this next array are:
{-8, -6, -9, -5, 0, -4, 1, -2, -7, -6, 5, 8, 3, 9, -2, -1, 2, 5, -5, 0} { 0, -6, -9, -5, -5, -4, -1, -2, -7, -6, -2, -8, 3, 9, 8, 5, 2, 5, 1, 0} {-8, -6, -9, -5, -5, -4, -1, -2, -7, -6, -2, 0, 0, 9, 8, 5, 2, 5, 1, 3}
To return the two indices, we will use the standard library's std::pair class:
std::pair<std::size_t, std::size_t> ternary_partition(...);
The library is
#include <utility>
Rather than calling a constructor, there is a convenient factory, so if we are to return such a pair, we would use
// Assume 'index_less' is the index of the // first equal to the 'value', and // 'index_greater' is the first index of the // first entry greater than the 'value'. return std::make_pair( index_less, index_greater ); }
There are two public member variables, first and second, and these can be accessed quite easily as they are public:
auto bounds{ ternary_partition( array, capacity, f ) }; std::cout << bounds.first << ", " << bounds.second << capacity << std::endl;
You can view this code at replit.com.
In this program, we also offer an alternative, whereby rather than just passing a value, we can pass a comparator that returns either a negative integer, 0 or a positive integer. Anything for which the comparator returns 0 will be placed in the middle third. This allows for a much more general application of this function.