Princeton Programming Assignment 3: Pattern Recognition


http://coursera.cs.princeton.edu/algs4/assignments/collinear.html
https://www.cs.princeton.edu/courses/archive/fall12/cos226/checklist/collinear.html
Computer vision involves analyzing patterns in visual images and reconstructing the real-world objects that produced them. The process is often broken up into two phases: feature detection and pattern recognition. Feature detection involves selecting important features of the image; pattern recognition involves discovering patterns in the features. We will investigate a particularly clean pattern recognition problem involving points and line segments. This kind of pattern recognition arises in many other applications such as statistical data analysis.
The problem. Given a set of N distinct points in the plane, find every (maximal) line segment that connects a subset of 4 or more of the points.

Points and lines
Brute force. Write a program BruteCollinearPoints.java that examines 4 points at a time and checks whether they all lie on the same line segment, returning all such line segments. To check whether the 4 points pqr, and s are collinear, check whether the three slopes between p and q, between p and r, and between p and s are all equal.
public class BruteCollinearPoints {
   public BruteCollinearPoints(Point[] points)    // finds all line segments containing 4 points
   public           int numberOfSegments()        // the number of line segments
   public LineSegment[] segments()                // the line segments
}
The method segments() should include each line segment containing 4 points exactly once. If 4 points appear on a line segment in the order pqrs, then you should include either the line segment ps or sp (but not both) and you should not include subsegments such as pr or qr. For simplicity, we will not supply any input to BruteCollinearPoints that has 5 or more collinear points.
Corner cases. Throw a java.lang.NullPointerException either the argument to the constructor is null or if any point in the array is null. Throw a java.lang.IllegalArgumentException if the argument to the constructor contains a repeated point.
Performance requirement. The order of growth of the running time of your program should be N4 in the worst case and it should use space proportional to N plus the number of line segments returned.
A faster, sorting-based solution. Remarkably, it is possible to solve the problem much faster than the brute-force solution described above. Given a point p, the following method determines whether p participates in a set of 4 or more collinear points.
  • 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.
