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  阅读(1862)  评论(2)    收藏  举报