cousera 上algorithm part I第三週課程講述的是排序,包括插入排序、選擇排序、希爾排序、歸併排序和快速排序。其配套作業爲Collinear Points,題目大意爲給定若干點,求出其中的有四個及以上點共線的線段。
要求提交三個文件,Point.java,BruteCollinearPoints.java,FastCollinearPoints.java。
Point類
給定的的接口如下:
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 <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) {
/* 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();
}*/
}
}
BruteCollinearPoints類的實現
從這個類開始計算擁有四個以上共線點的線段,但這個類不會放入五個即以上共線點線段的數據,而且時間複雜度要求爲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的實現
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();
}
}