Applying this method for each of the N points in turn yields an efficient algorithm to the problem. The algorithm solves the problem because points that have equal slopes with respect to p are collinear, and sorting brings such points together. The algorithm is fast because the bottleneck operation is sorting.
Points and slopes
Write a program FastCollinearPoints.java that implements this algorithm.
public class FastCollinearPoints {
   public FastCollinearPoints(Point[] points)     // finds all line segments containing 4 or more points
   public           int numberOfSegments()        // the number of line segments
   public LineSegment[] segments()                // the line segments
}
The method segments() should include each maximal line segment containing 4 (or more) points exactly once. For example, if 5 points appear on a line segment in the order pqrst, then do not include the subsegments ps or qt.
Corner cases. Throw a java.lang.NullPointerException either the argument to the constructor is null or if any point in the array is null. Throw a java.lang.IllegalArgumentException if the argument to the constructor contains a repeated point.
Performance requirement. The order of growth of the running time of your program should be N2 log N in the worst case and it should use space proportional to N plus the number of line segments returned.FastCollinearPoints should work properly even if the input has 5 or more collinear points.
public static void main(String[] args) throws IOException {
  String fileName = args[0];
  List<Point> points = readList(fileName); 
  Collections.sort(points);
  StdDraw.setXscale(0, 32768);
  StdDraw.setYscale(0, 32768);
  for(int i=0;i<points.size();i++) points.get(i).draw();
      Map<String, Boolean> printedMap = new HashMap<String, Boolean>();
      for(int i=0;i<points.size();i++) {
        List<Point> tmpPoints = new ArrayList<Point>();
        for(int j=i+1;j<points.size();j++) tmpPoints.add(points.get(j));
      Collections.sort(tmpPoints, points.get(i).SLOPE_ORDER);
      for(int j=0;j<tmpPoints.size();){
          double slote = points.get(i).slopeTo(tmpPoints.get(j));
        List<Point> nList = new ArrayList<Point>(); 
        nList.add(points.get(i)); nList.add(tmpPoints.get(j));
        j++;
        while(j<tmpPoints.size() && points.get(i).slopeTo(tmpPoints.get(j))==slote) {
          nList.add(tmpPoints.get(j));
          j ++;
          } 
          if(nList.size()>=4) {
            boolean printedFlag = false;
            for(int k=0;k<nList.size()-1;k++) {
              String seg = nList.get(k).toString()+","+nList.get(k+1).toString();
              if(!printedMap.containsKey(seg)) {
                printedMap.put(seg, true);
                printedFlag = true;
              }
            }
            if(printedFlag){
          Collections.sort(nList);
          for(int k=0;k<nList.size()-1;k++) System.out.print(nList.get(k)+" -> ");
          System.out.print(nList.get(nList.size()-1)+"\n");
          points.get(i).drawTo(nList.get(nList.size()-1));
            }
          }
        }
      }
}
public static void main(String[] args) {
    // Rescale the coordinate system.
    StdDraw.setXscale(0, 32768);
    StdDraw.setYscale(0, 32768);
    // Read points from the input file.
    In in = new In(args[0]);
    int pointsCount = in.readInt();
    Point[] points = new Point[pointsCount];
    for (int i = 0; i < pointsCount; i++) {
        int x = in.readInt();
        int y = in.readInt();
        points[i] = new Point(x, y);
        points[i].draw();
    }
    // Go each point p.
    for (int p = 0; p < pointsCount; p++) {
        // Sort the points according to the slopes they makes with p.
        sort(points, points[p].SLOPE_ORDER);
        // Check if any 3 (or more) adjacent points in the sorted order have equal slopes with respect to p
        ArrayList<Point> collinearPoints = new ArrayList<Point>(pointsCount);
        for (int q = 0; q < pointsCount - 1; q++) {
            if (p == q) {
                continue;
            }
            if (collinearPoints.isEmpty()) {
                collinearPoints.add(points[q]);
            } else if (points[p].slopeTo(points[q - 1]) == points[p].slopeTo(points[q])) {
                collinearPoints.add(points[q]);
            } else if (collinearPoints.size() > 2) {
                // Draw collinear points.
                collinearPoints.add(points[p]);
                Collections.sort(collinearPoints);
                // Display collinear points.
                for (int i = 0; i < 3; i++) {
                    StdOut.print(collinearPoints.get(i));
                    StdOut.print(" -> ");
                }
                StdOut.println(Collections.max(collinearPoints));
                Collections.min(collinearPoints).drawTo(Collections.max(collinearPoints));
                break;
            } else {
                collinearPoints.clear();
                collinearPoints.add(points[q]);
            }
        }
    }
}

/***********************************************************************
 *  Bottom-Up merge sorting functions
 ***********************************************************************/

// stably merge a[lo..m] with a[m+1..hi] using aux[lo..hi]
private static void merge(Point[] a, Point[] aux, int lo, int m, int hi, Comparator<Point> comparator) {
    // copy to aux[]
    for (int k = lo; k <= hi; k++) {
        aux[k] = a[k];
    }
    // merge back to a[]
    int i = lo, j = m+1;
    for (int k = lo; k <= hi; k++) {
        if      (i > m)                a[k] = aux[j++];
        else if (j > hi)               a[k] = aux[i++];
        else if (less(comparator, aux[j], aux[i])) a[k] = aux[j++];
        else                           a[k] = aux[i++];
    }
}

// bottom-up mergesort
public static void sort(Point[] a, Comparator<Point> comparator) {
    int N = a.length;
    Point[] aux = new Point[N];
    for (int n = 1; n < N; n = n+n) {
        for (int i = 0; i < N-n; i += n+n) {
            int lo = i;
            int m  = i+n-1;
            int hi = Math.min(i+n+n-1, N-1);
            merge(a, aux, lo, m, hi, comparator);
        }
    }
}
// is v < w ?
private static boolean less(Comparator<Point> comparator, Point v, Point w) {
    return comparator.compare(v, w) < 0;
}


