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
这个类的设计思想题目中已经描述的很详细了,主要就是以下四点:
- 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) { //如果大于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 }
本文版权归evasean所有,转载请标明出处
http://www.cnblogs.com/evasean/