《算法》编程作业3-Pattern Recognition/Collinear Points

问题描述:http://coursera.cs.princeton.edu/algs4/assignments/collinear.html
我的代码:https://github.com/hym105289/Pattern-Recognition/

1.基本介绍

给定n个不同的点,识别出至少4个点在一条直线上的点集。


p——q——r——s,如果这四个点在一条直线上,那么斜率pq=斜率qr=斜率rs,我们可以从n个点中随意地找4个点,然后判断斜率是否相等,识别出这四个点是否在一条直线上。
难点:
①p-q-r-s,也可以以是p-q-s-r等,如何避免重复搜索,这是一个要考虑的问题。
②java实现中的Comparable和Comparator接口
③p-q-r-s线段,只能记录成p-s,任何子线短p-r等都不应该保存,如何保证线段只记录一次,是一个非常难的问题。

Point.java

import java.util.Arrays;
import java.util.Comparator;
import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.StdDraw;
import edu.princeton.cs.algs4.StdOut;

public class Point implements Comparable<Point> {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public void draw() {
        StdDraw.point(x, y);
    }

    public void drawTo(Point that) {
        StdDraw.line(this.x, this.y, that.x, that.y);
    }

    /**
     * Returns the slope between this point and the specified point. Formally,
     * if the two points are (x0, y0) and (x1, y1), then the slope is (y1 - y0)
     * / (x1 - x0). For completeness, the slope is defined to be +0.0 if the
     * line segment connecting the two points is horizontal;
     * Double.POSITIVE_INFINITY if the line segment is vertical; and
     * Double.NEGATIVE_INFINITY if (x0, y0) and (x1, y1) are equal.
     *
     * @param that
     *            the other point
     * @return the slope between this point and the specified point
     */
    public double slopeTo(Point that) {
        if (this.x == that.x && this.y == that.y) {
            return Double.NEGATIVE_INFINITY;
        } else if (this.x == that.x) {
            return Double.POSITIVE_INFINITY;
        } else if (this.y == that.y) {
            return +0.0;
        } else {
            return (that.y - this.y) / (double) (that.x - this.x);
        }
    }

    /**
     * Compares two points by y-coordinate, breaking ties by x-coordinate.
     * Formally, the invoking point (x0, y0) is less than the argument point
     * (x1, y1) if and only if either y0 < y1 or if y0 = y1 and x0 < x1.
     *
     * @param that
     *            the other point
     * @return the value <tt>0</tt> if this point is equal to the argument point
     *         (x0 = x1 and y0 = y1); a negative integer if this point is less
     *         than the argument point; and a positive integer if this point is
     *         greater than the argument point
     */
    public int compareTo(Point that) {
        if (this.x == that.x && this.y == that.y) {
            return 0;
        } else if (this.y < that.y || (this.y == that.y && this.x < that.x)) {
            return -1;
        } else {
            return 1;
        }
    }

    public Comparator<Point> slopeOrder() {
        return new SlopeOrder();
    }

    private class SlopeOrder implements Comparator<Point> {
        public int compare(Point p1, Point p2) {
            double slope1 = slopeTo(p1);
            double slope2 = slopeTo(p2);
            if (slope1 < slope2) {
                return -1;
            } else if (slope1 > slope2) {
                return 1;
            } else {
                return 0;
            }
        }
    }

    public String toString() {
        return "(" + x + "," + y + ")";
    }

    public static void main(String[] args) {
        String filename = args[0];
        In in = new In(filename);
        int N = in.readInt();
        Point[] points = new Point[N];
        for (int i = 0; i < N; i++) {
            int x = in.readInt();
            int y = in.readInt();
            Point p = new Point(x, y);
            points[i] = p;
        }
        Arrays.sort(points, points[0].slopeOrder());
        for (Point p : points)
            StdOut.println(p);
    }
}

Comparable和Comparatoe都是java的一个接口,并且是用来对自定义的class比较大小的。
Comparable:定义在类的内部,实现的函数是comparTo,当我们调用Array,sort(points)时,覆盖compareTo的比较大小的方式进行排序。在Point类中,根据y坐标进行排序,当y相同时再根据x坐标进行排序。
Comparator:一般是定义在类的外部,但是在这个例子中我们定义在类的内部,使用private class SlopeOrder implements Comparator<Point> {}进行定义,假设给定了内部点p,外部点q和r,将q和r按照斜率pq和斜率pr进行排序。覆盖compare方法。
Comparable:

public class Person implements Comparable<Person> {
     String name;
     int age
     public int compareTo(Person another) {
          int i = 0;
          i = name.compareTo(another.name); // 使用字符串的比较
          if(i == 0) { // 如果名字一样,比较年龄, 返回比较年龄结果
               return age - another.age;
          } else {
               return i; // 名字不一样, 返回比较名字的结果.
          }
     }
}