Brute Force
public static void main(String[] args) {
    // Rescale the coordinate system.
    StdDraw.setXscale(0, 32768);
    StdDraw.setYscale(0, 32768);
    // Read points from the input file.
    In in = new In(args[0]);
    int pointsCount = in.readInt();
    Point[] points = new Point[pointsCount];
    for (int i = 0; i < pointsCount; i++) {
        int x = in.readInt();
        int y = in.readInt();
        points[i] = new Point(x, y);
        points[i].draw();
    }
    // Go each 4 points and check whether they all lie on the same line segment.
    for (int p = 0; p < pointsCount; p++) {
        for (int q = p + 1; q < pointsCount; q++) {
            double slopeToQ = points[p].slopeTo(points[q]);
            for (int r = q + 1; r < pointsCount; r++) {
                double slopeToR = points[p].slopeTo(points[r]);
                if (slopeToQ == slopeToR) {
                    for (int s = r + 1; s < pointsCount; s++) {
                        double slopeToS = points[p].slopeTo(points[s]);
                        if (slopeToQ == slopeToS) {
                            // Create the list of collinear points and sort them.
                            List<Point> collinearPoints = new ArrayList<Point>(4);
                            collinearPoints.add(points[p]);
                            collinearPoints.add(points[q]);
                            collinearPoints.add(points[r]);
                            collinearPoints.add(points[s]);
                            Collections.sort(collinearPoints);
                            // Display collinear points.
                            for (int i = 0; i < 3; i++) {
                                StdOut.print(collinearPoints.get(i));
                                StdOut.print(" -> ");
                            }
                            StdOut.println(Collections.max(collinearPoints));
                            Collections.min(collinearPoints).drawTo(Collections.max(collinearPoints));
                        }
                    }
                }
            }
        }
    }
}


public class Point implements Comparable<Point> {

    // compare points by slope
    public final Comparator<Point> SLOPE_ORDER = new Comparator<Point>() {
        public int compare(Point o1, Point o2) {
            double slope1 = slopeTo(o1);
            double slope2 = slopeTo(o2);
            if (slope1 == slope2) {
                return 0;
            }
            if (slope1 < slope2) {
                return -1;
            }
            return 1;
        }
    };

    private final int x;                              // x coordinate
    private final int y;                              // y coordinate

    // create the point (x, y)
    public Point(int x, int y) {
        /* DO NOT MODIFY */
        this.x = x;
        this.y = y;
    }

    // plot this point to standard drawing
    public void draw() {
        /* DO NOT MODIFY */
        StdDraw.point(x, y);
    }

    // draw line between this point and that point to standard drawing
    public void drawTo(Point that) {
        /* DO NOT MODIFY */
        StdDraw.line(this.x, this.y, that.x, that.y);
    }

    // slope between this point and that point
    public double slopeTo(Point that) {
        if (that == null) {
            throw new NullPointerException();
        }
        if (that.x == x) {
            if (that.y == y) {
                return Double.NEGATIVE_INFINITY;
            }
            return Double.POSITIVE_INFINITY;
        }
        if (that.y == y) {
            return 0.0;
        }
        return (double) (that.y - this.y) / (that.x - this.x);
    }

    // is this point lexicographically smaller than that one?
    // comparing y-coordinates and breaking ties by x-coordinates
    public int compareTo(Point that) {
        if (y == that.y && x == that.x) {
            return 0;
        }
        if (y < that.y || (y == that.y && x < that.x)) {
            return -1;
        }
        return 1;
    }

