这个O(n ^ 4)算法可以避免吗?

时间:2013-07-16 02:15:56

标签: c arrays

免责声明:这不是一个家庭作业问题。我甚至不上学。

#include <stdio.h>
void printIntersect(float, float, float, float);

int main(){
   int x, y, z, a;
   float slopes[] = {5, 9, 4/3.0f, 2/3.0f, 3};
   float inter[] = {3, 2, 1, 0, 5/3.0f};  

   for(x = 0; x < (sizeof(slopes) / sizeof(slopes[0])) - 1; x++)
       for(y = 1; y < (sizeof(slopes) / sizeof(slopes[0])); y++)
           for(z = 0; z < sizeof(inter) / sizeof(inter[0]); z++)
                  for(a = 0; a < sizeof(inter) / sizeof(inter[0]); a++)
                      if(slopes[x] != slopes[y])
                          printIntersect(slopes[x], slopes[y], inter[z], inter[a]);

   return 0;
}

void printIntersect(float m_one, float m_two, float b_one, float b_two){
    float m_final, b_final, x_intersect;

    m_final = m_one - m_two;
    b_final = b_two - b_one;

    if(m_final < 0 && b_final < 0)
        m_final *= -1.0f, b_final *= -1.0f;

    if (b_final != 0)
        x_intersect = b_final / m_final;
    else
        x_intersect = 0;  

        printf("The intersection of y = %.2fx %c %.2f and y = %.2fx %c %.2f is x = %.2f\n",
            m_one, (b_one < 0) ? '-' : '+', b_one, m_two, (b_two < 0) ? '-' : '+', b_two, x_intersect);


    return;
}

情景:在我的一本C书中有一个我不确定的练习。我得到的问题是我要有两个数组:一个代表一条线的可能斜率,另一个代表所有可能的y截距。目标是使用所有可能的斜率组合和截距与两条线来找到它们的交点。我要忽略平行线和重复线(无论如何都会隐式忽略它们,如果它们不能平行,那么它们就不可能是同一条线)。

假设这是前提(我真的不在乎这一点,它只是一个练习),我编写的程序使用了4个嵌套for循环。你可以看到为什么会让我感到担心,但话说回来,也许其中有4个是必要的。

由于我不能有平行线,我从第一行开始迭代斜率,然后迭代遍历数组中所有其他斜率作为第二行的斜率。通过这种方式,我获得了所有可能的斜坡组合。

拦截是不同的,因为我可以有相同的拦截线,但仍然有不同。因此,这些之间的迭代不需要考虑重复。话虽这么说,我遍历拦截数组的方式应该考虑这两行中的每一个可能的对。

如果这些线不平行,我会调用一个打印x截距的函数。

我的问题是,这个O(n ^ 4)解决方案是否可以避免或至少简化?你对这些处理数组有什么建议吗?

3 个答案:

答案 0 :(得分:3)

