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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章