    // return string representation of this point
    public String toString() {
        /* DO NOT MODIFY */
        return "(" + x + ", " + y + ")";
    }
}
http://blog.csdn.net/liuweiran900217/article/details/19285111
slopeTo类相当于来计算两点间连线的斜率了。初中数学我们就知道,有如下几种情况。
(1)两个点的y坐标相同,即连线与x轴平行。这种情况的斜率为0。但是我们要注意一个问题,在Java中实际上有两种0,即+0和-0,并且Java内置的比较函数认为+0>-0。这是怎么来的呢?在题目的CheckList中已经给出了相关的介绍,我们放在这里供大家参考:
What does it mean for slopeTo() to return positive zero?Java (and the IEEE 754 floating-point standard) define two representations of zero: negative zero and positive zero.
double a = 1.0;
double x = (a - a) /  a;   // positive zero ( 0.0)
double y = (a - a) / -a;   // negative zero (-0.0)
Note that while (x == y) is guaranteed to be true,Arrays.sort() treatsnegative zero as strictly less than positive zero.Thus, to make the specification precise, we require you to return positive zero for horizontal line segments.Unless your program casts to the wrapper type Double (either explicitly or via autoboxing),you probably will not notice any difference in behavior;but, if your program does cast to the wrapper type and fails only on (some) horizontal line segments, this may bethe cause.
(2)两个点的x坐标相同,即连线与y轴平行。根据题目要求,我们要把这种斜率定义为正无穷大。在Java中,Double类给出了双精度浮点数正无穷大的表示,为Double.POSITIVE_INFINITY,因此用这个值代表正无穷大即可。
(3)两个点相同。这是一个极为特殊的情况,在CheckList中也有过说明,内容如下:
Can the same point appear more than once as input to Brute or Fast?You may assume the input to Bruteand Fast are N distinct points.Nevertheless, the methods implemented as part of the Point data typemust correctly handle the case when the points are not distinct:for the slopeTo() method, this requirement is explicitly stated in the API;for the comparison methods, this requirement is implict in the contracts for Comparable andComparator.
对于这种情况,我的处理方法是将其结果设置为负无穷大,即Double.NEGATIVE_INFINITY。
(4)其他一般情况。这种情况下,我们直接计算斜率即可,计算公式很简单,为:(y_1 - y_0) / (x_1 - x_0)。不过注意到点的坐标表示是int,斜率为double,因此我们还需要一个强制转换来保证计算的精度。

compareTo()

这个类的实现也很容易,直接按照题目要求来实现即可。当y_0 < y_1或者当y_0 = y_1且x_0 < x_1时,返回-1。

SLOPE_ORDER

这个类的实现也很容易,分别计算两个点对于第三个点的斜率(调用slopeTo()),然后返回两个结果的比较值即可。因为在slopeTo()中我们已经处理过了3种特殊的情况,因此直接返回比较值就是正确的结果。
 private static void FastMethod(Point[] pointArray){
  int N = pointArray.length;
  for (int i=0; i<N; i++){
   Point origPoint = pointArray[i];
   Point[] otherPoint = new Point[N-1];
   for (int j=0; j<pointArray.length; j++){
    if (j < i) otherPoint[j] = pointArray[j];
    if (j > i) otherPoint[j-1] = pointArray[j];
   }
   Arrays.sort(otherPoint, origPoint.SLOPE_ORDER);
   
   int count = 0;
   int index = 0;
   double tempSlope = origPoint.slopeTo(otherPoint[0]);
   for (int j=0; j<otherPoint.length;j++){
    if (Double.compare(origPoint.slopeTo(otherPoint[j]),  tempSlope) == 0){
     count++;
     continue;
    }else{
     if (count >=3){
      if (otherPoint[index].compareTo(origPoint) >=0){
       StdOut.print(origPoint + " -> ");
       for (int k=index; k<j-1; k++){
        StdOut.print(otherPoint[k] + " -> ");
       }
       StdOut.println(otherPoint[j-1]);
       origPoint.drawTo(otherPoint[j-1]);
      }
     }
     count = 1;
     index = j;
     tempSlope = origPoint.slopeTo(otherPoint[j]);
    }
   }
   if (count >= 3){
    if (otherPoint[index].compareTo(origPoint) >= 0){
     StdOut.print(origPoint + " -> ");
     for (int k=index; k<otherPoint.length - 1; k++){
      StdOut.print(otherPoint[k] + " -> ");
     }
     StdOut.println(otherPoint[otherPoint.length-1]);
     origPoint.drawTo(otherPoint[otherPoint.length-1]);
    }
   }
  }
  StdDraw.show(0);
 }