使用Collections.sort( persons)就可以对persons进行排序。
Comparator:

class PersonComparator implements Comparator <Person>{ 
     public int compare(Person one, Person another) {
          int i = 0;
          i = one.name.compareTo(another.name); // 使用字符串的比较
          if(i == 0) { // 如果名字一样,比较年龄,返回比较年龄结果
               return one.age - another.age;
          } else {
               return i; // 名字不一样, 返回比较名字的结果.
          }
     }
}

使用 Collections.sort( persons , new PersonComparator()) 可以对其排序。

BruteCollinearPoints.java

import java.util.Arrays;
import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.StdDraw;

public class BruteCollinearPoints {
    private int lineNumber;
    private ListNode last;

    private class ListNode {
        private LineSegment val;
        private ListNode pre;
    }

    public BruteCollinearPoints(Point[] points) {
        if (points == null) {
            throw new IllegalArgumentException();
        }
        int num = points.length;
        Point[] copy = new Point[num];
        if (num < 4)
            return;
        for (int i = 0; i < num; i++) {
            if (points[i] == null) {
                throw new IllegalArgumentException();
            }
            for (int j = i + 1; j < num; j++) {
                if (points[i].compareTo(points[j]) == 0) {
                    throw new IllegalArgumentException();
                }
            }
            copy[i] = points[i];
        }
        Arrays.sort(copy, 0, num);// be careful
        for (int i = 0; i < num; i++) {
            for (int j = i + 1; j < num; j++) {
                for (int k = j + 1; k < num; k++) {
                    for (int l = k + 1; l < num; l++) {
                        double slope1 = copy[i].slopeTo(copy[j]);
                        double slope2 = copy[j].slopeTo(copy[k]);
                        double slope3 = copy[k].slopeTo(copy[l]);
                        if (Double.compare(slope1, slope2) == 0 && Double.compare(slope2, slope3) == 0) {
                            addLine(copy[i], copy[l]);
                            lineNumber++;
                        }
                    }
                }
            }
        }
    }

    private void addLine(Point a, Point b) {
        if (last != null) {
            ListNode node = new ListNode();
            node.pre = last;
            node.val = new LineSegment(a, b);
            last = node;
        } else {
            last = new ListNode();
            last.val = new LineSegment(a, b);
        }
    }

    public int numberOfSegments() {
        return lineNumber;
    }

    public LineSegment[] segments() {
        LineSegment[] lines = new LineSegment[lineNumber];
        ListNode pNode = last;
        for (int i = 0; i < lineNumber; i++) {
            lines[i] = pNode.val;
            pNode = pNode.pre;
        }
        return lines;
    }

    public static void main(String[] args) {
        StdDraw.enableDoubleBuffering();
        StdDraw.setXscale(0, 32768);
        StdDraw.setYscale(0, 32768);
        StdDraw.setPenRadius(0.01);
        String filename = args[0];
        In in = new In(filename);
        int n = in.readInt();
        Point[] points = new Point[n];
        for (int i = 0; i < n; i++) {
            int x = in.readInt();
            int y = in.readInt();
            Point p = new Point(x, y);
            points[i] = p;
            p.draw();
        }
        StdDraw.show();
        BruteCollinearPoints collinear = new BruteCollinearPoints(points);
        System.out.println(collinear.lineNumber);
        for (LineSegment segment : collinear.segments()) {
            System.out.println(segment);
            segment.draw();
        }
        StdDraw.show();
    }
}

这里要注意的地方是在随机选取之前,要先进行排序,这样能够解决重复搜索的问题。比如一共有6个点:排序后的结果1,2,3,4,5,6;这样就能避免1234组合出现后再出现2134,4123等序列,这样能够保证取了四个相同点的线段只能出现一次。
暴力的解法中,如果四个点在一条直线中就把start,end插入到链表中即可。
比如input6.txt中有如下内容

6
19000 10000
18000 10000
32000 10000
21000 10000
1234 5678
14000 10000

根据Comparatble接口排序后:1234,14000,18000,19000,21000;已知14000,18000,19000,21000,32000这5点共线;当第一个点是1234时没有任何线段加入,当第一个点是14000,可以产生14000, 18000, 19000, 21000;14000,18000,19000,32000;14000, 18000, 21000, 32000; 14000,19000,21000,32000四个线段,当第一个点为18000时可以产生18000,19000,21000,32000一个线段;不可能产生相同的四个点;

FastCollinearPoints.java

import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.StdDraw;
import edu.princeton.cs.algs4.StdOut;

import java.util.Arrays;

public class FastCollinearPoints {
    private int lineNumber;
    private ListNode last;

    private class ListNode {
        private LineSegment val;
        private ListNode pre;
    }

