英文:
Can this algorithm be implemented in O(N) time in C++?
问题
我正在尝试在C++中实现一个函数,该函数接受一个整数数组并按以下方式返回相同的数组,排序如下(我对C++和计算机科学都是初学者):
- 所有奇数在所有偶数之前。
- 奇数将保持其原始相对顺序。
- 偶数将以相反的顺序(相对于其原始顺序)放置。
所以如果 arr = [5, 2, 11, 7, 6, 4],那么输出将是 [5, 11, 7, 4, 6, 2]。
是否可以在O(n)时间内按照上述规定对数组进行排序?如果可以的话,提示会很有帮助,我会尝试自己找出答案!
我想到的最有效的解决方案是:
-
如果数组的第i个元素是偶数,则与尚未交换的数组中的最后一个偶数元素交换。
-
如果第i个元素是奇数,则将其与数组中的第[i-1]个元素交换,直到第[i-1]个元素为奇数或达到数组的开头。
-
移至数组中的下一个元素。
我已经编写了代码,它可以工作,但在最坏的情况下,似乎需要O(n^2)的时间,因为基本上您必须遍历数组一次以获取正确的偶数相对顺序,然后再次遍历数组以在偶数之前获取所有奇数。
我很难想出一个O(n)的实现,因为我无法想出一种在列表的一次遍历中确保所有元素都被放置在数组中的适当位置的方法。基本上,我可以想到的所有算法都需要遍历数组,然后进行某种类型的排序。在这种情况下,是否甚至可能有O(n)的算法?
当然,您可以通过只创建一个新数组并将元素按适当的顺序复制到其中,然后返回新数组来完成。但这不那么有趣,我想更好地了解如何实现高效的算法。
英文:
I'm trying to implement a function in C++ that takes an array of integers and returns the same array, sorted as follows (I am a beginner to both C++ and CS in general)
- All the odd numbers come before all the even numbers
- The odd numbers will keep their original relative order
- The even numbers will be placed in a reversed order (relative to their original order).
So if arr = [5, 2, 11, 7, 6, 4], then output would be [5, 11, 7, 4, 6, 2].
Is it possible to sort the array as specified above in O(n) time? If so, a hint would be helpful and I will try and figure it out on my own!
The most efficient solution I could think of was:
if ith element of array is even, then swap with the last even element in the array that has not yet been swapped
if the ith element is odd, swap it with the [i-1]th element in the array until the [i-1]th element is odd or you reach the beginning of the array
move onto the next element in the array
I've coded it up and it works, but this seems to run in O(n^2) time in worst case, since basically you have to traverse the array once to get the evens in the right relative order, and then go back through the array to get all the odds before the evens.
I'm having a hard time thinking of an O(n) implementation, as I can't think of a way in one pass of the list to ensure that all the elements are put into the appropriate position in the array. Basically all algorithms I can think of require a pass through the array and then some type of sort. Is an O(n) algorithm even possible in this case?
Of course you could do it by just creating a new array and copying the elements in the appropriate order and then returning the new array, but that's less interesting and I want to get a better understanding of implementing efficient algorithms
答案1
得分: 2
你可以使用标准库来完成这个任务。
首先,执行一个 stable_partition,使用一个返回true的谓词,如果一个数字是奇数。这将把所有奇数值移到左边,保持所有值的相对顺序不变。
该函数返回一个指向右侧起始位置的迭代器,因此在那一点上,你可以简单地 reverse 数据。
stable_partition
的最坏情况时间复杂度为 O(N.logN),而 reverse
的时间复杂度为 O(N)。因此,这个解决方案的最坏情况时间复杂度为 O(N.logN),存储复杂度为 O(1)。
实际上,更可能是 O(N)。文档对于如何实现 stable_partition
有一些不清楚的地方,但它确实指出该函数分配了内存。在这种情况下,它是 O(N),只有在分配失败时才会回退到 O(N.logN) 的行为。
#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>
inline bool is_odd(int value)
{
return value % 2 != 0;
}
void show(const std::vector<int>& values)
{
std::copy(values.begin(), values.end(), std::ostream_iterator<int>(std::cout, " "));
std::cout << "\n";
}
int main()
{
std::vector<int> values{ 5, 2, 11, 7, 6, 4 };
show(values);
// 对数组进行分割,将奇数排在前面,然后是偶数,保持顺序不变
auto it = std::stable_partition(values.begin(), values.end(), is_odd);
show(values);
// 反转偶数
std::reverse(it, values.end());
show(values);
}
英文:
You can use the standard library for this.
First, perform a stable_partition, using a predicate that returns true if a number is odd. This will shift all the odd values to the left-hand side, preserving the relative order of all values.
The function returns an iterator to the beginning of the right-hand side, so at that point you can simply reverse that data.
The stable_partition
is O(N.logN) worst-case, and the reverse
is O(N). So this solution is at worst O(N.logN) time complexity with O(1) storage complexity.
In fact, it's more likely to be O(N). The documentation is a little unclear about how stable_partition
should be implemented, but does indicate that the function allocates memory. In that case, it's O(N), only falling back to O(N.logN) behavior if allocation fails.
#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>
inline bool is_odd(int value)
{
return value % 2 != 0;
}
void show(const std::vector<int>& values)
{
std::copy(values.begin(), values.end(), std::ostream_iterator<int>(std::cout, " "));
std::cout << "\n";
}
int main()
{
std::vector<int> values{ 5, 2, 11, 7, 6, 4 };
show(values);
// Partition the array, placing odd numbers first, then even numbers, preserving order
auto it = std::stable_partition(values.begin(), values.end(), is_odd);
show(values);
// Reverse the even numbers
std::reverse(it, values.end());
show(values);
}
答案2
得分: 0
你上一段提到的"当然,你可以通过创建一个新数组来做到[...]",清楚地表明你不仅寻求线性时间,还寻求次线性空间。你尝试使用交换操作表明你甚至试图只使用常数空间。
是的,这是可能的,可以参考在线性时间内进行稳定的最小空间分割。我认为的关键思想是将长度为n的数组视为大小为log n的块序列,然后使每个块只包含偶数或奇数,然后对块序列进行排序。该排序使用了一个具有O(m log m)比较和O(m)移动的算法,其中m是元素的数量。在我们的情况下,元素是块,我们有m = n / (log n)个块。因此,比较的数量为O(m log m) = O(n/logn log (n/logn)) ≤ O(n/logn logn) = O(n)。移动的数量是O(m) = O(n/logn) 块 移动,由于块的大小为logn,这是O(n)移动数组元素。因此,总体时间复杂度为O(n)。而且空间复杂度为O(1)。
如果我记得正确,那篇论文相对容易理解,但其中使用的排序算法在一篇相当难以理解的论文中,甚至cstheory的顶级用户称之为"相当复杂"。也许现在有更简单的方法,这是我所知道的唯一方法。
英文:
Your last paragraph saying "Of course you could do it by just creating a new array and [...]" makes it clear you're looking not just for linear time but also for sublinear space. Your attempt with swaps indicates you're even trying to use only constant space.
Yes, that is possible, see Stable Minimum Space Partitioning in Linear Time. The key idea (in my opinion) is to view the array of length n as a sequence of blocks of size log n, then make each block have either only even numbers or only odd numbers, and then sort the sequence of blocks. That sorting uses an algorithm with O(m log m) comparisons and O(m) moves, where m is the number of elements. In our case, the elements are the blocks and we have m = n / (log n) blocks. So the number of comparisons is O(m log m) = O(n/logn log (n/logn)) ≤ O(n/logn logn) = O(n). And the number of moves is O(m) = O(n/logn) block moves, and since blocks have size logn, that's O(n) moves of your array elements. So overall O(n) time. And all with O(1) space.
If I remember correctly, that paper was relatively easy to understand, but that sorting algorithm used in there is in a rather unaccessible paper that even cstheory's top user called "quite complicated". Maybe there are simpler ways now, this is the only one I'm aware of.
答案3
得分: -1
这可以确实在O(N)的时间内完成。以下是一个简单的方法。这是O(N),比分区排序更简单,分区排序首先尝试分配一个完整的数组。它有一个备用方案,但然后变慢了,不再是O(N)。我认为没有比这更有效的方法,因为这是一次遍历,不需要反转。
#include <vector>
std::vector<int> partition_odd_even(const std::vector<int>& v)
{
std::vector<int> ret(v.size());
auto pstart = ret.begin(), pend = ret.end();
for (auto x : v)
{
if (x & 1)
*pstart++ = x;
else
*--pend = x;
}
return ret;
}
int main()
{
std::vector<int> v{ 1,2,3,4,5,6,7 };
auto vout = partition_odd_even(v);
// vout is 1,3,5,7,6,4,2
}
英文:
This can certainly be done O(N). Here's a simple approach. This is O(N) and is simpler than a partition sort which will first attempt to allocate a full array. It has a fallback but then gets slow and isn't O(N). I don't think there is anything more efficient as this is a single pass and doesn't require reversal.
#include <vector>
std::vector<int> partition_odd_even(const std::vector<int>& v)
{
std::vector<int> ret(v.size());
auto pstart = ret.begin(), pend = ret.end();
for (auto x : v)
{
if (x & 1)
*pstart++ = x;
else
*--pend = x;
}
return ret;
}
int main()
{
std::vector<int> v{ 1,2,3,4,5,6,7 };
auto vout = partition_odd_even(v);
// vout is 1,3,5,7,6,4,2
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论