作业说明中对核心算法的描述非常清晰,应当特别小心的技术点(浮点误差、正负零等等)也都在checklist中予以强调,因而实现难度不算很大,其中使用了Java的Comparable与Comparator,排序过程调用Arrays.sort(),详细思考问题理清关系后,实现非常自然,前提是编程基本功必须扎实。
值得提到的点:
  • checklist鼓励初学者开始编写快速算法时先不要担心5个或以上的点共线的情况,而实际上对基本功扎实的同学,从开始便考虑这个问题更为合适;
  • checklist提到compare()和FastCollinearPoints类可以完全避免浮点数用整形运算实现,我想到的唯一符合要求(Point.java注释中规定不得依赖toString())的方法是,对Point类的compareTo()方法返回值增加意义(不单纯返回正负1),以获取两点的坐标之差(因题目给定坐标取值范围0-32767,可在返回值低两位字节存储x坐标差, 高两位字节存储y坐标差),再利用坐标差判断斜率是否相等。虽依然完全符合题目要求,但已有一点奇技淫巧之嫌,并且不一定能够通过autograder(评分时会替换Point类去测试FastCollinearPoints),不多讨论。(更新:此方法已确定违反FastCollinearPoints的API。)

Labels

LeetCode (1432) GeeksforGeeks (1122) LeetCode - Review (1067) Review (882) Algorithm (668) to-do (609) Classic Algorithm (270) Google Interview (237) Classic Interview (222) Dynamic Programming (220) DP (186) Bit Algorithms (145) POJ (141) Math (137) Tree (132) LeetCode - Phone (129) EPI (122) Cracking Coding Interview (119) DFS (115) Difficult Algorithm (115) Lintcode (115) Different Solutions (110) Smart Algorithm (104) Binary Search (96) BFS (91) HackerRank (90) Binary Tree (86) Hard (79) Two Pointers (78) Stack (76) Company-Facebook (75) BST (72) Graph Algorithm (72) Time Complexity (69) Greedy Algorithm (68) Interval (63) Company - Google (62) Geometry Algorithm (61) Interview Corner (61) LeetCode - Extended (61) Union-Find (60) Trie (58) Advanced Data Structure (56) List (56) Priority Queue (53) Codility (52) ComProGuide (50) LeetCode Hard (50) Matrix (50) Bisection (48) Segment Tree (48) Sliding Window (48) USACO (46) Space Optimization (45) Company-Airbnb (41) Greedy (41) Mathematical Algorithm (41) Tree - Post-Order (41) ACM-ICPC (40) Algorithm Interview (40) Data Structure Design (40) Graph (40) Backtracking (39) Data Structure (39) Jobdu (39) Random (39) Codeforces (38) Knapsack (38) LeetCode - DP (38) Recursive Algorithm (38) String Algorithm (38) TopCoder (38) Sort (37) Introduction to Algorithms (36) Pre-Sort (36) Beauty of Programming (35) Must Known (34) Binary Search Tree (33) Follow Up (33) prismoskills (33) Palindrome (32) Permutation (31) Array (30) Google Code Jam (30) HDU (30) Array O(N) (29) Logic Thinking (29) Monotonic Stack (29) Puzzles (29) Code - Detail (27) Company-Zenefits (27) Microsoft 100 - July (27) Queue (27) Binary Indexed Trees (26) TreeMap (26) to-do-must (26) 1point3acres (25) GeeksQuiz (25) Merge Sort (25) Reverse Thinking (25) hihocoder (25) Company - LinkedIn (24) Hash (24) High Frequency (24) Summary (24) Divide and Conquer (23) Proof (23) Game Theory (22) Topological Sort (22) Lintcode - Review (21) Tree - Modification (21) Algorithm Game (20) CareerCup (20) Company - Twitter (20) DFS + Review (20) DP - Relation (20) Brain Teaser (19) DP - Tree (19) Left and Right Array (19) O(N) (19) Sweep Line (19) UVA (19) DP - Bit Masking (18) LeetCode - Thinking (18) KMP (17) LeetCode - TODO (17) Probabilities (17) Simulation (17) String Search (17) Codercareer (16) Company-Uber (16) Iterator (16) Number (16) O(1) Space (16) Shortest Path (16) itint5 (16) DFS+Cache (15) Dijkstra (15) Euclidean GCD (15) Heap (15) LeetCode - Hard (15) Majority (15) Number Theory (15) Rolling Hash (15) Tree Traversal (15) Brute Force (14) Bucket Sort (14) DP - Knapsack (14) DP - Probability (14) Difficult (14) Fast Power Algorithm (14) Pattern (14) Prefix Sum (14) TreeSet (14) Algorithm Videos (13) Amazon Interview (13) Basic Algorithm (13) Codechef (13) Combination (13) Computational Geometry (13) DP - Digit (13) LCA (13) LeetCode - DFS (13) Linked List (13) Long Increasing Sequence(LIS) (13) Math-Divisible (13) Reservoir Sampling (13) mitbbs (13) Algorithm - How To (12) Company - Microsoft (12) DP - Interval (12) DP - Multiple Relation (12) DP - Relation Optimization (12) LeetCode - Classic (12) Level Order Traversal (12) Prime (12) Pruning (12) Reconstruct Tree (12) Thinking (12) X Sum (12) AOJ (11) Bit Mask (11) Company-Snapchat (11) DP - Space Optimization (11) Dequeue (11) Graph DFS (11) MinMax (11) Miscs (11) Princeton (11) Quick Sort (11) Stack - Tree (11) 尺取法 (11) 挑战程序设计竞赛 (11) Coin Change (10) DFS+Backtracking (10) Facebook Hacker Cup (10) Fast Slow Pointers (10) HackerRank Easy (10) Interval Tree (10) Limited Range (10) Matrix - Traverse (10) Monotone Queue (10) SPOJ (10) Starting Point (10) States (10) Stock (10) Theory (10) Tutorialhorizon (10) Kadane - Extended (9) Mathblog (9) Max-Min Flow (9) Maze (9) Median (9) O(32N) (9) Quick Select (9) Stack Overflow (9) System Design (9) Tree - Conversion (9) Use XOR (9) Book Notes (8) Company-Amazon (8) DFS+BFS (8) DP - States (8) Expression (8) Longest Common Subsequence(LCS) (8) One Pass (8) Quadtrees (8) Traversal Once (8) Trie - Suffix (8) 穷竭搜索 (8) Algorithm Problem List (7) All Sub (7) Catalan Number (7) Cycle (7) DP - Cases (7) Facebook Interview (7) Fibonacci Numbers (7) Flood fill (7) Game Nim (7) Graph BFS (7) HackerRank Difficult (7) Hackerearth (7) Inversion (7) Kadane’s Algorithm (7) Manacher (7) Morris Traversal (7) Multiple Data Structures (7) Normalized Key (7) O(XN) (7) Radix Sort (7) Recursion (7) Sampling (7) Suffix Array (7) Tech-Queries (7) Tree - Serialization (7) Tree DP (7) Trie - Bit (7) 蓝桥杯 (7) Algorithm - Brain Teaser (6) BFS - Priority Queue (6) BFS - Unusual (6) Classic Data Structure Impl (6) DP - 2D (6) DP - Monotone Queue (6) DP - Unusual (6) DP-Space Optimization (6) Dutch Flag (6) How To (6) Interviewstreet (6) Knapsack - MultiplePack (6) Local MinMax (6) MST (6) Minimum Spanning Tree (6) Number - Reach (6) Parentheses (6) Pre-Sum (6) Probability (6) Programming Pearls (6) Rabin-Karp (6) Reverse (6) Scan from right (6) Schedule (6) Stream (6) Subset Sum (6) TSP (6) Xpost (6) n00tc0d3r (6) reddit (6) AI (5) Abbreviation (5) Anagram (5) Art Of Programming-July (5) Assumption (5) Bellman Ford (5) Big Data (5) Code - Solid (5) Code Kata (5) Codility-lessons (5) Coding (5) Company - WMware (5) Convex Hull (5) Crazyforcode (5) DFS - Multiple (5) DFS+DP (5) DP - Multi-Dimension (5) DP-Multiple Relation (5) Eulerian Cycle (5) Graph - Unusual (5) Graph Cycle (5) Hash Strategy (5) Immutability (5) Java (5) LogN (5) Manhattan Distance (5) Matrix Chain Multiplication (5) N Queens (5) Pre-Sort: Index (5) Quick Partition (5) Quora (5) Randomized Algorithms (5) Resources (5) Robot (5) SPFA(Shortest Path Faster Algorithm) (5) Shuffle (5) Sieve of Eratosthenes (5) Strongly Connected Components (5) Subarray Sum (5) Sudoku (5) Suffix Tree (5) Swap (5) Threaded (5) Tree - Creation (5) Warshall Floyd (5) Word Search (5) jiuzhang (5)

Popular Posts