如果a斜率和b相交,则可以生成a*b行。有a*b choose 2对线。这大约是a*b*a*b的一半(它是(a*b*(a*b-1)/2)。如果没有其他信息,似乎你必须检查所有这些 - 所以你的算法确实是O(a*a*b*b)

编辑:就答案而言,让我们以不同的方式看问题。给定a斜率和b相交,并通过组合a*b行,我们将拥有多少个交叉点?让我们假设为了简单起见,这组斜率是不同的。

这是一个不同的问题,而不是询问我们给出了n行的交叉点数,因为行的构造方式。给定一个斜率,我们创建b个平行线。鉴于下一个,我们创建另一条b平行线,其中没有一条线与第一组线平行。重复a次。

在一组中,我们没有交叉点,因为它们都是平行的。

在两组之间,我们有多少个交叉路口?一组中的任何一条线都不与另一组中的线平行,因此一组中的每条线将与第二组中的每条线相交一次。由于每个集合都有b行,因此任意两个集合之间都会有b*b个交叉点。

这里我们注意到所有这些行集也共享同一组b个相交。因此,对于与当前相交线组相交的每一组额外线,您将添加(b*b - b)*c = b*c*(b-1)个交点,其中c是已包含的线组数,因为已经考虑了b y截距的交点,因此我们只为已经存在的每组线添加b*(b - 1)个交点。

因此,我们有两组初始b^2,加上b*(b - 1)*2用于添加第三组,加b*(b - 1)*3用于添加第四组等,最多为b*(b-1)*(a-1)添加a集。也就是说,交叉点I的数量为:

I = b^2 + 2b(b-1) + 3b(b-1) + ... + (a-1)b(b-1)

我们可以将b^2重写为b + b(b-1)

I = b + b(b-1) + 2b(b-1) + ... + (a-1)b(b-1)

将最后b(b-1)个词中的常见a-1排除在外:

I = b + b(b-1)[1 + 2 + ... + (a-1)]

我们注意到1x的数字总和为x*(x+1)/2

                (a-1)*a 
I = b + b(b-1)  ------- 
                   2

        a*(a-1)*b*(b-1)
  = b + ---------------
               2

        (a^2 - a)(b^2 - b)
  = b + ------------------
                2

    a^2*b^2 - a^2*b - a*b^2 + a*b + 2b
  = ----------------------------------
                    2

因此,无论您使用何种算法,您都必须生成许多单独的输出,这些输出确实小于a^2*b^2,但不是很大。

答案 1 :(得分:2)

我们已经可以通过改变第二个循环的起始索引将循环减半。

for (y = 1, ...)

for (y = x + 1; ...)

for (x = 0; x < (sizeof(slopes) / sizeof(slopes[0])) - 1; x++)
    for (y = x + 1; y < (sizeof(slopes) / sizeof(slopes[0])); y++)
        for (z = 0; z < sizeof(inter) / sizeof(inter[0]); z++)
            for (a = 0; a < sizeof(inter) / sizeof(inter[0]); a++)
                printIntersect(slopes[x], slopes[y], inter[z], inter[a]);

我也删除了相等性检查,即使jxh指出,slopes可能包含重复的条目。好吧,我这样做是因为你可以删除O(n)中的重复项。

当然,这不会改变算法的总体时间复杂度,但它应该有助于运行时。

答案 2 :(得分:0)

我不相信你可以远离O(M 2 x Y 2 )(M个明显的斜率和Y个截然不同的截距),但你可以过滤你的数组使它们首先是不同的。这使得程序在输入包含许多重复项的情况下运行得更快。此外,在不删除重复的y截距的情况下,您也可能生成并打印重复的线交叉点。

因此,使用qsort您可以对输入进行排序,然后删除重复项。由于排序是O(N x log 2 N),它不会影响算法的整体复杂性(并且在有许多重复的情况下加快它的速度) )。使用散列表可以更快地消除重复,但标准C库不提供(您必须自己找一个或实现一个)。

#define ARRAY_SZ(x) (sizeof(x)/((void *)x == &x ? sizeof(x[0]) : 0))

static int is_equal_float (float a, float b) { /*...*/ }

static int cmp_float (const void *a, const void *b) {
    float af = *(const float *)a;
    float bf = *(const float *)b;
    if (is_equal_float(af, bf)) return 0;
    return (af > bf) - (af < bf);
}

/* ... */
qsort(slopes, ARRAY_SZ(slopes), sizeof(slopes[0]), cmp_float);
qsort(inter, ARRAY_SZ(inter), sizeof(slopes[0]), cmp_float);

for (x = 0, y = 1; y < ARRAY_SZ(slopes) - 1; y++) 
    if (slopes[y] != slopes[x]) slopes[++x] slopes[y];
for (z = 0, a = 1; a < ARRAY_SZ(inter) - 1; a++) 
    if (inter[z] != inter[a]) inter[++z] slopes[a];
M = x+1;
Y = z+1;

for (x = 0; x < M - 1; x++) 
    for (y = x + 1; y < M; y++)
        for (z = 0; z < Y; z++)
            for (a = 0; a < Y; a++)
                printIntersect(slopes[x], slopes[y], inter[z], inter[a]);

我了解如何比较float,但您可以咨询Most effective way for float and double comparison,了解如何以满足您的应用需求的方式进行操作。