如何在不改变相对位置的情况下将整数数组排序为负、零、正部分?
给出一个 O(n) 算法,该算法以数组 S 作为输入,然后将 S 分为三个集合:负数、零和正数。展示如何就地实现这一点,即不分配新内存。而且你必须保持数字的相对顺序。 例如: {-1, 4, 0, -2, 1, 2} ==> {-1, -2, 0, 4, 1, 2}
我不确定是否存在这样的解决方案。我能想到的最佳解决方案是:
解决方案 1:使用额外的整数数组,然后遍历整个数组以获取负数,然后获取 0,然后获取正数。
解决方案2:不保留数字的相对顺序。然后循环数组两次:
template <typename Type>
void Partion(Type *array, int begin, int end, Type v, int &l, int &r)
{
l = begin;
for (int i=begin; i!=end; ++i)
{
if (array[i] < v)
swap(array[i], array[l++]);
}
r = l;
for (int j=l; j!=end; ++j)
{
if (array[j] == v)
swap(array[j], array[r++]);
}
}
Give an O(n) algorithm which takes as input an array S, then divides S into three sets: negatives, zeros, and positives. Show how to implement this in place, that is, without allocating new memory. And you have to keep the number's relative sequence.
for example:
{-1, 4, 0, -2, 1, 2} ==> {-1, -2, 0, 4, 1, 2}
I am not sure whether or not such an solution exits. The best solutions I can think out are:
Solution 1: Using an extra integer array, then traverse the whole array to get negatives, then 0s, then positives.
Solution 2: Do not keep number's relative sequence. Then loop the array two times:
template <typename Type>
void Partion(Type *array, int begin, int end, Type v, int &l, int &r)
{
l = begin;
for (int i=begin; i!=end; ++i)
{
if (array[i] < v)
swap(array[i], array[l++]);
}
r = l;
for (int j=l; j!=end; ++j)
{
if (array[j] == v)
swap(array[j], array[r++]);
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
这是 Edsger Dijkstra 研究的荷兰国旗问题的一个实例。有趣的是,这个问题没有已知的稳定解决方案可以在 O(n) 时间和 O(1) 空间中运行(或者至少在我上次检查文献时,没有已知的解决方案问题确实存在)。
This is an instance of the Dutch national flag problem studied by Edsger Dijkstra. It's interesting in that no stable solution to this problem is known that runs in O(n) time and O(1) space (or at least, the last time I checked the literature, no known solution to the problem exists).
我不确定这是否有帮助,但是稳定划分为三类的要求可以简化为稳定划分为两类的问题:将负数与非负数分开,然后将正数与非正数分开。如果二类问题可以在 O(1) 空间和 O(n) 时间内解决,则可以应用该解决方案两次来解决原始问题。
I'm not sure if this helps, but the requirement to stably partition into three classes can be reduced to the problem of stably partitioning into two classes: separate the negative from non-negative, then the positive from non-positive. If the two-class problem can be solved in O(1) space and O(n) time, the solution can be applied twice to solve the original problem.
零是难以区分的,所以我想你并不关心它们是否会被交换,甚至只是在最后被覆盖(即,在我们完成将正数和负数移动到数组的两端之后,我们只是将中间部分清零)。
如果您正在考虑整数只是更大事物的键的情况,那么情况很可能并非如此 - 您可能希望保留零并稳定分区。但如果不是,这里有两个见解:
首先,您的问题与稳定二进制分区问题相同。
当然,针对您的问题的算法可以实现稳定的二进制分区(只是一个没有零的数组)。相反,如果数组有零,您仍然可以使用二进制分区来完成脏工作:直接扫描数组,将遇到的每个零与下一个负值交换(跟踪它在哪里,这样您就不会这样做) n^2 整体扫描),结果是
[混合 -,+][可能有额外的零][混合 0,+]。
然后进行两个二进制分区以获得
[-][+][0][+]
并将 + 值移动以获得所需的结果。
AFAIK 对于二进制分区,您可以选择 stable、in-place 和 O(n) 中的任意两个。所以看起来你运气不好,但显然就地 O(n*log n) 算法被称为使用 log(n) 暂存空间的 O(n) 算法。
其次,如果你能保证零的数量至少为f(n),那么零可以弥补暂存空间的不足;在 O(n^2/f(n)) 时间内获得稳定的就地分区很简单。特别是,如果零至少是数组的某个常数部分,则只需运行以下两个步骤即可获得 O(n) 运行时间,直到完成:
如果零与其他类型一样多,则在执行 1 然后 2 然后再次 1 后完成此操作。
Zeros are indistinguishable so I presume you don't care whether they get swapped around or even simply overwritten at the end (i.e. we just zero out the middle part after we've finished getting the positive and negative numbers moved to opposite ends of the array).
If you're looking at a situation where the integers are just keys for something bigger, this may well not be the case- you may want zeros preserved and stably partitioned. But if not, here's two insights:
First, your problem is identical to the stable binary partition problem.
An algorithm for your problem of course does stable binary partitions (just an array with no zeros). Contrariwise, if the array has zeros you can still use a binary partition to do the dirty work: scan right through the array, swapping each zero you come across with the next negative value (keeping track of where that was so you don't do n^2 overall scanning), resulting in
[mixed -,+][possibly extra zeros][mixed 0,+].
Then you do two binary partitions to get
[-][+][0][+]
and shift the + values over to get the desired result.
AFAIK with binary partitions you can choose any two of stable, in-place, and O(n). So it looks like you're outta luck, but apparently an in-place O(n*log n) algorithm is known as is an O(n) algorithm using log(n) scratch space.
Second, if you can guarantee that the number of zeros will be at least f(n), the zeros can compensate for the lack of scratch space; it's simple to get a stable in-place partition in time O(n^2/f(n)). In particular, if the zeros will be at least some constant fraction of the array, you get O(n) runtime by just running these two steps till you're done:
If zeros are just as plentiful as either of the other types, this is done after doing 1 then 2 then 1 again.
难道不能简单地使用仅检查符号的自定义比较器执行的任何“稳定排序”来完成此操作吗?
编辑:
不,那是 O(n log n)。
您可以在线性时间内做的一件事就是减少问题。由于零无法排序(如何区分一个和另一个?),因此您可以遍历数组,跳过零并用非零值填充。然后在末尾添加正确数量的零。
现在您可以忽略最后一部分,问题就变成为 0 左右的稳定分区找到 O(n) 算法。不幸的是,
stable_partition
函数来自 c++ stl 的比较只有 O(n) 次,但如果没有额外的空间可用,则需要 O(n log n) 次交换。不过这篇文章:"稳定线性时间内的最小空间划分” 似乎表明它在 O(n) 内是可能的。我想我对它的理解还不够清楚,无法在这里清楚地总结它。
如果可行,最后一步是将零插入分区之间,这也是 O(n),因为零没有需要维护的顺序。
Can't this be done simply using any "stable sort" performed with a custom comparitor which only checks the sign?
Edit:
No, that's O(n log n).
One thing you can do in linear time is reduce the problem. Since the zeros can't be ordered (how do you tell one from the other?), you can make a pass where you walk through the array, skipping the zeroes and filling in with the non-zero values. Then add the correct number of zeros at the end.
Now you can ignore the last section and the problem becomes finding an O(n) algorithm for a stable partition around 0. Unfortunately, the
stable_partition
function from the c++ stl has only O(n) comparisons, but O(n log n) swaps if no additional space is available.However, this article: "Stable minimum space partitioning in linear time" seems to indicate that it is possible in O(n). I don't think I understand it well enough to summarize it clearly here.
If that works, The final step is to insert the zeros back inbetween the partitions, which is also O(n), since the zeros have no order to maintain.
C++ 库有一个
stable_partition
算法,该算法需要当就地运行时,n 次比较和 O(n log n) 次交换。正如@Ted 指出,该问题需要该算法的两次应用。
The C++ library has a
stable_partition
algorithm which requires n comparisons and O(n log n) swaps when it runs in-place.As @Ted points out, the problem requires two applications of this algorithm.