題目原文詳見http://coursera.cs.princeton.edu/algs4/assignments/collinear.html 程式的主要目的是尋找n個points中的line segment,line segment的要求就是包含不少於4個點。 作業包含三部分程式實現: 一、Poi ...
題目原文詳見http://coursera.cs.princeton.edu/algs4/assignments/collinear.html
程式的主要目的是尋找n個points中的line segment,line segment的要求就是包含不少於4個點。
作業包含三部分程式實現:
一、Point
compareTo()用來比較本節點this與其他節點that的大小:假如this節點坐標(x0, y0),that節點坐標(x1, y1),只有y0 < y1或(y0==y1 && x0<x1)的時候this < that
slopeTo()用來計算that節點到本節點this的斜率,計算方法為(y1 − y0) / (x1 − x0),特別註意的是,x0 != x1 && y0==y1時,slople為0,x0==x1 && y0!=y1時,slople應為positive infinity,x0==x1 && y0==y1時,slope應為negative infinity,
slopeOrder()用來返回比較器,這個比較器的參照點是本節點p0(x0, y0),如果兩個待比較節點分別是p1(x1, y1)和p2(x2, y2),此比較器的設計要求是當p1相對於p0的斜率大於p2相對於p0的斜率時,p1>p2。比較器主要在排序方法中使用
1 import java.util.Comparator; 2 import edu.princeton.cs.algs4.StdDraw; 3 4 public class Point implements Comparable<Point> { 5 6 private final int x; // x-coordinate of this point 7 private final int y; // y-coordinate of this point 8 9 /** 10 * Initializes a new point. 11 * 12 * @param x 13 * the <em>x</em>-coordinate of the point 14 * @param y 15 * the <em>y</em>-coordinate of the point 16 */ 17 public Point(int x, int y) { 18 /* DO NOT MODIFY */ 19 this.x = x; 20 this.y = y; 21 } 22 23 /** 24 * Draws this point to standard draw. 25 */ 26 public void draw() { 27 /* DO NOT MODIFY */ 28 StdDraw.point(x, y); 29 } 30 31 /** 32 * Draws the line segment between this point and the specified point to 33 * standard draw. 34 * 35 * @param that 36 * the other point 37 */ 38 public void drawTo(Point that) { 39 /* DO NOT MODIFY */ 40 StdDraw.line(this.x, this.y, that.x, that.y); 41 } 42 43 /** 44 * Returns the slope between this point and the specified point. Formally, 45 * if the two points are (x0, y0) and (x1, y1), then the slope is (y1 - y0) 46 * / (x1 - x0). For completeness, the slope is defined to be +0.0 if the 47 * line segment connecting the two points is horizontal; 48 * Double.POSITIVE_INFINITY if the line segment is vertical; and 49 * Double.NEGATIVE_INFINITY if (x0, y0) and (x1, y1) are equal. 50 * 51 * @param that 52 * the other point 53 * @return the slope between this point and the specified point 54 */ 55 public double slopeTo(Point that) { 56 /* YOUR CODE HERE */ 57 int x0 = this.x; 58 int y0 = this.y; 59 int x1 = that.x; 60 int y1 = that.y; 61 if (x0 == x1 && y0 == y1) 62 return Double.NEGATIVE_INFINITY; 63 else if (x0 == x1) 64 return Double.POSITIVE_INFINITY; 65 else if (y0 == y1) 66 return +0.0; 67 else 68 return (y1 - y0) / (double)(x1 - x0); 69 } 70 71 /** 72 * Compares two points by y-coordinate, breaking ties by x-coordinate. 73 * Formally, the invoking point (x0, y0) is less than the argument point 74 * (x1, y1) if and only if either y0 < y1 or if y0 = y1 and x0 < x1. 75 * 76 * @param that 77 * the other point 78 * @return the value <tt>0</tt> if this point is equal to the argument point 79 * (x0 = x1 and y0 = y1); a negative integer if this point is less 80 * than the argument point; and a positive integer if this point is 81 * greater than the argument point 82 */ 83 public int compareTo(Point that) { 84 /* YOUR CODE HERE */ 85 int x0 = this.x; 86 int y0 = this.y; 87 int x1 = that.x; 88 int y1 = that.y; 89 if (y0 == y1) { 90 if (x0 == x1) 91 return 0; 92 else if (x0 > x1) 93 return 1; 94 else 95 return -1; 96 } else if (y0 > y1) 97 return 1; 98 else 99 return -1; 100 } 101 102 /** 103 * Compares two points by the slope they make with this point. The slope is 104 * defined as in the slopeTo() method. 105 * 106 * @return the Comparator that defines this ordering on points 107 */ 108 public Comparator<Point> slopeOrder() { 109 /* YOUR CODE HERE */ 110 return new SlopeOrder(this); 111 } 112 /** 113 * 此comparator提供兩個點關於參照點的比較方法,主要供排序方法使用 114 * invokePoint就是參照點,兩個待比較的Point的大小,這就是排序方法中要用的排序依據 115 * @author evasean www.cnblogs.com/evasean/ 116 * 117 */ 118 private class SlopeOrder implements Comparator<Point>{ 119 private final Point p0; 120 public SlopeOrder(Point invokePoint){ 121 this.p0 = invokePoint; 122 } 123 @Override 124 public int compare(Point o1, Point o2) { 125 // TODO Auto-generated method stub 126 double slope1 = p0.slopeTo(o1); 127 double slope2 = p0.slopeTo(o2); 128 return Double.compare(slope1, slope2); //double不推薦用==直接比大小,採用這種方式比較好 129 } 130 } 131 132 /** 133 * Returns a string representation of this point. This method is provide for 134 * debugging; your program should not rely on the format of the string 135 * representation. 136 * 137 * @return a string representation of this point 138 */ 139 public String toString() { 140 /* DO NOT MODIFY */ 141 return "(" + x + ", " + y + ")"; 142 } 143 144 /** 145 * Unit tests the Point data type. 146 */ 147 public static void main(String[] args) { 148 /* YOUR CODE HERE */ 149 Point p1 = new Point(0, 0); 150 Point p2 = new Point(1, 1); 151 System.out.println("p1.compareTo(p2)=" + p1.compareTo(p2)); 152 System.out.println("p1.slopeTo(p2)=" + p1.slopeTo(p2)); 153 Point p3 = new Point(0, 4); 154 System.out.println("p1.slopeTo(p3)=" + p1.slopeTo(p3)); 155 Point p4 = new Point(4, 4); 156 System.out.println("p3.compareTo(p4)=" + p3.compareTo(p4)); 157 System.out.println("p3.slopeTo(p4)=" + p3.slopeTo(p4)); 158 Point p5 = new Point(0, 0); 159 System.out.println("p1.slopeTo(p5)=" + p1.slopeTo(p5)); 160 } 161 }
二、Brute force
這個類很簡單,直接看代碼
1 import java.util.ArrayList; 2 import java.util.Arrays; 3 import java.util.Comparator; 4 5 import edu.princeton.cs.algs4.In; 6 import edu.princeton.cs.algs4.StdDraw; 7 import edu.princeton.cs.algs4.StdOut; 8 /** 9 * @author evasean www.cnblogs.com/evasean/ 10 */ 11 public class BruteCollinearPoints { 12 private int segNum; 13 private Point[] points; //提交作業時提示輸入的給構造函數的數組內容不能發生改變,故類中加個數組將輸入參數存起來 14 private ArrayList<LineSegment> segmentList= new ArrayList<LineSegment>(); 15 16 public BruteCollinearPoints(Point[] inpoints) { 17 if (inpoints == null) 18 throw new IllegalArgumentException("Constructor argument Point[] is null!"); 19 // finds all line segments containing 4 points 20 for (int i=0;i<inpoints.length;i++) { 21 if (inpoints[i] == null) 22 throw new IllegalArgumentException("there is null in constructor argument"); 23 } 24 points = new Point[inpoints.length]; 25 for (int i=0;i<inpoints.length;i++) { 26 points[i] = inpoints[i]; 27 } 28 Arrays.sort(points); //對本對象的私有數組進行排序 29 for (int i=0;i<points.length-1;i++) { 30 if (points[i].compareTo(points[i+1]) == 0) // 與前一個元素相等 31 throw new IllegalArgumentException("there exists repeated points!"); 32 } 33 //作業提交時提示隨機穿插順序調用numberOfSegments()和segment()方法返回結果要求穩定 34 //那麼構造函數中就要把LineSegment找好 35 findLineSegment(points); 36 } 37 38 /** 39 * 按照作業要求用四層迴圈來做 40 * @param points 41 */ 42 private void findLineSegment(Point[] points) { 43 int pNum = points.length; 44 for (int i = 0; i < pNum - 3; i++) { // i不需要遍歷最後三個節點,因為至少四個節點才能組成LineSegment 45 // 每個comparator需要占據額外空間,總共需要n-4個Comparator<Point>的額外空間 46 Comparator<Point> comparator = points[i].slopeOrder(); 47 for (int j = i + 1; j < pNum - 2; j++) { 48 if (points[j].compareTo(points[i]) == 0) 49 continue; // 相同point直接跳過 50 for (int l = j + 1; l < pNum - 1; l++) { 51 if (points[l].compareTo(points[i]) == 0) 52 continue; 53 if (points[l].compareTo(points[j]) == 0) 54 continue; 55 if (comparator.compare(points[j], points[l]) == 0) { // point[j]和point[l]相對於point[i]的斜率相等 56 for (int m = l + 1; m < pNum; m++) { 57 if (points[m].compareTo(points[i]) == 0) 58 continue; 59 if (points[m].compareTo(points[j]) == 0) 60 continue; 61 if (points[m].compareTo(points[l]) == 0) 62 continue; 63 if (comparator.compare(points[l], points[m]) == 0) { 64 // point[l]和point[m]相對於point[i]的斜率相等時,i、j、l、m 四點可以組成一個linesegment 65 // 每個LineSegment需要占據一份額外空間 66 LineSegment seg = new LineSegment(points[i], points[m]); 67 segmentList.add(seg); 68 } 69 } 70 } 71 } 72 } 73 } 74 segNum = segmentList.size(); 75 76 } 77 78 public int numberOfSegments() { 79 // the number of line segments 80 return segNum; 81 } 82 83 public LineSegment[] segments() { 84 // the line segments 85 //作業提交時,提示要求多次調用segments()方法返回的應該是不同的對象 86 LineSegment[] segments = new LineSegment[segNum]; 87 int i = 0; 88 for(LineSegment seg : segmentList){ 89 segments[i++] = seg; 90 } 91 return segments; 92 } 93 94 public static void main(String[] args) { 95 // read the n points from a file 96 In in = new In(args[0]); 97 //In in = new In("collinear/input8.txt"); //本地測試使用 98 int n = in.readInt(); 99 Point[] points = new Point[n]; 100 for (int i = 0; i < n; i++) { 101 int x = in.readInt(); 102 int y = in.readInt(); 103 points[i] = new Point(x, y); 104 } 105 106 // draw the points 107 StdDraw.enableDoubleBuffering(); 108 StdDraw.setXscale(0, 32768); 109 StdDraw.setYscale(0, 32768); 110 StdDraw.setPenColor(StdDraw.RED); 111 StdDraw.setPenRadius(0.01); 112 for (Point p : points) { 113 p.draw(); 114 } 115 StdDraw.show(); 116 117 // print and draw the line segments 118 BruteCollinearPoints collinear = new BruteCollinearPoints(points); 119 for (LineSegment segment : collinear.segments()) { 120 StdOut.println(segment); 121 segment.draw(); 122 } 123 StdDraw.show(); 124 } 125 }
三、A faster, sorting-based solution
這個類的設計思想題目中已經描述的很詳細了,主要就是以下四點:
- Think of p as the origin.
- For each other point q, determine the slope it makes with p.
- Sort the points according to the slopes they makes with p.
- Check if any 3 (or more) adjacent points in the sorted order have equal slopes with respect to p. If so, these points, together with p, are collinear.
這個演算法的性能瓶頸就在於第三點的排序,更詳細的設計思路我直接寫在代碼註釋里。
1 import java.util.ArrayList; 2 import java.util.Arrays; 3 import java.util.Comparator; 4 import java.util.HashMap; 5 import java.util.List; 6 import java.util.Map; 7 8 import edu.princeton.cs.algs4.In; 9 import edu.princeton.cs.algs4.StdDraw; 10 import edu.princeton.cs.algs4.StdOut; 11 /** 12 * @author evasean www.cnblogs.com/evasean/ 13 */ 14 public class FastCollinearPoints { 15 16 private Point[] points; //提交作業時提示輸入的給構造函數的數組內容不能發生改變,故類中加個數組將輸入參數存起來 17 private final LineSegment[] segments; 18 private int segNum; 19 20 private List<PointPair> pointPairList; //存儲構成LineSegment的起點和終點Point對 21 /** 22 * LineSegment類不允許變動,但是可使用靈活度受限,自己新加個內部類使用 23 * 本類用來存儲可構成LineSegment的起點和終點point對 24 * 由於在遍歷過程中會存在包含關係的起點和終點point對,僅僅靠LineSegment類識別包含關係的效率會很低 25 * 此類中加了slope來記錄就可以很大的提高效率了,因為一個點和一個斜率就確定了一條直線 26 * 不需要再進行額外比較和計算 27 * 因為由於PointPair是對points從前到後遍歷產生的,所以如果兩個PointPair存在包含關係,那麼 28 * 這兩個PointPair中largePoint和slope一定相等 29 * 但smallPoint不相等,smallPoint更小的那個PointPair包含了另一個PointPair 30 * 這是LineSegment去重的關鍵 31 * @author evasean www.cnblogs.com/evasean/ 32 */ 33 private class PointPair{ 34 private final Point smallPoint; 35 private final Point largePoint; 36 private final double slope; 37 public PointPair(Point smallPoint, Point largePoint){ 38 this.smallPoint = smallPoint; 39 this.largePoint = largePoint; 40 this.slope = largePoint.slopeTo(smallPoint); 41 } 42 public Point getLargePoint(){ 43 return this.largePoint; 44 } 45 public Point getSmallPoint(){ 46 return this.smallPoint; 47 } 48 public double getSlope(){ 49 return this.slope; 50 } 51 public int compareTo(PointPair that) { 52 Point l1 = this.getLargePoint(); 53 Point l2 = that.getLargePoint(); 54 double s1 = this.getSlope(); 55 double s2 = that.getSlope(); 56 if(l1.compareTo(l2) > 0) return 1; 57 else if(l1.compareTo(l2) < 0) return -1; 58 else{ 59 if(s1>s2) return 1; 60 else if(s1<s2) return -1; 61 else return 0; 62 } 63 } 64 /** 65 * 判斷PointPair中的包含關係時需要用到比較器 66 * 此比較器是以largePoint為比較的主要元素,slope為次要元素 67 * smallPoint不參比較大小的考核,僅僅在兩個PointPair相等時用作判斷包含關係之用 68 * 兩個PointPair pp1 和 pp2中 69 * if pp1.largePoint > pp2.largePoint --> pp1 > pp2 70 * else if pp1.largePoint < pp2.largePoint --> pp1 < pp2 71 * if pp1.largePoint == pp2.largePoint && pp1.slope > pp2.slope --> pp1 > pp2 72 * if pp1.largePoint == pp2.largePoint && pp1.slope < pp2.slope --> pp1 < pp2 73 * if pp1.largePoint == pp2.largePoint && pp1.slope == pp2.slope --> pp1 == pp2 74 * @return 75 */ 76 public Comparator<PointPair> pointPairComparator() { 77 return new PointPairComparator(); 78 } 79 private class PointPairComparator implements Comparator<PointPair>{ 80 @Override 81 public int compare(PointPair pp1, PointPair pp2) { 82 // TODO Auto-generated method stub 83 Point l1 = pp1.getLargePoint(); 84 Point l2 = pp2.getLargePoint(); 85 double s1 = pp1.getSlope(); 86 double s2 = pp2.getSlope(); 87 if(l1.compareTo(l2) > 0) return 1; 88 else if(l1.compareTo(l2) < 0) return -1; 89 else{ 90 return Double.compare(s1, s2); //double元素用Double.compare進行比較更精確 91 } 92 } 93 } 94 } 95 96 public FastCollinearPoints(Point[] inpoints) { 97 // finds all line segments containing 4 or more points 98 if (inpoints == null) 99 throw new IllegalArgumentException("Constructor argument Point[] is null!"); 100 // finds all line segments containing 4 points 101 for (int i=0;i<inpoints.length;i++) { 102 if (inpoints[i] == null) 103 throw new IllegalArgumentException("there is null in constructor argument"); 104 } 105 points = new Point[inpoints.length]; 106 for (int i=0;i<inpoints.length;i++) { 107 points[i] = inpoints[i]; 108 } 109 Arrays.sort(points); //對本對象的私有數組進行排序 110 for (int i=0;i<points.length-1;i++) { 111 if (points[i].compareTo(points[i+1]) == 0) // 與前一個元素相等 112 throw new IllegalArgumentException("there exists repeated points!"); 113 } 114 //作業提交時提示隨機穿插順序調用numberOfSegments()和segment()方法返回結果要求穩定 115 //那麼構造函數中就要把LineSegment找好 116 findPointPairForLineSegment(points); 117 segments = generateLineSegment(); 118 } 119 120 /** 121 * 尋找滿足LineSegment的PointPair 122 * @param points 123 */ 124 private void findPointPairForLineSegment(Point[] points){ 125 int pNum = points.length; 126 pointPairList = new ArrayList<PointPair>(); 127 for (int i = 0; i < pNum - 3; i++) { //i不需要遍歷最後三個節點,因為至少四個節點才能組成LineSegment 128 if(points[i]==null) 129 throw new IllegalArgumentException("there is null in constructor argument"); 130 Point origin = points[i]; //i處節點作為相對原點 131 Point[] tPoints = new Point[pNum-i-1]; //需要用到額外空間來存儲本輪i之後的節點根據它們各自與節點i的相對斜率來排序的結果 132 int tpNum = 0; 133 for (int j = i + 1; j < pNum; j++) { 134 tPoints[tpNum++] = points[j]; 135 } 136 //origin.slopeOrder()這個比較器就是告訴Arrays.sort待排序的那些節點tPoints排序的依據是各自與節點i的斜率 137 Arrays.sort(tPoints,origin.slopeOrder()); 138 139 int startPostion = 0; //startPosition用來記錄slope相同的point位置區間的起始位置 140 double slope = origin.slopeTo(tPoints[0]); 141 Map<Integer,Integer> intervalMap = new HashMap<Integer,Integer>(); //記錄slope相同的point位置區間 142 int curPostion = 1; 143 for(; curPostion<tpNum; curPostion++){ 144 if(Double.compare(origin.slopeTo(tPoints[curPostion]), slope)==0) 145 continue; 146 else{ //遍歷至slope不與之前相同的位置 147 if(curPostion-startPostion >= 3) {