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 }

 

posted @ 2017-07-27 21:56  evasean  阅读(1703)  评论(2编辑  收藏  举报