我正在研究一些算法分配,并且被问到如下:在O(n)中对元素'r'或'w'进行排序,这样'r'总是在'w'之前。 因此,如果输入看起来像[w,r,w,r,w,r,w,w],则在算法运行后,数组看起来像[r,r,r,w,w,w,w,w]
从概念上讲,这似乎很明显。我不得不使用两个边界变量来保存第一个'w'元素的位置,一个边界变量保存最后一个'r'元素。
我的代码如下:
char[] A = new char[] { 'w', 'r', 'w', 'r', 'w', 'r', 'w', 'w' };
int lastRedPosition = A.Length - 1;
int firstWhitePosition = 0;
for (int i = firstWhitePosition ; i < A.Length; i++)
{
// determine the first red from the right e.g. the lastred, ending at the firstwhite position
for (int j = lastRedPosition; j > firstWhitePosition; j--)
{
if (A[j] == 'r')
{
lastRedPosition = j;
break;
}
}
for (int k = firstWhitePosition; k < lastRedPosition; k++)
{
if (A[k] == 'w')
{
firstWhitePosition = k;
break;
}
}
A[firstWhitePosition] = 'r';
A[lastRedPosition] = 'w';
}
现在我觉得它在O(n)中直观地运行,尽管有嵌套的for循环。这是因为总而言之,数组只遍历一次。因为我们用lastRedPosition和firstWhitePosition变量跟踪我们的位置。
然而,我发现很难以更正式的方式激励它,因为相同的嵌套for循环...有人能给我一些指向正确的直接投影吗?答案 0 :(得分:4)
这不仅仅是quicksort的迭代1吗?
您从左侧扫描,直到您点击“w”。 然后你从右边扫描,直到你点击'r'。 然后你交换这两个元素并继续。 当两个扫描仪结合在一起时,你会停下来。
那是O(n)。
编辑:例如:
int i = 0, j = n-1;
while(true){
while(a[i]=='r' && i<n) i++;
while(a[j]=='w' && j>0) j--;
if (i >= j) break;
swap(a[i], a[j]);
}
答案 1 :(得分:2)
第一个问题是您的算法不正确。如果数组中只有'w'
s或'r'
s,那么在外部循环结束时仍然会向数组写'w'
和'r'
两者
在这种情况下,您的代码实际上会花费Θ(N²)
时间。假设一切都是'r'
:
char[] A = new char[] { 'r', 'r', 'r', 'r', 'r', 'r', 'r', 'r' };
int lastRedPosition = A.Length - 1;
int firstWhitePosition = 0;
for (int i = firstWhitePosition ; i < A.Length; i++)
{
// determine the first red from the right e.g. the lastred, ending at the firstwhite position
for (int j = lastRedPosition; j > firstWhitePosition; j--)
{
if (A[j] == 'r')
{
lastRedPosition = j;
break;
}
}
j
现在是A.length - 1 - i
。在第一次迭代中,它立即找到了'r'
,之后,已经向数组的最后一个条目写了i
'w'
,lastRedPosition
是该数组的索引。首先,except for the first iteration,
j`只减少一次。
for (int k = firstWhitePosition; k < lastRedPosition; k++)
{
if (A[k] == 'w')
{
firstWhitePosition = k;
break;
}
}
firstWhitePosition
始终为0. k
会递增,直到它变为lastRedPosition
而未找到'w'
,因此firstWhitePosition
不会更改{ EM>。因此k
递增A.length - 1 - i
次。
总计i
总计~N²/2
增量为k
。
A[firstWhitePosition] = 'r';
A[lastRedPosition] = 'w';
}
您必须检查A[firstWhitePosition]
实际上是'w'
还是A[lastRedPosition]
实际'r'
。如果没有,你就完成了。您的外部循环应该检查firstWhitePosition < lastRedPosition
。
答案 2 :(得分:1)
仅仅因为整个数组只被遍历一次并不会自动使其成为O(n)。事实上,在最不理想的情况下(这是big-O的定义),整个数组实际上将遍历两次,第二次遍历发生在外部循环内,使其成为O(n ^ 2)。
如果你可以保证只有r和w将在数组中,O(n)方法只需要通过数组一次,计算有多少r和w,然后自己重建数组。这实际上是2个遍历,使其成为O(2n),但通常会丢弃这些系数。
答案 3 :(得分:1)
更容易看出是否将外部循环转换为while
循环,直到firstWhitePosition
和lastRedPosition
相遇。很明显,这两个变量一起遍历数组。
答案 4 :(得分:0)
从技术上讲,这是O(N);然而,从性能的角度来看,它是O(2N)意味着它在平均情况下的速度是可能的一半。那是因为你的外部循环每个元素执行一次,并且它们之间的内部循环遍历每个元素,这意味着迭代次数是元素的两倍。
你有正确的想法,但是如果你“知道”所有r
都在{{1}的左边,你就会停止,而不是遍历每个元素的外部循环。 }} S'
换句话说,首先从数组的右端扫描w
(应该在左侧),从左侧扫描r
(应该去在右边)。如果w
位于w
的左侧,则应交换它们并继续;但是,如果您的“最后一个r”索引位于“第一个W”索引的左侧,则表示您已完成并应终止整个函数。相反,您当前的实现保持循环(和交换)。这是效率低下的问题。我会用Henry建议用while循环替换外部for循环,终止条件是最后w
位于第一个r
的左侧。
答案 5 :(得分:0)
通过数组一次,计算'r'和'w'的数量,将它们存储为numberR和numberW。将非'r'和非'w'字符存储在名为S2的StringBuffer中。
将numberR'r打印到新的StringBuffer S1中。将S2添加到S1。将'w'的数字W附加到S1。
从StringBuffer获取字符数组。
总费用:O(n)