争论我的算法是正确的并且在界限范围内

时间:2013-01-07 18:49:16

标签: performance algorithm correctness

我正在研究一些算法分配,并且被问到如下:在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循环...有人能给我一些指向正确的直接投影吗?

6 个答案:

答案 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 不会更改。因此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循环,直到firstWhitePositionlastRedPosition相遇。很明显,这两个变量一起遍历数组。

答案 4 :(得分:0)

从技术上讲,这是O(N);然而,从性能的角度来看,它是O(2N)意味着它在平均情况下的速度是可能的一半。那是因为你的外部循环每个元素执行一次,并且它们之间的内部循环遍历每个元素,这意味着迭代次数是元素的两倍。

你有正确的想法,但是如果你“知道”所有r都在{{1}的左边,你就会停止,而不是遍历每个元素的外部循环。 }} S'

换句话说,首先从数组的右端扫描w(应该在左侧),从左侧扫描r(应该去在右边)。如果w位于w的左侧,则应交换它们并继续;但是,如果您的“最后一个r”索引位于“第一个W”索引的左侧,则表示您已完成并应终止整个函数。相反,您当前的实现保持循环(和交换)。这是效率低下的问题。我会用Henry建议用while循环替换外部for循环,终止条件是最后w位于第一个r的左侧。

答案 5 :(得分:0)

  1. 通过数组一次,计算'r'和'w'的数量,将它们存储为numberR和numberW。将非'r'和非'w'字符存储在名为S2的StringBuffer中。

  2. 将numberR'r打印到新的StringBuffer S1中。将S2添加到S1。将'w'的数字W附加到S1。

  3. 从StringBuffer获取字符数组。

  4. 总费用:O(n)