Coursera Algorithms Programming Assignment 3: Pattern Recognition (100分)


題目原文詳見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

 

這個類的設計思想題目中已經描述的很詳細了,主要就是以下四點:

  1. Think of p as the origin. 
  2. For each other point q, determine the slope it makes with p
  3. Sort the points according to the slopes they makes with p
  4. 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) { //如果大於3,就表示滿足了組成LineSegment的條件,記錄point位置區間
148                         intervalMap.put(startPostion, curPostion-1);//curPostion-1就是區間終止節點位置
149                     }
150                     slope = origin.slopeTo(tPoints[curPostion]);
151                     startPostion = curPostion; //重置起始節點
152                 }
153             }
154             if(curPostion-startPostion >= 3) { //tPoints最后一個節點也可能與前一節點有相同的slope
155                 intervalMap.put(startPostion, curPostion-1);
156             }
157             //根據滿足條件的區間位置,創建PointPair
158             for(int key : intervalMap.keySet()){
159                 int value = intervalMap.get(key);
160                 Point[] linearPoints = new Point[value-key+2];
161                 linearPoints[0] = origin;
162                 int l = 1;
163                 while(key<=value){
164                     linearPoints[l++] = tPoints[key++];
165                 }
166                 Arrays.sort(linearPoints);
167                 PointPair pointPair = new PointPair(linearPoints[0], linearPoints[l-1]);
168                 pointPairList.add(pointPair);
169             }
170             //清空臨時數據,便於垃圾回收
171             intervalMap.clear();
172             intervalMap = null;
173             for(int t=0;t<tPoints.length;t++){
174                 tPoints[t] = null;
175             }
176             tPoints = null;
177         }
178     }
179     /**
180      * 生成LineSegment
181      * @return
182      */
183     private LineSegment[]  generateLineSegment(){
184         int ppsize = pointPairList.size();
185         if(ppsize==0) return new LineSegment[0];;
186         PointPair[] pointPairs =  new PointPair[ppsize];
187         int i = 0;
188         for(PointPair pp : pointPairList){
189             pointPairs[i++] = pp;
190         }
191         pointPairList.clear();
192         //根據pointPairComparator比較器所定制的排序依據進行排序,使得存在包含關系的PointPair變成相鄰關系
193         Arrays.sort(pointPairs,pointPairs[0].pointPairComparator());
194         List<LineSegment> lineSegmentList = new ArrayList<LineSegment>();
195         
196         PointPair ppls = pointPairs[0]; 
197         for(i=1;i<ppsize;i++){
198             if(ppls.compareTo(pointPairs[i])==0){ //相鄰的PointPair相等時,具有更小smallPoint的PointPair區間更大
199                 Point s = pointPairs[i].getSmallPoint();
200                 if(ppls.getSmallPoint().compareTo(s) > 0)
201                     ppls = pointPairs[i];
202             }else{
203                 LineSegment seg = new LineSegment(ppls.getSmallPoint(),ppls.getLargePoint());
204                 lineSegmentList.add(seg);
205                 ppls = pointPairs[i];
206             }
207         }
208         LineSegment seg = new LineSegment(ppls.getSmallPoint(),ppls.getLargePoint());
209         lineSegmentList.add(seg);
210         
211         LineSegment[] segments = new LineSegment[lineSegmentList.size()];
212         segNum = 0;
213         for (LineSegment ls : lineSegmentList) {
214             segments[segNum++] = ls;
215         }
216         return segments;
217     }
218     
219     public int numberOfSegments() {
220         // the number of line segments
221         return segNum;
222     }
223     
224     public LineSegment[] segments() {
225         // the line segments
226         //作業提交時,提示要求多次調用segments()方法返回的應該是不同的對象
227         LineSegment[] retseg = new LineSegment[segNum];
228         for(int i =0 ;i<segNum;i++){
229             retseg[i] = segments[i];
230         }
231         return retseg;
232     }
233     
234     public static void main(String[] args) {
235         // read the n points from a file
236         //In in = new In(args[0]);
237         In in = new In("collinear/rs1423.txt"); //本地測試使用
238         int n = in.readInt();
239         Point[] points = new Point[n];
240         for (int i = 0; i < n; i++) {
241             int x = in.readInt();
242             int y = in.readInt();
243             points[i] = new Point(x, y);
244         }
245 
246         // draw the points
247         StdDraw.enableDoubleBuffering();
248         StdDraw.setXscale(0, 32768);
249         StdDraw.setYscale(0, 32768);
250 //        StdDraw.setPenColor(StdDraw.RED);
251 //        StdDraw.setPenRadius(0.01);
252         for (Point p : points) {
253             p.draw();
254         }
255         StdDraw.show();
256 
257         // print and draw the line segments
258         FastCollinearPoints collinear = new FastCollinearPoints(points);
259         for (LineSegment segment : collinear.segments()) {
260             StdOut.println(segment);
261             segment.draw();
262         }
263         StdDraw.show();
264     }
265 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM