algorithm第三周作业 Collinear Points

cousera 上algorithm part I第三周课程讲述的是排序,包括插入排序、选择排序、希尔排序、归并排序和快速排序。其配套作业为Collinear Points,题目大意为给定若干点,求出其中的有四个及以上点共线的线段。
要求提交三个文件,Point.java,BruteCollinearPoints.java,FastCollinearPoints.java。

Point类

给定的的接口如下:

public class Point implements Comparable<Point> {
   public Point(int x, int y)                         // constructs the point (x, y)

   public   void draw()                               // draws this point
   public   void drawTo(Point that)                   // draws the line segment from this point to that point
   public String toString()                           // string representation
//上面的函数已经写好了,只要实现下面三个函数即可
   public               int compareTo(Point that)     // compare two points by y-coordinates, breaking ties by x-coordinates
   public            double slopeTo(Point that)       // the slope between this point and that point
   public Comparator<Point> slopeOrder()              // compare two points by slopes they make with this point
}

Points类实现的难点和注意点:
1.对java初学中来说,compareTo和Comparator实现有一些难点。简而言之,这两个接口的实现都是用来做比较的,两者的差别在于,打个比方,compareTo是默认比较器,Comparator是给默认分类器做补充的比较器。

Point[] points = new Point[10];
....//初始化Points
Point anchor = new Point(3,3);
Arrays.sort(points);//默认情况下用CompareTo里实现的比较方法来排序
Arrays.sort(points,anchor.slopeOrder())//不想用默认的,自己再写一个比较器来排序。

Point类实现如下:

/******************************************************************************
 *  Compilation:  javac Point.java
 *  Execution:    java Point
 *  Dependencies: none
 *
 *  An immutable data type for points in the plane.
 *  For use on Coursera, Algorithms Part I programming assignment.
 *
 ******************************************************************************/
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import edu.princeton.cs.algs4.StdDraw;

public class Point implements Comparable<Point> {

    private final int x;     // x-coordinate of this point
    private final int y;     // y-coordinate of this point
    public Point(int x, int y) {
        /* DO NOT MODIFY */
        this.x = x;
        this.y = y;
    }

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

    /**
     * Draws the line segment between this point and the specified point
     * to standard draw.
     *
     * @param that the other point
     */
    public void drawTo(Point that) {
        /* DO NOT MODIFY */
        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) {
        /* YOUR CODE HERE */
        if (this.y == that.y) //斜率为0或者同一个位置的点
        {
            if (this.x == that.x)
            {
                return Double.NEGATIVE_INFINITY;
            }
            else
            {
                return 0.0;//斜率为0
            }
        }
        else {
            if (this.x == that.x)
            {
                return Double.POSITIVE_INFINITY;
            }
            else
            {
                return 1.0 * (that.y - this.y) / (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) {
        /* YOUR CODE HERE */
        if (this.y < that.y)
        {
            return -1;
        }
        else if (this.y > that.y)
        {
            return 1;
        }
        else
        {
            if (this.x < that.x)
            {
                return -1;
            }
            else if (this.x > that.x)
            {
                return 1;
            }
            else
            {
                return 0;
            }
        }
    }

    /**
     * Compares two points by the slope they make with this point.
     * The slope is defined as in the slopeTo() method.
     *
     * @return the Comparator that defines this ordering on points
     */

    private class BySlope implements Comparator<Point>
    {
        @Override
        public int compare(Point o1, Point o2)
        {
            double slope1 = slopeTo(o1);
            double slope2 = slopeTo(o2);
            if (slope1 < slope2)
            {
                return -1;
            }
            else if(slope1 > slope2)
            {
                return 1;
            }
            else {
                return 0;
            }
        }
    }

    public Comparator<Point> slopeOrder()
    {

        /* YOUR CODE HERE */
        return  new Point.BySlope();
    }


    /**
     * Returns a string representation of this point.
     * This method is provide for debugging;
     * your program should not rely on the format of the string representation.
     *
     * @return a string representation of this point
     */
    public String toString() {
        /* DO NOT MODIFY */
        return "(" + x + ", " + y + ")";
    }

    /**
     * Unit tests the Point data type.
     */
    public static void main(String[] args) {
        /* YOUR CODE HERE */
        Point x = new Point(0,0);
        Point p = new Point(303, 104);
        Point q = new Point(188, 16);
        Point[] points = {new Point(1,2), new Point(3,4), new Point(2,4), new Point(5,7)};
        //Arrays.sort(points,x.slopeOrder());
        /*StdDraw.enableDoubleBuffering();
        StdDraw.setXscale(-5,5);
        StdDraw.setYscale(-5, 5);
        x.draw();
        points[1].draw();
        StdDraw.show();*/
        System.out.println(p.slopeTo(q));
        /*for(Point ele:points)
        {
            ele.draw();
        }*/
    }
}

BruteCollinearPoints类的实现

从这个类开始计算拥有四个以上共线点的线段,但这个类不会放入五个即以上共线点线段的数据,而且时间复杂度要求为O(n^2),所以只要用四个for循环即可。
难点和注意点:
1.如何保证程序输出的线段和cousera给出的线段一样,因为如果点A,B,C,D共线,线段可以是AB,AC,AB,BC,如何让这些保证这些表示同一段线段的线段仅输出一次,且每次按同样的规范输出。
方法:将数组中的点排序,每次输出的端点为排序中最小的和最大的点就可以保证只输出一次,且规范相同了。
2.如果在类中用类成员线段数组来存储结果,那在segments()中不应该直接return这个数组,因为如果直接return的话,就给了外部类修改类成员的机会,破坏了封装性,应该返回一个副本。

class BruteCollinearPoints
{
	Linesegment[] lines;//存放结果
	int segNum;
	linesegment[] segments()//返回结果
	{
		//return results; 直接输出,错误,破坏了封装性 
	 	LineSegment[] result = new LineSegment[segNum];
     	for(int i = 0; i < segNum; i++)
     	{
            result[i] = lines[i];
     	}
        return result;//返回lines的副本
    }

3.对于斜率之间的比较,不能用简单的==,因为存在垂线,即横座标相等的点,这个时候它们的斜率为Double.POSITIVE_INFINITY,不能用简单的==比较,得用Double.compare
类实现如下:

/******************************************************************************
 *  Name:    lark
 *  CreatedTime: 2020/2/10 23:06.
 *
 *  Description:  暴力求出包含四个共线点的线段
 ******************************************************************************/

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

//如何使得空间复杂度为n+线段个数,因为数组是不可伸缩的
public class BruteCollinearPoints
{
    private int segNum = 0;
    private LineSegment[] lines;
    public BruteCollinearPoints(Point[] points)// finds all line segments containing 4 points
    {
        if (points == null)
        {
            throw new IllegalArgumentException();
        }
        Point[] pts = new Point[points.length];
        for (int i = 0; i < points.length; i++)
        {
            if (points[i] == null)
            {
                throw new IllegalArgumentException();
            }
            pts[i] = points[i];
        }
        Arrays.sort(pts);//点按照x,y大小排序
        if (isduplicate(pts))//有重复的
        {
            throw new IllegalArgumentException();
        }
        LineSegment[] tempresult = new LineSegment[points.length];
        for (int i = 0; i < pts.length; i++)
        {
            for ( int j = (i + 1); j < pts.length; j++)
            {
                for (int k = (j + 1); k < pts.length; k++)
                {
                    for (int l = (k + 1); l < pts.length; l++)
                    {
                        if (Double.compare(pts[i].slopeTo(pts[j]), pts[k].slopeTo(pts[j])) == 0 && Double.compare(pts[k].slopeTo(pts[l]), pts[k].slopeTo(pts[j])) == 0 )
                        {
                            tempresult[segNum++] = new LineSegment(pts[i],pts[l]);
                        }
                    }
                }
            }
        }
        lines = Arrays.copyOf(tempresult,segNum);
        tempresult = null;

    }

    public int numberOfSegments()        // the number of line segment
    {
        return segNum;
    }

    public LineSegment[] segments()                // the line segments
    {
        //将结果复制一份传出去,如果直接将lines传出去,别人可以通过函数返回值在main里直接修改lines。
        LineSegment[] result = new LineSegment[segNum];
        for(int i = 0; i < segNum; i++)
        {
            result[i] = lines[i];
        }
        return result;
    }

    private boolean isduplicate(Point[] items)
    {
        for (int i = 1; i < items.length; i++)
        {
            if (items[i].compareTo(items[i-1]) == 0)
            {
                return true;
            }
        }
        return false;
    }
    public static  void main(String[] args)
    {
        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);
        BruteCollinearPoints collinear = new BruteCollinearPoints(points);
        collinear.numberOfSegments();
        collinear.numberOfSegments();
        for (LineSegment segment : collinear.segments()) {
            StdOut.println(segment);
            //segment.draw();
        }
        collinear.numberOfSegments();
        collinear.numberOfSegments();
        collinear.numberOfSegments();
        collinear.numberOfSegments();
        for (LineSegment segment : collinear.segments()) {
            StdOut.println(segment);
            //segment.draw();
        }

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

FastCollinearPoints的实现

FastCollinearPoints类的输入包括五个以上共线点的线段,而且时间复杂度要求为O(n^2longn),此时再用BruteCollinearPoints类就不行了。
思路:
以数组中的一个点A为基点,将其他点按和点A的斜率排序,若A存在共线点B,C,D,那么排序后BCD三个点肯定是在一起的,因为它们和点A的斜率一样。计算有多少个斜率一样的点,如果大于等于三个,那么就说明这条线段满足条件(A点本来就算一个共线点)
难点和注意点:
1.要对数组中的每个点采用上面的思路来求共线点线段,这样的话如何避免重复,保证唯一性。比如,ABCD共线,以点A为基点时,可以求出ABCD线段,以点B为基点时,又可以求出ABCD线段。
解决方法:以每个点为基点时,只求基点为线段中最小点的线段,这样就可以避免重复,以ABCD为例,线段ABCD只在基点为A时(A最小)求出。
2.对于垂直线的,即斜率为正无穷大共线点的测试,需要注意for循环的边界条件
代码如下:

/******************************************************************************
 *  Name:    lark
 *  CreatedTime: 2020/2/11 09:28.
 *
 *  Description:  
 ******************************************************************************/

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 LineSegment[] lines;
    private int linenum = 0;

    public FastCollinearPoints(Point[] points)     // finds all line segments containing 4 or more points
    {
        if (points == null)
        {
            throw new  IllegalArgumentException();
        }
        int len = points.length;
        Point[] copy1 = new Point[len];
        Point[] copy2 = new Point[len];
        LineSegment[] lineset = new LineSegment[len * len];//申请一个大数组暂时存放结果
        copy(points,copy1);
        Arrays.sort(copy1);
        copy(copy1,copy2);
        if (isduplicate(copy1))//有重复的
        {
            throw new IllegalArgumentException();
        }
        for (int i = 0; i < len; i++)
        {
            Arrays.sort(copy2,copy1[i].slopeOrder());//根据斜率排序
            Point min = copy2[0];
            Point max = copy2[0];
            int count = 1;
            for (int j = 1; j < len; j++)
            {
                if (Double.compare(copy1[i].slopeTo(copy2[j]), copy1[i].slopeTo(copy2[j-1])) == 0 )
                {
                    if (copy2[j].compareTo(min) < 0)
                    {
                        min = copy2[j];
                    }
                    else if (copy2[j].compareTo(max) > 0)
                    {
                        max = copy2[j];
                    }
                    count++;
                    if (j == (len - 1) && count >= 3 && min.compareTo(copy1[i]) > 0)
                    {
                        lineset[linenum++] = new LineSegment(copy1[i],max);
                    }
                }
                else
                {
                    if (count >= 3 && min.compareTo(copy1[i]) > 0) {
                        lineset[linenum++] = new LineSegment(copy1[i],max);
                    }
                        min = copy2[j];
                        max = copy2[j];
                        count = 1;
                }
            }
        }
        lines = Arrays.copyOf(lineset, linenum);
    }
    public int numberOfSegments()        // the number of line segments
    {
        return linenum;
    }

    public LineSegment[] segments()                // the line segments
    {
        LineSegment[] result = new LineSegment[linenum];
        for(int i = 0; i < linenum; i++)
        {
            result[i] = lines[i];
        }
        return result;
    }

    private void copy(Point[] origin, Point[] newarray)
    {
        int len = origin.length;
        for(int i = 0 ; i < len ; i++) //复制一份辅助数组
        {
            if (origin[i] == null) {
                throw new IllegalArgumentException();
            }
            newarray[i] = origin[i];
        }
    }

    private boolean isduplicate(Point[] items)
    {
        for (int i = 1; i < items.length; i++)
        {
            if (items[i].compareTo(items[i-1]) == 0)
            {
                return true;
            }
        }
        return false;
    }

    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);
        for (Point p : points) {
            p.draw();
        }
        StdDraw.show();

        // print and draw the line segments
        FastCollinearPoints collinear = new FastCollinearPoints(points);
        for (LineSegment segment : collinear.segments()) {
            StdOut.println(segment);
            segment.draw();
        }
        StdDraw.show();
    }
}
发布了9 篇原创文章 · 获赞 6 · 访问量 3106
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章