Programming Assignment 3: Pattern Recognition
Write a program to recognize line patterns in a given set of points.
Algorithms 第三周的编程任务,主要目的是编写一个Point
类来代表平面上的点,一个BruteCollinearPoints
类来计算所有包含四个点的线段,一个FastCollinearPoints
来计算所有包含4个及四个以上点的线段。
课程给出的四个数据类型说明(API):
Point data type. Create an immutable data type Point
,(use the data type Point.java), that represents a point in the plane by implementing the following API:
public class Point implements Comparable
{ 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 slopeOrder() // compare two points by slopes they make with this point }
Line segment data type. To represent line segments in the plane, use the data type LineSegment.java, which has the following API:
public class LineSegment { public LineSegment(Point p, Point q) // constructs the line segment between points p and q public void draw() // draws this line segment public String toString() // string representation }
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 }
Fast . 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 }
方案
Point 类没什么可以说的,照着API的需求写就可以了。
代码实现
import edu.princeton.cs.algs4.StdDraw;
import java.util.Comparator;
public class Point implements Comparable {
private final int x;
private final int y;
/**
* Initializes a new point.
*
* @param x the x-coordinate of the point
* @param y the y-coordinate of the point
*/
public Point(int x, int y) {
/* DO NOT MODIFY */
this.x = x;
this.y = y;
}
/**
* Draws this point to standard draw.
*/
public void draw() {
/* DO NOT MODIFY */
StdDraw.point(x, y);
}
/**
* Draws the line segment between this point and the specified point
* to standard draw.
*
* @param that the other point
*/
public void drawTo(Point that) {
/* DO NOT MODIFY */
StdDraw.line(this.x, this.y, that.x, that.y);
}
/**
* Returns the slope between this point and the specified point.
* Formally, if the two points are (x0, y0) and (x1, y1), then the slope
* is (y1 - y0) / (x1 - x0). For completeness, the slope is defined to be
* +0.0 if the line segment connecting the two points is horizontal;
* Double.POSITIVE_INFINITY if the line segment is vertical;
* and Double.NEGATIVE_INFINITY if (x0, y0) and (x1, y1) are equal.
*
* @param that the other point
* @return the slope between this point and the specified point
*/
public double slopeTo(Point that) {
double diffX = (double) (that.x - this.x);
double diffY = (double) (that.y - this.y);
if (diffY == 0) {
return diffX == 0 ? Double.NEGATIVE_INFINITY : +0.0;
} else if (diffX == 0) {
return Double.POSITIVE_INFINITY;
} else {
return diffY / diffX;
}
}
/**
* Compares two points by y-coordinate, breaking ties by x-coordinate.
* 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.
*
* @param that the other point
* @return the value 0 if this point is equal to the argument
* point (x0 = x1 and y0 = y1);
* a negative integer if this point is less than the argument
* point; and a positive integer if this point is greater than the
* argument point
*/
@Override
public int compareTo(Point that) {
if (this.y > that.y) {
return +1;
} else if (this.y < that.y) {
return -1;
} else {
return Integer.compare(this.x, that.x);
}
}
/**
* Compares two points by the slope they make with this point.
* The slope is defined as in the slopeTo() method.
*
* @return the Comparator that defines this ordering on points
*/
public Comparator slopeOrder() {
return new BySlope();
}
/**
* Returns a string representation of this point.
* This method is provide for debugging;
* your program should not rely on the format of the string representation.
*
* @return a string representation of this point
*/
@Override
public String toString() {
/* DO NOT MODIFY */
return "(" + x + ", " + y + ")";
}
private class BySlope implements Comparator {
@Override
public int compare(Point o1, Point o2) {
Double slope1 = Point.this.slopeTo(o1);
Double slope2 = Point.this.slopeTo(o2);
return slope1.compareTo(slope2);
}
}
}
BruteCollinearPoints :只需要进行四层for循环嵌套,然后进行判断就可以了。
实现代码
import java.util.ArrayList;
import java.util.Arrays;
public class BruteCollinearPoints {
private final ArrayList lineSegments;
/**
* Finds all line segments containing 4 points.
*
* @throws IllegalArgumentException the argument to the constructor is null,
* if any point in the array is null, or if the
* argument to the constructor contains a
* repeated point.
*/
public BruteCollinearPoints(Point[] points) {
isLegal(points);
Point[] pointsCopy = Arrays.copyOf(points, points.length);
lineSegments = new ArrayList<>();
Arrays.sort(pointsCopy);
for (int i = 0; i < pointsCopy.length - 3; i++) {
for (int j = i + 1; j < pointsCopy.length - 2; j++) {
for (int k = j + 1; k < pointsCopy.length - 1; k++) {
if (!isCollinear(pointsCopy, i, j, k)) {
continue;
}
for (int m = k + 1; m < pointsCopy.length; ++m) {
if (isCollinear(pointsCopy, i, k, m)) {
lineSegments.add(new LineSegment(pointsCopy[i], pointsCopy[m]));
}
}
}
}
}
}
private boolean isCollinear(Point[] points, int i, int j, int k) {
double firstSlope = points[i].slopeTo(points[j]);
double secondSlope = points[i].slopeTo(points[k]);
return firstSlope == secondSlope;
}
private void isLegal(Point[] points) {
if (points == null) {
throw new java.lang.IllegalArgumentException();
}
for (Point point : points) {
if (point == null) {
throw new IllegalArgumentException();
}
}
for (int i = 0; i < points.length - 1; i++) {
for (int j = i + 1; j < points.length; j++) {
if (points[i].compareTo(points[j]) == 0) {
throw new IllegalArgumentException();
}
}
}
}
/**
* Returns the number of line segments.
*
* @return the number of line segments.
*/
public int numberOfSegments() {
return lineSegments.size();
}
/**
* Returns a LineSegment array.
*
* @return a LineSegment array.
*/
public LineSegment[] segments() {
return lineSegments.toArray(new LineSegment[numberOfSegments()]);
}
}
FastCollinearPoints:思想如下,
我们选中一个点作为起始点,因为API要求我们不能存储一条线段的子线段,所以我们需要判断当前线段是否为之前线段的子线段。用数组存储它和它之前的点表示的斜率,如果起始点与它之后的点构成四点及以上的一线,那么可以通过判断起始点和它之后的点代表的斜率是否与之前的斜率存在相等的情况来得出当前得出的线段是否是之前得出的线段的子线段。
如上图情况一是不能添加到线段集合的,而情况二则是可以的。
我们存储当前点之前的斜率时,需要对数组进行排序,目的是为了方便之后的查找。
实现代码
import java.util.ArrayList;
import java.util.Arrays;
public class FastCollinearPoints {
private final ArrayList lineSegments;
/**
* Finds all line segments containing 4 pointsCopy or more pointsCopy.
*
* @throws IllegalArgumentException if the argument to the constructor is null if any
* point in the array is null, or if the argument to
* the constructor contains a repeated point.
*/
public FastCollinearPoints(Point[] points) {
isLegal(points);
Point[] pointsCopy = Arrays.copyOf(points, points.length);
Arrays.sort(pointsCopy);
lineSegments = new ArrayList<>();
for (int i = 0; i < pointsCopy.length - 3; i++) {
Point startPoint = pointsCopy[i];
double[] preSlopes = new double[i];
Point[] nextPoints = new Point[pointsCopy.length - i - 1];
for (int j = 0; j < i; j++) {
preSlopes[j] = startPoint.slopeTo(pointsCopy[j]);
}
for (int j = 0; j < pointsCopy.length - i - 1; j++) {
nextPoints[j] = pointsCopy[i + j + 1];
}
//for binary search
Arrays.sort(preSlopes);
// sort after point by slope
Arrays.sort(nextPoints, startPoint.slopeOrder());
findLineSegments(preSlopes, startPoint, nextPoints);
}
}
private void findLineSegments(double[] preSlopes, Point startPoint, Point[] nextPoints) {
double currentSlope;
double beforeSlope = Double.NEGATIVE_INFINITY;
int countRepeat = 1;
for (int i = 0; i < nextPoints.length; i++) {
currentSlope = startPoint.slopeTo(nextPoints[i]);
if (beforeSlope != currentSlope) {
//beforeSlope != currentSlope and countRepeat>= 3
if (countRepeat >= 3 && !isSubLine(beforeSlope, preSlopes)) {
lineSegments.add(new LineSegment(startPoint, nextPoints[i - 1]));
}
countRepeat = 1;
} else {
countRepeat++;
}
beforeSlope = currentSlope;
}
//record the rest of the situation.
if (countRepeat >= 3 && !isSubLine(beforeSlope, preSlopes)) {
lineSegments.add(new LineSegment(startPoint, nextPoints[nextPoints.length - 1]));
}
}
private void isLegal(Point[] points) {
if (points == null) {
throw new java.lang.IllegalArgumentException();
}
for (Point point : points) {
if (point == null) {
throw new IllegalArgumentException();
}
}
for (int i = 0; i < points.length - 1; i++) {
for (int j = i + 1; j < points.length; j++) {
if (points[i].compareTo(points[j]) == 0) {
throw new IllegalArgumentException();
}
}
}
}
private boolean isSubLine(double tempSlope, double[] beforeSlope) {
int lo = 0;
int hi = beforeSlope.length - 1;
// use binary search
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (tempSlope < beforeSlope[mid]) {
hi = mid - 1;
} else if (tempSlope > beforeSlope[mid]) {
lo = mid + 1;
} else {
return true;
}
}
return false;
}
/**
* Returns the number of line segments.
*
* @return the number of line segments.
*/
public int numberOfSegments() {
return lineSegments.size();
}
/**
* Returns a LineSegment array.
*
* @return a LineSegment array.
*/
public LineSegment[] segments() {
return lineSegments.toArray(new LineSegment[numberOfSegments()]);
}
}