Your solution using the native sort on the vector is a good one, and your subsequent relocate-and-sort are relatively fast too. There's no scalability problem here in terms of performance, because they are no worse than the sort, but I can't help but think that there's a better way to do this. Why have the same scalability complexity of the sort, when you can do better....?
The ordering/sorting of the data is a bit of a red herring. Rather, it's not a red-herring, it's a minor complication. The sorting is the first part of a two part process, and the problem should be considered as two parts. You should be able to rearrange the even/odd indices of any input vector, whether it's sorted or not. You should be able to relocate the data regardless of the values, simply based on the index.
To make this obvious, there should be two functions, a 'prepare' function that sets the vector up in the input order, and then the 'real' function which rearranges the values in the even/odd index order, and the second function should be indifferent to the type of data stored in the vector. So, consider a test function that looks like:
void test( std::vector<int>& input, const std::vector<int>& expected, const std::string & testName)
{
std::cout << "Test case : " << testName << "\n";
prepareEntries(input);
shuffleEvenOdd(input);
....
In an actual implementation, the prepareEntries
will sort the data.
My particular interest is in the second stage of the process, the shuffleEvenOdd
. This intrigues me, and is what I have studied, and reduced to an \$O(n)\$ operation.
There's a "simple" function you can apply to each index in the vector, and determine, from that where each value should go. The new location of any index value is given by:
int moveTo(const int index, const int right) {
const int shift = index >> 1;
return ((index & 1) == 0) ? shift : (right - shift);
}
The way that works, is that odd numbers are positioned relative to the right-side of the vector, and even values are relative to the left. If the value is even, we put it at the left-index + half the value. If the value is odd, we position at the right-index - half the value.
An odd index 5 in a vector of size 11 (which will have the right-most index of 10), will be at 10 - (5/2)
, or 8
... the resulting positions of each input index:
0 1 2 3 4 5 6 7 8 9 10
will be:
0 2 4 6 8 10 9 7 5 3 1
Note how index 5 ended at position 8.
Using the above function, you can create a 'rotation' in the vector. Take an index in the vector, finc out where that index needs to be relocated, take the value from the target spot and save it in a temp variable, then put the source value in to the new location. Then find the new location of the value we just pulled, and save away the value at that spot, and replace it, and so on, until we get back to where we started.
Once we get around the loop, we stop, and we have 'rotated all the values tghat form a sequence in the input vector.
Note that there may be multiple discrete 'loops' in the vector. For example, in a vector of size 5, there are actually 3 loops:
0, 1, 2, 3, 4
Loop 1 is at index 0. When we apply as the source index to our relocation function, the result is the value 0. It is a single-item loop.
Loop 2 is at index 1. Look at the index 1 sequence:
0, 1, 2, 3, 4
# ^
Index 1 belongs at index 4, so we save 4 in a temp value, and put index 1 at index 4:
0, 1, 2, 3, 1 (4)
Now, index 4, in our function, belongs at position 2, so we save away the index 2 value, and put the index 4 value there instead:
0, 1, 4, 3, 1 (2)
The value at index 2 now belongs at index 1, so we do that move (and save 1 away):
0, 2, 4, 3, 1 (1)
At this point, we have looped back to index 1, so we stop this loop.
Note that we have not yet visited index 3, it is also a discrete loop. Index 3 in our function returns itself to index 3.
If you consider that index 0 always forms a discrete loop, then you can skip it.
Still, larger vectors have more complicated multiple loops.
Here is a function that will rotate a single discrete loop inside a vector, starting a specific index:
int moveTo(const int index, const int right) {
const int shift = index >> 1;
return ((index & 1) == 0) ? shift : (right - shift);
}
void rotate(std::vector<int>& nums, const int start) {
const int right = nums.size() - 1;
int index = start;
int buffer = nums[index];
do {
const int npos = moveTo(index, right);
const int tmp = nums[npos];
nums[npos] = buffer;
buffer = tmp;
index = npos;
} while (index != start);
}
The challenge here is to find all the discrete loops inside a vector, and to call the above function just once per loop.... to solve that, I recommend having a 'seen' vector of bool, that allows you to identify which indices have been visited.
Combining the seen
vector with the various loop rotations, allows you to iterate the vector twice, once looking for unseen values, and the other doing actual rotations. Since each seen member is visited once, and since each value is rotated just once, the result is that the overall solution is a regular \$O(n)\$ loop.
This reduces the complexity from \$O(n \log(n))\$ that your solution currently has.
Now, here we can prove that the actual values in the vector are not significant any more, and all that's important is the index. We can make the method a template method, and it will work on any vector data:
template<class T>
int rotate(std::vector<T>& values, const int start, std::vector<bool>& seen) {
...
T buffer = nums[index];
...
}
template<class T>
void shuffleEvenOdd(std::vector<T>& values) {
....
}
I have kept the same variable names as you had, but the verctors are now templates, not <int>
....
int moveTo(const int index, const int right) {
const int shift = index >> 1;
return ((index & 1) == 0) ? shift : (right - shift);
}
template<class T>
int rotate(std::vector<T>& nums, const int start, std::vector<bool>& seen) {
const int right = nums.size() - 1;
int index = start;
T buffer = nums[index];
int count = 0;
do {
const int npos = moveTo(index, right);
const T tmp = nums[npos];
nums[npos] = buffer;
buffer = tmp;
index = npos;
count++;
seen[npos] = true;
} while (index != start);
return count;
}
template<class T>
void shuffleEvenOdd(std::vector<T>& nums) {
const int sz = nums.size();
if (sz < 3) {
return;
}
std::vector<bool> seen = std::vector<bool>(sz, false);
int count = 0;
for (int i = 0; i < sz && count < sz; i++) {
if (!seen[i]) {
count += rotate(nums, i, seen);
}
}
}
I have put this, with an additional test, up on Ideone