优化算法从O(n ^ 3)到O(n ^ 2)

时间:2014-01-10 01:27:06

标签: java algorithm optimization

我想解决的问题如下:

  

假设您在二维空间中获得了一组点以及如何   我们可以得到最大数量的共线点。

我在Java中解决了这个问题。 首先,我创建了一个检查线性度的方法:

return (y1 - y2) * (x1 - x3) = (y1 - y3) * (x1 - x2);

然后我使用了三个for循环,这使得我的算法为O(n ^ 3)。但我试图看看这是否可以减少到O(n ^ 2)。

在网上搜索后,我发现我的实现非常类似于什么here。所以问题是我们如何才能提高复杂性。任何一个例子都会很棒。

这就是我最终做的事情:

int p = 2; 
for (int i = 0; i < points.lenght(); i++) {
    for (int j = i+1; j < points.length(); j++) {
        int count = 2;
        for (int k =0; i < points.length(); k++) {
            if (k == i || k == j)
                 continue;
            //use linearity function to check if they are linear...    
        }
        p = max(p,count);
    }
}

5 个答案:

答案 0 :(得分:4)

我找到了与@hqt解决方案非常相似的内容,并希望详细说明他们遗漏的细节。

如果它们的比率dxdy之比(即斜率)相同,则此类的两个元素相等。

static class Direction {
    Direction(Point p, Point q) {
        // handle anti-parallel via normalization
        // by making (dx, dy) lexicographically non-negative
        if (p.x > q.x) {
          dx = p.x - q.x;
          dy = p.y - q.y;
        } else if (p.x < q.x) {
          dx = q.x - p.x;
          dy = q.y - p.y;
        } else {
          dx = 0;
          dy = Math.abs(p.y - q.y);
        }
    }

    public boolean equals(Object obj) {
        if (obj==this) return true;
        if (!(obj instanceof Direction)) return false;
        final Direction other = (Direction) obj;
        return dx * other.dy == other.dx * dy; // avoid division
    }

    public int hashCode() {
        // pretty hacky, but round-off error is no problem here
        return dy==0 ? 42 : Float.floatToIntBits((float) dx / dy);
    }

    private final int dx, dy;
}

现在通过遍历所有对(复杂Multimap<Direction, PointPair>)来填充番石榴O(n*n)。迭代所有键(即方向)并通过union find algorithm处理List<PointPair>。找到的分区是一对共线点。如果有k个共线点,那么您将找到一个包含所有对的点。

由于联合查找算法,复杂度为O(n*n*log(n)),避免排序没有帮助。

答案 1 :(得分:3)

你可以使用Ox之间的两点之间的角度系数来解决这个问题。例如,对于3个点:A B C.当且仅当线AB和线AC与Ox线具有相同的角系数时它们是共线的。所以,这是我的伪代码:

