Collinear Points
Point.java
照要求补API即可。
import java.util.Comparator;
import edu.princeton.cs.algs4.StdDraw;
public class Point implements Comparable<Point> {
private final int x; // x-coordinate of this point
private final int y; // y-coordinate of this point
/**
* Initializes a new point.
*
* @param x the <em>x</em>-coordinate of the point
* @param y the <em>y</em>-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) {
if (compareTo(that) == 0) {
return Double.NEGATIVE_INFINITY;
}
if (this.x == that.x) {
return Double.POSITIVE_INFINITY;
}
if (this.y == that.y) {
return +0.0;
}
return 1.0 * (this.y - that.y) / (this.x - that.x);
}
/**
* 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 <tt>0</tt> 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
*/
public int compareTo(Point that) {
if (this.y < that.y) {
return -1;
} else if (this.y > that.y) {
return 1;
} else if (this.x < that.x) {
return -1;
} else if (this.x > that.x) {
return 1;
} else {
return 0;
}
}
/**
* 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<Point> slopeOrder() {
return new SlopeComparator();
}
/**
* 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
*/
public String toString() {
/* DO NOT MODIFY */
return "(" + x + ", " + y + ")";
}
private class SlopeComparator implements Comparator<Point> {
@Override
public int compare(Point o1, Point o2) {
if (slopeTo(o1) < slopeTo(o2)) {
return -1;
} else if (slopeTo(o1) > slopeTo(o2)) {
return 1;
} else {
return 0;
}
}
}
/**
* Unit tests the Point data type.
*/
public static void main(String[] args) {
}
}
BruteCollinearPoints.java
暴力四重循环就完事了。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class BruteCollinearPoints {
private final List<LineSegment> segments;
public BruteCollinearPoints(Point[] points) {
if (points == null) {
throw new IllegalArgumentException();
}
Point[] copy = new Point[points.length];
segments = new ArrayList<>();
// 避免修改原数组,需要先复制下来
for (int i = 0; i < points.length; i++) {
if (points[i] == null) {
throw new IllegalArgumentException();
}
copy[i] = points[i];
}
Arrays.sort(copy);
for (int i = 0; i < copy.length - 1; i++) {
if (copy[i].compareTo(copy[i + 1]) == 0) {
throw new IllegalArgumentException();
}
}
run(copy);
}
public int numberOfSegments() {
return segments.size();
}
public LineSegment[] segments() {
LineSegment[] res = new LineSegment[segments.size()];
int i = 0;
for (LineSegment segment : segments) {
res[i++] = segment;
}
return res;
}
private void run(Point[] points) {
int n = points.length;
// 直接四重遍历
for (int i = 0; i < n - 3; i++) {
for (int j = i + 1; j < n - 2; j++) {
double s1 = points[i].slopeTo(points[j]);
for (int k = j + 1; k < n - 1; k++) {
double s2 = points[i].slopeTo(points[k]);
if (s1 == s2) {
for (int m = k + 1; m < n; m++) {
double s3 = points[i].slopeTo(points[m]);
if (s1 == s3) {
LineSegment temp = new LineSegment(points[i], points[m]);
segments.add(temp);
}
}
}
}
}
}
}
}
FastCollinearPoints.java
基本方法是以某一个点为基准,将其余点按照斜率大小进行排序,遍历得到的数组,相同斜率的点排在一起,即共线。问题在于如何找到线的端点以及排除重复端点对。思路如下:
- 一条线段的端点,与线段上其余点相比,其x/y值必然是最大/小的。所以可以直接用compareTo()来找到端点。
- 从基准点数组中,每次按顺序取一个基准点来排序斜率数组。在找到端点后,只保存以当前基准点为较小端点的线段,这样就可以避免端点对的重复。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class FastCollinearPoints {
private final List<LineSegment> segments;
public FastCollinearPoints(Point[] points) {
if (points == null) {
throw new IllegalArgumentException();
}
Point[] copy = new Point[points.length];
segments = new ArrayList<>();
for (int i = 0; i < points.length; i++) {
if (points[i] == null) {
throw new IllegalArgumentException();
}
copy[i] = points[i];
}
Arrays.sort(copy);
for (int i = 0; i < copy.length - 1; i++) {
if (copy[i].compareTo(copy[i + 1]) == 0) {
throw new IllegalArgumentException();
}
}
run(copy);
}
public int numberOfSegments() {
return segments.size();
}
public LineSegment[] segments() {
LineSegment[] res = new LineSegment[segments.size()];
int i = 0;
for (LineSegment segment : segments) {
res[i++] = segment;
}
return res;
}
private void run(Point[] points) {
int n = points.length;
Point[] bases = Arrays.copyOf(points, n); // 基准点数组
int current = 0;
while (current < n) {
Point base = bases[current++]; // 选取下一个基准点
Point min = base; // 共线线段较小端点
Point max = base; // 贡献线段较大端点
int count = 2; // 线段中至少会有两个点
Arrays.sort(points, base.slopeOrder()); // 按照与基准点的斜率进行排序
for (int i = 0; i < n - 1; i++) {
double s1 = base.slopeTo(points[i]);
double s2 = base.slopeTo(points[i + 1]);
if (s1 == s2) {
count++;
// 在新加入共线点的情况下更新小端点和大端点
if (max.compareTo(points[i + 1]) < 0) {
max = points[i + 1];
} else if (min.compareTo(points[i + 1]) > 0) {
min = points[i + 1];
}
// 当点i、i+1为最后两个点时,需要进行判断
// 只保存以base为小端点的线段
if (i == n - 2 && count >= 4 && base.compareTo(min) == 0) {
LineSegment temp = new LineSegment(min, max);
segments.add(temp);
}
} else {
// 当相同斜率序列中断时,需要进行判断
// 只保存以base为小端点的线段
if (count >= 4 && base.compareTo(min) == 0) {
LineSegment temp = new LineSegment(min, max);
segments.add(temp);
}
// 开始新斜率序列时,需要将base与新斜率的第一个点进行比较,重置min和max
if (base.compareTo(points[i + 1]) > 0) {
min = points[i + 1];
max = base;
} else {
min = base;
max = points[i + 1];
}
count = 2;
}
}
}
}
}