    public FastCollinearPoints(Point[] points) // finds all line segments
                                                // containing 4 or more points
    {
        if (points == null) {
            throw new IllegalArgumentException();
        }
        int num = points.length;
        Point[] copy = new Point[num];
        for (int i = 0; i < num; i++) {
            if (points[i] == null) {
                throw new IllegalArgumentException();
            }
            for (int j = i + 1; j < num; j++) {
                if (points[i].compareTo(points[j]) == 0) {
                    throw new IllegalArgumentException();
                }
            }
            copy[i] = points[i];
        }
        Arrays.sort(copy, 0, num);// be careful
        if (num < 4) {
            return;
        }
        for (int i = 0; i < num - 1; i++) {
            Point origPoint = copy[i];
            int otherPointsN = 0;
            Point[] otherPoints = new Point[num - 1];

            for (int j = 0; j < num; j++) {
                if (i != j)
                    otherPoints[otherPointsN++] = copy[j];
            }
            Arrays.sort(otherPoints, copy[i].slopeOrder());
            int count = 0;
            Point min = null;
            Point max = null;
            for (int j = 0; j < otherPointsN - 1; j++) {
                if (Double.compare(origPoint.slopeTo(otherPoints[j]), origPoint.slopeTo(otherPoints[j + 1])) == 0) {
                    count++;
                    if (min == null && max == null) {
                        if (origPoint.compareTo(otherPoints[j]) > 0) {
                            max = origPoint;
                            min = otherPoints[j];
                        } else {
                            max = otherPoints[j];
                            min = origPoint;
                        }
                    }
                    if (otherPoints[j + 1].compareTo(min) < 0) {
                        min = otherPoints[j + 1];
                    }
                    if (otherPoints[j + 1].compareTo(max) > 0) {
                        max = otherPoints[j + 1];
                    }
                    if (j == otherPointsN - 2 && count >= 2 && origPoint.compareTo(min) == 0) {
                        addLine(min, max);
                        lineNumber++;
                    }
                } else {
                    if (count >= 2 && origPoint.compareTo(min) == 0) {
                        addLine(min, max);
                        lineNumber++;
                    }
                    count = 0;
                    max = null;
                    min = null;
                }
            }
        }
    }

    private void addLine(Point a, Point b) {
        if (last != null) {
            ListNode node = new ListNode();
            node.pre = last;
            node.val = new LineSegment(a, b);
            last = node;
        } else {
            last = new ListNode();
            last.val = new LineSegment(a, b);
        }
    }

    public int numberOfSegments() // the Nber of line segments
    {
        return lineNumber;
    }

    public LineSegment[] segments() // the line segments
    {
        LineSegment[] lines = new LineSegment[lineNumber];
        ListNode current = last;

        for (int i = 0; i < lineNumber; i++) {
            lines[i] = current.val;
            current = current.pre;
        }

        return lines;
    }

    public static void main(String[] args) {
        // read the n points from a file
        In in = new In(args[0]);
        int n = in.readInt();
        Point[] points = new Point[n];

        for (int i = 0; i < n; i++) {
            int x = in.readInt();
            int y = in.readInt();
            points[i] = new Point(x, y);
        }

        // draw the points
        StdDraw.enableDoubleBuffering();
        StdDraw.setXscale(0, 32768);
        StdDraw.setYscale(0, 32768);
        StdDraw.setPenRadius(0.01);
        for (Point p : points) {
            p.draw();
        }

        StdDraw.show();

        // print and draw the line segments
        FastCollinearPoints collinear = new FastCollinearPoints(points);
        System.out.println(collinear.lineNumber);
        for (LineSegment segment : collinear.segments()) {
            StdOut.println(segment);
            segment.draw();
        }
        StdDraw.show();
    }
}

基本思想:加入给定点p,将剩余的所有点,根据和p的斜率进行排序,如果至少有pqr三个点到p点的斜率都相等,那就说明pqrs四点共线。
但是这样可能会出现一个问题:p到qrs的斜率都相同,pqrs四点共线,还会出现p到qsr的斜率也相同的情况,要求我们同一个线段只记录最长的那一段,p-q-r-s只添加线段p-s,同一个线段我们只记录min和max即可,不管遍历四个点的顺序如何我们都只记录min和max;
Example:
当前点p=14000,其余点按斜率排序:1234,18000,19000,21000,3200;斜率分别是-0.338,+0.0,+0.0,+0.0;这里没有区分+0和-0是因为题目本身要求当y相同时k=+0.0;基本思路是扫描一遍斜率,更新min和max,记录连续相同k的个数,当k>=2时并且当前点=min时添加min&max;添加<14000,32000>(不要忘记处理最后两个数据相等的情况)
当前点p=18000,其余点:1234,14000,19000,21000,32000;斜率分别是:-0.257,+0.0,+0.0,+0.0;出现了三个相同的斜率,说明四点共线,但是18000!=min=14000,则说明该线段已经保存过了,所以不能添加,这样我们就能多点共线只保留min和max了;上述的数据集保存的线段就是<14000,32000>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容