// Type : an object to store information to use later
List<Type> res = new ArrayList<Type>();  
for (int i = 0; i < points.lenght(); i++) {
    for (int j = i+1; j < points.length(); j++) {
       double coefficient = CoeffiecientBetweenTwoLine(
                   line(points[i], points[j]), line((0,0), (0,1));
       res.add(new Type(points[i], points[j], coefficient);
    }
}

之后,您使用QuickSort,再次按照Coefficient在List基础上进行排序。任何系数都等于,我们可以知道哪些点是共线的。此算法的复杂性为O(N^2logN)(通过使用O(N^2)元素对列表进行排序,仅构建列表所需的O(N^2)

<强> @Edit: 那么当我们显示相等的系数时,我们怎么能知道有多少个共线? 有很多方法可以解决这个问题。

  • 在排序步骤中,您可以按第一个参数排序(是哪个点 那条线)当两个系数相等时。例如。排序后, 结果应该是(在这种情况下,如果1 3和4是共线的):

    (1 3) (1 4) (3 4)

从上面的建筑物中,你只需要看到1的条纹,在这个例子中,是2.所以结果应该是3.(总是k + 1)

  • 使用公式:因为始终等于的对数:n*(n-1)/2 。所以,你将拥有:n*(n-1)/2 = 3。你可以知道n = 3(n> = 0)。这意味着你可以在这里解决二次方程(但不是 很难,因为你总是知道它有解决方案,而且只是得到 一个积极的解决方案

编辑2 知道有多少共线点的上述步骤不正确,因为例如,A B和C D是两条平行线(并且线AB与线CD不同),结果,它们仍然具有与Ox相同的系数。所以,我认为要解决这个问题,你可以使用Union-Find数据结构来解决这个问题。步骤将是:

  1. 再次对角系数进行排序 例如:(1 2 3 4)是共线的,它们与(5,6,7)平行,而点8在其他地方。因此,排序后,结果应为:

    (1 2)(1 3)(1 4)(2 3)(2 4)(5 6)(5,7)(6,7)角系数等于,但是在两条不同的线上

    (1,5)(1,6).. //在两组平行线之间会有一对连接。 (1,8)

    (5,8)(3,8).... //随机顺序。因为不知道。

  2. 使用Union-Find Data结构连接树:从第二个元素开始迭代,如果您看到它的角度系数等于之前,则加入自身并加入之前的元素。例如,

    (1,3)==(1,2):加入1和2,加入1和3.

    (1,4)==(1,3):加入1和3,加入1和4. ....

    (5,6):加入2和4,加入5和6.

    (5,7):加入5和7,加入5和6 ...

    (1,8):不加入任何东西。 (5,8):不加入任何东西......

  3. 完成此步骤后。你所拥有的只是一棵多树,在每棵树中,是一组它们共线的点。

    在上面的步骤中,您会看到一些对多次加入。您可以通过标记简单地修复它,如果它们已经加入,则忽略以提高性能。

    @:我认为这个解决方案并不好,我只是通过脑力思考,而不是真正的算法背后。所以,任何其他明确的想法,请告诉我。

答案 2 :(得分:2)

尝试下面

    //just to create random 15 points
    Random random = new Random();
    ArrayList<Point> points = new ArrayList<Point>();
    for(int i = 0; i < 15; i++){
        Point p = new Point(random.nextInt(3), random.nextInt(3));
        System.out.println("added x = " + p.x + " y = " + p.y);
        points.add(p);
    }

    //code to count max colinear points
    int p = 0;
    for(int i = 0; i < points.size() -1; i++){
        int colinear_with_x = 1;
        int colinear_with_y = 1;
        for(int j = i + 1; j < points.size(); j++){
            if(points.get(i).x == points.get(j).x){
                colinear_with_x++;
            }
            if(points.get(i).y == points.get(j).y){
                colinear_with_y++;
            }
        }
        p = max(p,colinear_with_x,colinear_with_y);
    }

答案 3 :(得分:2)

一种严重依赖于良好散列映射的方法:

关键是使用线性方程(定义一条直线),这样就可以得到一条沿

线的地图
map<key=(vector, point), value=quantity> pointsOnLine

其中向量定义两点确定的线性函数。

然后你迭代所有n个点:

maxPoints = 2
for i=1 to n
  for j=i+1 to n
    newKey = lineParametersFromPoints(point[i], point[j])
    if pointsOnLine.contains(newKey)
      pointsOnLine[key] += 1
      if maxPoints < pointsOnLine[key]
        maxPoints = pointsOnLine[key]
    else
      pointsOnLine.add(key)
      pointsOnLine[key] = 2

maxPoints则包含最大共线点数。

请注意(这可能是最重要的),地图的哈希比较函数必须检查两行表示相同的线性函数,即使向量是反平行的或两条线上的点不一样(但是一条线满足另一条方程)。

这种方法当然严重依赖于具有快速访问和插入时间的地图。

答案 4 :(得分:1)

算法: - 可以在O(N^2*logN)中执行的简单算法: -

  
      
  1. 选择一个点p。
  2.   
  3. 找到从p到所有其他点的斜率
  4.   
  5. 根据斜率对点进行排序
  6.   
  7. 扫描已排序的数组以检查具有相同斜率值的最大连续点
  8.   
  9. 最大值+ 1是与p包含的最大点。
  10.   
  11. 对所有点执行1到5并找到找到的最大共线点
  12.   

时间复杂度: O(N)用于斜率计算,O(NlogN)用于排序,O(N^2*logN)用于所有N点。

对于斜坡和点,

空间复杂度 O(N)