1. 程式人生 > >Programming Assignment 3: Pattern Recognition

Programming Assignment 3: Pattern Recognition

程式設計作業一

作業連結:specification & Checklist

我的程式碼:BruteCollinearPoints.java & FastCollinearPoints.java & Point.java

問題簡介

計算機視覺涉及分析視覺影象中的模式並重建產生它們的現實世界物件。該過程通常分為兩個階段:特徵檢測和模式識別。特徵檢測涉及選擇影象的重要特徵;模式識別涉及發現特徵中的模式。我們將研究一個涉及點和線段的特別簡單的模式識別問題。這種模式識別出現在許多其他應用中,例如統計資料分析。

The problem. Given a set of n distinct points in the plane, find every(maximal) line segment that connects a subset of 4 or more of the points.

lines2

給定一些點,要求找到所有至少包含四個點的線段。

任務摘要

Point data type. Create an immutable data type Point that represents a point in the plane by implementing the following API:

public class Point implements Comparable<Point> {
  public Point(int x, int y)                         // constructs the point (x, y)

  public   void draw()                               // draws this point
  public   void drawTo(Point that)                   // draws the line segment from this point to that point
  public String toString()                           // string representation

  public               int compareTo(Point that)     // compare two points by y-coordinates, breaking ties by x-coordinates
  public            double slopeTo(Point that)       // the slope between this point and that point
  public Comparator<Point> slopeOrder()              // compare two points by slopes they make with this point
}

實現表示點的資料型別,要求完成後三個方法,詳細參見:specification

Brute force. Write a program BruteCollinearPoints.java that examines 4 points at a time and checks whether they all lie on the same line segment, returning all such line segments. To check whether the 4 points p, q, r, and s are collinear, check whether the three slopes between p and q, between p and r, and between p and s are all equal.

public class BruteCollinearPoints {
public BruteCollinearPoints(Point[] points)    // finds all line segments containing 4 points
public           int numberOfSegments()        // the number of line segments
public LineSegment[] segments()                // the line segments
}

除了暴力檢測每四個點是否共線(\(n^{4}\)),還有更快的做法,對點 P:

  • 把 P 當做原點。

  • 其它點按和點 P 的斜率(slope)排序。

  • 檢查是否有三個點(或者更多)有同樣的斜率。如果有,那這些點和點 P 就構成目標線段。

lines1

對其它的點,重複上述過程,就能找到所有目標線段,因為排序把斜率一樣的點聚在一起。而這演算法更快是因為瓶頸操作是排序,排序是 nlgn,最後是 \(n^{2}lgn\),也比 \(n^{4}\) 好得多。

Write a program FastCollinearPoints.java that implements this algorithm.

public class FastCollinearPoints {
public FastCollinearPoints(Point[] points)     // finds all line segments containing 4 or more points
public           int numberOfSegments()        // the number of line segments
public LineSegment[] segments()                // the line segments
}

問題分析

仍然按照 Checklist 裡建議的程式設計步驟,先實現 Point.java。下載 Point.java,完成要求實現的方法。實際上,specification 裡說得挺詳細的,沒什麼問題,大概主要是讓我們熟悉下可比較介面和比較器。

  • The compareTo() method should compare points by their y-coordinates, breaking ties by their x-coordinates. Formally, the invoking point (x0, y0) is less than the argument point (x1, y1) if and only if either y0 < y1 or if y0 = y1 and x0 < x1.

  • The slopeTo() method should return the slope between the invoking point (x0, y0) and the argument point (x1, y1), which is given by the formula (y1 − y0) / (x1 − x0). Treat the slope of a horizontal line segment as positive zero; treat the slope of a vertical line segment as positive infinity; treat the slope of a degenerate line segment (between a point and itself) as negative infinity.

  • The slopeOrder() method should return a comparator that compares its two argument points by the slopes they make with the invoking point (x0, y0). Formally, the point (x1, y1) is less than the point (x2, y2) if and only if the slope (y1 − y0) / (x1 − x0) is less than the slope (y2 − y0) / (x2 − x0). Treat horizontal, vertical, and degenerate line segments as in the slopeTo() method.

  • Do not override the equals() or hashCode() methods.

slopeTo() 方法兩個點連線水平時返回正零,Checklist 裡也有解釋。

What does it mean for slopeTo() to return positive zero?

Java (and the IEEE 754 floating-point standard) define two representations of zero: negative zero and positive zero.

double a = 1.0;
double x = (a - a) /  a;   // positive zero ( 0.0)
double y = (a - a) / -a;   // negative zero (-0.0)

Note that while (x == y) is guaranteed to be true, Arrays.sort() treats negative zero as strictly less than positive zero. Thus, to make the specification precise, we require you to return positive zero for horizontal line segments. Unless your program casts to the wrapper type Double (either explicitly or via autoboxing), you probably will not notice any difference in behavior; but, if your program does cast to the wrapper type and fails only on (some) horizontal line segments, this may be the cause.

接著實現 BruteCollinearPoints.java ,邏輯比較簡單,還給了很多提示,也沒問題。

To form a line segment, you need to know its endpoints. One approach is to form a line segment only if the 4 points are in ascending order (say, relative to the natural order), in which case, the endpoints are the first and last points.

Hint: don't waste time micro-optimizing the brute-force solution. Though, there are two easy opportunities. First, you can iterate through all combinations of 4 points (N choose 4) instead of all 4 tuples (N^4), saving a factor of 4! = 24. Second, you don't need to consider whether 4 points are collinear if you already know that the first 3 are not collinear; this can save you a factor of N on typical inputs.

把點排下序,線段的起始點自然就是遍歷的頭尾,甚至也教我們怎麼排序。

How do I sort a subarray in Java?

Arrays.sort(a, lo, hi) sorts the subarray from a[lo] to a[hi-1] according to the natural order of a[]. You can use a Comparator as the fourth argument to sort according to an alternate order.

於是,最後的 FastCollinearPoints.java 相對複雜些。我是用了兩個排序,一開始和暴力演算法一樣先按自然順序排,就是先比 y 再比 x 那種,另一個是遍歷點的時候按和該點的斜率排。斜率一樣計數的就 ++,不小於三個再看這點的自然順序是不是最小,是最小才將其和自然順序最大的組成線段存起來。通過這種方法,獲得線段的起始點,避免重複加入線段。

測試結果

part1-pa3