cousera 上algorithm part I第三周课程讲述的是排序,包括插入排序、选择排序、希尔排序、归并排序和快速排序。其配套作业为Collinear Points,题目大意为给定若干点,求出其中的有四个及以上点共线的线段。
要求提交三个文件,Point.java,BruteCollinearPoints.java,FastCollinearPoints.java。
给定的的接口如下:
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
}
Points类实现的难点和注意点:
1.对java初学中来说,compareTo和Comparator实现有一些难点。简而言之,这两个接口的实现都是用来做比较的,两者的差别在于,打个比方,compareTo是默认比较器,Comparator是给默认分类器做补充的比较器。
Point[] points = new Point[10];
....//初始化Points
Point anchor = new Point(3,3);
Arrays.sort(points);//默认情况下用CompareTo里实现的比较方法来排序
Arrays.sort(points,anchor.slopeOrder())//不想用默认的,自己再写一个比较器来排序。
Point类实现如下:
/******************************************************************************
* Compilation: javac Point.java
* Execution: java Point
* Dependencies: none
*
* An immutable data type for points in the plane.
* For use on Coursera, Algorithms Part I programming assignment.
*
******************************************************************************/
import java.util.Arrays;
import java.util.Collection;
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
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) {
/* YOUR CODE HERE */
if (this.y == that.y) //斜率为0或者同一个位置的点
{
if (this.x == that.x)
{
return Double.NEGATIVE_INFINITY;
}
else
{
return 0.0;//斜率为0
}
}
else {
if (this.x == that.x)
{
return Double.POSITIVE_INFINITY;
}
else
{
return 1.0 * (that.y - this.y) / (that.x - this.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 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
*/
public int compareTo(Point that) {
/* YOUR CODE HERE */
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
*/
private class BySlope implements Comparator<Point>
{
@Override
public int compare(Point o1, Point o2)
{
double slope1 = slopeTo(o1);
double slope2 = slopeTo(o2);
if (slope1 < slope2)
{
return -1;
}
else if(slope1 > slope2)
{
return 1;
}
else {
return 0;
}
}
}
public Comparator<Point> slopeOrder()
{
/* YOUR CODE HERE */
return new Point.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
*/
public String toString() {
/* DO NOT MODIFY */
return "(" + x + ", " + y + ")";
}
/**
* Unit tests the Point data type.
*/
public static void main(String[] args) {
/* YOUR CODE HERE */
Point x = new Point(0,0);
Point p = new Point(303, 104);
Point q = new Point(188, 16);
Point[] points = {new Point(1,2), new Point(3,4), new Point(2,4), new Point(5,7)};
//Arrays.sort(points,x.slopeOrder());
/*StdDraw.enableDoubleBuffering();
StdDraw.setXscale(-5,5);
StdDraw.setYscale(-5, 5);
x.draw();
points[1].draw();
StdDraw.show();*/
System.out.println(p.slopeTo(q));
/*for(Point ele:points)
{
ele.draw();
}*/
}
}
从这个类开始计算拥有四个以上共线点的线段,但这个类不会放入五个即以上共线点线段的数据,而且时间复杂度要求为O(n^2),所以只要用四个for循环即可。
难点和注意点:
1.如何保证程序输出的线段和cousera给出的线段一样,因为如果点A,B,C,D共线,线段可以是AB,AC,AB,BC,如何让这些保证这些表示同一段线段的线段仅输出一次,且每次按同样的规范输出。
方法:将数组中的点排序,每次输出的端点为排序中最小的和最大的点就可以保证只输出一次,且规范相同了。
2.如果在类中用类成员线段数组来存储结果,那在segments()中不应该直接return这个数组,因为如果直接return的话,就给了外部类修改类成员的机会,破坏了封装性,应该返回一个副本。
class BruteCollinearPoints
{
Linesegment[] lines;//存放结果
int segNum;
linesegment[] segments()//返回结果
{
//return results; 直接输出,错误,破坏了封装性
LineSegment[] result = new LineSegment[segNum];
for(int i = 0; i < segNum; i++)
{
result[i] = lines[i];
}
return result;//返回lines的副本
}
3.对于斜率之间的比较,不能用简单的==,因为存在垂线,即横坐标相等的点,这个时候它们的斜率为Double.POSITIVE_INFINITY,不能用简单的==比较,得用Double.compare
类实现如下:
/******************************************************************************
* Name: lark
* CreatedTime: 2020/2/10 23:06.
*
* Description: 暴力求出包含四个共线点的线段
******************************************************************************/
import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.StdDraw;
import edu.princeton.cs.algs4.StdOut;
import java.util.Arrays;
//如何使得空间复杂度为n+线段个数,因为数组是不可伸缩的
public class BruteCollinearPoints
{
private int segNum = 0;
private LineSegment[] lines;
public BruteCollinearPoints(Point[] points)// finds all line segments containing 4 points
{
if (points == null)
{
throw new IllegalArgumentException();
}
Point[] pts = new Point[points.length];
for (int i = 0; i < points.length; i++)
{
if (points[i] == null)
{
throw new IllegalArgumentException();
}
pts[i] = points[i];
}
Arrays.sort(pts);//点按照x,y大小排序
if (isduplicate(pts))//有重复的
{
throw new IllegalArgumentException();
}
LineSegment[] tempresult = new LineSegment[points.length];
for (int i = 0; i < pts.length; i++)
{
for ( int j = (i + 1); j < pts.length; j++)
{
for (int k = (j + 1); k < pts.length; k++)
{
for (int l = (k + 1); l < pts.length; l++)
{
if (Double.compare(pts[i].slopeTo(pts[j]), pts[k].slopeTo(pts[j])) == 0 && Double.compare(pts[k].slopeTo(pts[l]), pts[k].slopeTo(pts[j])) == 0 )
{
tempresult[segNum++] = new LineSegment(pts[i],pts[l]);
}
}
}
}
}
lines = Arrays.copyOf(tempresult,segNum);
tempresult = null;
}
public int numberOfSegments() // the number of line segment
{
return segNum;
}
public LineSegment[] segments() // the line segments
{
//将结果复制一份传出去,如果直接将lines传出去,别人可以通过函数返回值在main里直接修改lines。
LineSegment[] result = new LineSegment[segNum];
for(int i = 0; i < segNum; i++)
{
result[i] = lines[i];
}
return result;
}
private boolean isduplicate(Point[] items)
{
for (int i = 1; i < items.length; i++)
{
if (items[i].compareTo(items[i-1]) == 0)
{
return true;
}
}
return false;
}
public static void main(String[] args)
{
In in = new In(args[0]);
int n = in.readInt();
Point[] points = new Point[n];
for (int i = 0; i < n; i++) {
int x = in.readInt();
int y = in.readInt();
points[i] = new Point(x, y);
}
// draw the points
StdDraw.enableDoubleBuffering();
StdDraw.setXscale(0, 32768);
StdDraw.setYscale(0, 32768);
BruteCollinearPoints collinear = new BruteCollinearPoints(points);
collinear.numberOfSegments();
collinear.numberOfSegments();
for (LineSegment segment : collinear.segments()) {
StdOut.println(segment);
//segment.draw();
}
collinear.numberOfSegments();
collinear.numberOfSegments();
collinear.numberOfSegments();
collinear.numberOfSegments();
for (LineSegment segment : collinear.segments()) {
StdOut.println(segment);
//segment.draw();
}
// print and draw the line segments
//BruteCollinearPoints collinear = new BruteCollinearPoints(points);
for (LineSegment segment : collinear.segments()) {
//StdOut.println(segment);
//segment.draw();
}
StdDraw.show();
}
}
FastCollinearPoints类的输入包括五个以上共线点的线段,而且时间复杂度要求为O(n^2longn),此时再用BruteCollinearPoints类就不行了。
思路:
以数组中的一个点A为基点,将其他点按和点A的斜率排序,若A存在共线点B,C,D,那么排序后BCD三个点肯定是在一起的,因为它们和点A的斜率一样。计算有多少个斜率一样的点,如果大于等于三个,那么就说明这条线段满足条件(A点本来就算一个共线点)
难点和注意点:
1.要对数组中的每个点采用上面的思路来求共线点线段,这样的话如何避免重复,保证唯一性。比如,ABCD共线,以点A为基点时,可以求出ABCD线段,以点B为基点时,又可以求出ABCD线段。
解决方法:以每个点为基点时,只求基点为线段中最小点的线段,这样就可以避免重复,以ABCD为例,线段ABCD只在基点为A时(A最小)求出。
2.对于垂直线的,即斜率为正无穷大共线点的测试,需要注意for循环的边界条件
代码如下:
/******************************************************************************
* Name: lark
* CreatedTime: 2020/2/11 09:28.
*
* Description:
******************************************************************************/
import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.StdDraw;
import edu.princeton.cs.algs4.StdOut;
import java.util.Arrays;
public class FastCollinearPoints {
private LineSegment[] lines;
private int linenum = 0;
public FastCollinearPoints(Point[] points) // finds all line segments containing 4 or more points
{
if (points == null)
{
throw new IllegalArgumentException();
}
int len = points.length;
Point[] copy1 = new Point[len];
Point[] copy2 = new Point[len];
LineSegment[] lineset = new LineSegment[len * len];//申请一个大数组暂时存放结果
copy(points,copy1);
Arrays.sort(copy1);
copy(copy1,copy2);
if (isduplicate(copy1))//有重复的
{
throw new IllegalArgumentException();
}
for (int i = 0; i < len; i++)
{
Arrays.sort(copy2,copy1[i].slopeOrder());//根据斜率排序
Point min = copy2[0];
Point max = copy2[0];
int count = 1;
for (int j = 1; j < len; j++)
{
if (Double.compare(copy1[i].slopeTo(copy2[j]), copy1[i].slopeTo(copy2[j-1])) == 0 )
{
if (copy2[j].compareTo(min) < 0)
{
min = copy2[j];
}
else if (copy2[j].compareTo(max) > 0)
{
max = copy2[j];
}
count++;
if (j == (len - 1) && count >= 3 && min.compareTo(copy1[i]) > 0)
{
lineset[linenum++] = new LineSegment(copy1[i],max);
}
}
else
{
if (count >= 3 && min.compareTo(copy1[i]) > 0) {
lineset[linenum++] = new LineSegment(copy1[i],max);
}
min = copy2[j];
max = copy2[j];
count = 1;
}
}
}
lines = Arrays.copyOf(lineset, linenum);
}
public int numberOfSegments() // the number of line segments
{
return linenum;
}
public LineSegment[] segments() // the line segments
{
LineSegment[] result = new LineSegment[linenum];
for(int i = 0; i < linenum; i++)
{
result[i] = lines[i];
}
return result;
}
private void copy(Point[] origin, Point[] newarray)
{
int len = origin.length;
for(int i = 0 ; i < len ; i++) //复制一份辅助数组
{
if (origin[i] == null) {
throw new IllegalArgumentException();
}
newarray[i] = origin[i];
}
}
private boolean isduplicate(Point[] items)
{
for (int i = 1; i < items.length; i++)
{
if (items[i].compareTo(items[i-1]) == 0)
{
return true;
}
}
return false;
}
public static void main(String[] args) {
// read the n points from a file
In in = new In(args[0]);
int n = in.readInt();
Point[] points = new Point[n];
for (int i = 0; i < n; i++) {
int x = in.readInt();
int y = in.readInt();
points[i] = new Point(x, y);
}
// draw the points
StdDraw.enableDoubleBuffering();
StdDraw.setXscale(0, 32768);
StdDraw.setYscale(0, 32768);
for (Point p : points) {
p.draw();
}
StdDraw.show();
// print and draw the line segments
FastCollinearPoints collinear = new FastCollinearPoints(points);
for (LineSegment segment : collinear.segments()) {
StdOut.println(segment);
segment.draw();
}
StdDraw.show();
}
}