1. 程式人生 > >判斷凸多邊形並排序演算法

判斷凸多邊形並排序演算法

在平面直角座標系中,給定一個點序列,判斷這些點是否能夠構成凸多邊形,

並且按照順時針方向輸出這些點。

其他要求:
1.輸出的起始的為距離原點最近的點,如果多點距離原點相等,取其中任一點即可;
2.如果有3個或者以上點在一條直線上,輸出"ERROR";

輸入輸出格式要求:
1.輸入為用逗號分隔的10進位制整形數字序列的字串形式,兩兩組成一個座標點,如:
"0,0,1,0,1,1",代表輸入了P(0,0),P(1,0),P(1,1)三個點;

2.輸出形式同輸入一致;


解析:

一、構造順時針多邊形順序演算法:

1.先找一個距離原點最近的點A,然後隨便選一個點B,組成直線集合,可以理解成向量L(A,B),即A->B;

2.再依次遍歷剩下的點,向直線集合中插入;

2.1.依次遍歷直線集合;

2.2.當點X在直線(L1,由L1(startPoint,endPoint)左邊時,記錄L1位置i,並從直線集合中移除,同時在i,i+1位置依次插入L2(L1.startPoint,X),L3(X,L1.endPoint),執行2;

2.3.當點X在所有直線的右側時,新增隊尾直線的end節點到X的直線,執行2;

3.全部點遍歷完畢並插入後,新增隊尾直線的end節點到A點的直線;

4.直線集合中的起點順序即為所有點的順時針順序;


二、判斷是否是凸多邊形演算法

1.在獲取到順時針的直線集合後,依次遍歷直線,如果剩下的點在直線兩側,則說明是非凸多邊形,否則是凸多邊形;


三、判斷點是否在線上,還是在左邊,需要根據斜率來計算,計算斜率時,又要注意有些特殊的直線的斜率是不能直接計算的(除數為0的特殊情況)


四、原始碼解析

1、定義一個點物件,包含x,y座標,距離原點的距離

/*
 * <pre>
 * 文 件 名:  Point.java
 * 描    述:  <描述>
 * 修改時間:  2016-4-17
 * </pre> 
 */
package com.justinsoft.polygon.model;

/**
 * 點
 */
public class Point implements Comparable<Point>
{
    private final int x;
    
    private final int y;
    
    /**
     * 距離(原點)的平方
     */
    private final Integer distance;
    
    public Point(int x, int y)
    {
        this.x = x;
        this.y = y;
        
        this.distance = calcDistance(x, y);
    }
    
    /**
     * 獲取 x
     * 
     * @return 返回 x
     */
    public int getX()
    {
        return x;
    }
    
    /**
     * 獲取 y
     * 
     * @return 返回 y
     */
    public int getY()
    {
        return y;
    }
    
    /**
     * 過載方法
     * 
     * @return
     */
    @Override
    public String toString()
    {
        return "Point [x=" + x + ", y=" + y + "]";
    }
    
    /**
     * 過載方法
     * 
     * @param o
     * @return
     */
    @Override
    public int compareTo(Point o)
    {
        return getDistance().compareTo(o.getDistance());
    }
    
    /**
     * 計算距離(距離原點的)
     * 
     * <pre>
     * @param x
     * @param y
     * @return
     * </pre>
     */
    private static int calcDistance(int x, int y)
    {
        return x * x + y * y;
    }
    
    /**
     * 獲取 distance
     * 
     * @return 返回 distance
     */
    private Integer getDistance()
    {
        return distance;
    }
}


2、定義直線物件(向量),包含起點、終點、斜率

/*
 * <pre>
 * 文 件 名:  Line.java
 * 描    述:  <描述>
 * 修改時間:  2016-4-17
 * </pre> 
 */
package com.justinsoft.polygon.model;

/**
 * <pre>
 * 線
 * </pre>
 */
public class Line
{
    /**
     * 起點
     */
    private final Point start;
    
    /**
     * 終點
     */
    private final Point end;
    
    /**
     * 斜率
     */
    private final float slopeRate;
    
    /**
     * <預設建構函式>
     */
    public Line(Point start, Point end)
    {
        this.start = start;
        this.end = end;
        
        this.slopeRate = calcSlopeRate(start, end);
    }
    
    /**
     * 點是否在直線上
     * 
     * @param point
     * @return
     */
    public boolean containsPoint(Point point)
    {
        // 1.計算斜率
        float slopeRate = calcSlopeRate(point, getStart());
        // 2.如果斜率為0,需要判斷是否是特殊情況
        if (slopeRate == 0)
        {
            // 如果是分母相等,說明x座標相等,則只有噹噹前點與另一個點的x座標也相等時才在一條直線上
            float delatX = point.getX() - getStart().getX();
            if (0 == delatX)
            {
                return point.getX() == getEnd().getX();
            }
            else
            {
                return point.getY() == getEnd().getY();
            }
        }
        return slopeRate == getSlopeRate();
    }
    
    /**
     * 點是否在直線的左邊(按照線的起點到終點的方向來看點是左邊還是右邊)<br>
     * <br>
     * 取點的y座標時,比較線上的x座標與點的x座標大小<br>
     * k= (y-y1)/(x-x1) x =(y-y1)/k+x1
     * 
     * <pre>
     * @param point
     * @return
     * </pre>
     */
    public boolean hasLeftPoint(Point point)
    {
        // 1.判斷斜率的特殊情況
        if (0 == getSlopeRate())
        {
            // 1.1如果線是與x軸垂直的直線,則判斷點的x座標是否更小
            if (getEnd().getX() == getStart().getX())
            {
                return point.getX() < getStart().getX();
            }
        }
        
        // 2.計算線上的點的x座標
        float xInLine = (point.getY() - getStart().getY()) / getSlopeRate() + getStart().getX();
        
        return point.getX() < xInLine;
    }
    
    /**
     * 計算斜率
     * 
     * <pre>
     * @param p1
     * @param p2
     * @return
     * </pre>
     */
    private static float calcSlopeRate(Point p1, Point p2)
    {
        float delatY = p2.getY() - p1.getY();
        float delatX = p2.getX() - p1.getX();
        if (0 == delatX)
        {
            return 0;
        }
        return delatY / delatX;
    }
    
    /**
     * 獲取 start
     * 
     * @return 返回 start
     */
    public Point getStart()
    {
        return start;
    }
    
    /**
     * 獲取 end
     * 
     * @return 返回 end
     */
    public Point getEnd()
    {
        return end;
    }
    
    /**
     * 獲取 slopeRate
     * 
     * @return 返回 slopeRate
     */
    private float getSlopeRate()
    {
        return slopeRate;
    }
    
    /**
     * 過載方法
     * 
     * @return
     */
    @Override
    public String toString()
    {
        return "Line [start=" + start + ", end=" + end + ", slopeRate=" + slopeRate + "]";
    }
}
3、演算法實現類

package com.justinsoft.polygon;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import com.justinsoft.polygon.model.Line;
import com.justinsoft.polygon.model.Point;

/**
 * 在平面直角座標系中,給定一個點序列,判斷這些點是否能夠構成凸多邊形,<br>
 * 並且按照順時針方向輸出這些點。<br>
 * <br>
 * 其他要求:<br>
 * 1.輸出的起始的為距離原點最近的點,如果多點距離原點相等,取其中任一點即可;<br>
 * 2.如果有3個或者以上點在一條直線上,輸出"ERROR";<br>
 * <br>
 * 輸入輸出格式要求:<br>
 * 1.輸入為用逗號分隔的10進位制整形數字序列的字串形式,兩兩組成一個座標點,如:<br>
 * "0,0,1,0,1,1",代表輸入了P(0,0),P(1,0),P(1,1)三個點;<br>
 * 2.輸出形式同輸入一致;<br>
 * <br>
 * <br>
 */
public class PolygonSort
{
    /**
     * 多邊形最少點數
     */
    private static final int MIN_POINT_SIZE = 3;
    
    /**
     * 點裡面的元素
     */
    private static final int NUM_IN_POINT = 2;
    
    /**
     * 分隔符
     */
    private static final String SPLIT = ",";
    
    /**
     * 錯誤資訊
     */
    private static final String ERROR = "ERROR";
    
    public static String findConvexPolygon(String input)
    {
        try
        {
            // 1.獲取所有的點
            List<Point> allPoint = getAllPoint(input);
            
            // 總的點數
            int size = allPoint.size();
            
            // 2.判斷是否有一點在其他兩點所在的直線上
            boolean hasPointInLine = hasPointInLine(allPoint);
            if (hasPointInLine)
            {
                return ERROR;
            }
            
            // 3.取距離原點最近的一個點(之一)
            Point minPoint = getFirstPoint(allPoint);
            allPoint.remove(minPoint);
            
            // 4.再從佇列中任意移除一個點
            Point point = allPoint.remove(0);
            
            // 5.組成任意一條直線(從距離原點最小的點開始)
            Line line = new Line(minPoint, point);
            
            List<Line> allLine = new ArrayList<Line>(size);
            allLine.add(line);
            
            // 6.向已經存在的線中加入點,重新按照順時針連線(根據點線上的位置來進行判斷)
            for (Point leftPoint : allPoint)
            {
                addPointToLine(allLine, leftPoint);
            }
            
            int lastIndex = allLine.size() - 1;
            Line lastLine = allLine.get(lastIndex);
            
            // 7.線還沒有閉環,缺少從最後一個點到起點的直線
            Line tailLine = new Line(lastLine.getEnd(), minPoint);
            allLine.add(tailLine);
            
            // 8.判斷多邊形是否是凸多邊形
            boolean isConvexPolygon = isConvexPolygon(allLine);
            if (!isConvexPolygon)
            {
                return ERROR;
            }
            
            // 8.拼裝輸出結果
            String seqOrder = getSeqOrder(allLine);
            
            return seqOrder;
        }
        catch (Exception e)
        {
            return ERROR;
        }
    }
    
    /**
     * <pre>
     * 拼裝輸出結果
     * 
     * @param allLine
     * @return
     * </pre>
     */
    private static String getSeqOrder(List<Line> allLine)
    {
        StringBuilder order = new StringBuilder();
        for (Line line : allLine)
        {
            order.append(line.getStart().getX());
            order.append(SPLIT);
            order.append(line.getStart().getY());
            order.append(SPLIT);
        }
        
        if (order.toString().endsWith(SPLIT))
        {
            order.deleteCharAt(order.length() - 1);
        }
        return order.toString();
    }
    
    /**
     * <pre>
     * 判斷是否是凸多邊形
     * 
     * @param allLine
     * @return
     * </pre>
     */
    private static boolean isConvexPolygon(List<Line> allLine)
    {
        int size = allLine.size();
        List<Point> allPoint = new ArrayList<Point>(size);
        for (Line line : allLine)
        {
            allPoint.add(line.getStart());
        }
        
        for (int i = 0; i < size; i++)
        {
            boolean hasLeftPoint = false;
            boolean hasRightPoint = false;
            
            Line line = allLine.get(i);
            
            if (i + 2 < size)
            {
                // 獲取線外的剩下的點
                List<Point> allLeftPoint = getLeftPoint(allPoint, line);
                for (Point point : allLeftPoint)
                {
                    if (line.hasLeftPoint(point))
                    {
                        hasLeftPoint = true;
                    }
                    else
                    {
                        hasRightPoint = true;
                    }
                    
                    // 按照順時針連線後,如果有點在其中某條線的左邊,同時還有點在其右邊,說明是凹多邊形
                    if (hasLeftPoint && hasRightPoint)
                    {
                        return false;
                    }
                }
            }
        }
        return true;
    }
    
    /**
     * <pre>
     * 獲取線外的所有點
     * 
     * @param allPoint
     * @param exceptLine
     * @return
     * </pre>
     */
    private static List<Point> getLeftPoint(List<Point> allPoint, Line exceptLine)
    {
        List<Point> allTempPoint = new ArrayList<Point>(allPoint);
        allTempPoint.remove(exceptLine.getStart());
        allTempPoint.remove(exceptLine.getEnd());
        return allTempPoint;
    }
    
    /**
     * <pre>
     * 向所有直線中加入點
     * 
     * @param allLine
     * @param point
     * </pre>
     */
    private static void addPointToLine(List<Line> allLine, Point point)
    {
        boolean hasLeftPoint = false;
        int size = allLine.size();
        
        for (int i = 0; i < size; i++)
        {
            Line line = allLine.get(i);
            hasLeftPoint = line.hasLeftPoint(point);
            if (hasLeftPoint)
            {
                allLine.remove(i);
                
                Line newLeftLine1 = new Line(line.getStart(), point);
                Line newLeftLine2 = new Line(point, line.getEnd());
                
                allLine.add(i, newLeftLine2);
                allLine.add(i, newLeftLine1);
                break;
            }
        }
        
        if (!hasLeftPoint)
        {
            int lastIndex = size - 1;
            Line newLine = new Line(allLine.get(lastIndex).getEnd(), point);
            
            allLine.add(newLine);
        }
    }
    
    /**
     * <pre>
     * 獲取所有的點 
     * 
     * @param input
     * @return
     * @throws Exception
     * </pre>
     */
    private static List<Point> getAllPoint(String input)
        throws Exception
    {
        if (null == input)
        {
            throw new Exception();
        }
        
        List<String> allNum = Arrays.asList(input.split(SPLIT));
        
        int numSize = allNum.size();
        int pointSize = numSize / NUM_IN_POINT;
        // 組成點的元素個數如果不是2的倍數或者點的個數小於3,說明都不能組成多變性
        if (0 != numSize % NUM_IN_POINT || pointSize < MIN_POINT_SIZE)
        {
            throw new Exception();
        }
        
        List<Point> allPoint = new ArrayList<Point>(pointSize);
        try
        {
            for (int i = 0; i < numSize;)
            {
                int x = Integer.parseInt(allNum.get(i));
                int y = Integer.parseInt(allNum.get(i + 1));
                
                Point point = new Point(x, y);
                allPoint.add(point);
                
                i += 2;
            }
            
            return allPoint;
        }
        catch (NumberFormatException e)
        {
            throw new Exception();
        }
    }
    
    /**
     * 判斷是否有點在其他點的直線上
     * 
     * <pre>
     * 演算法如下:
     * 1.從集合中的第一個點開始遍歷,並出棧;
     * 2.遍歷的當前點(i)和後面的每個點(序號為j,大小依次為i+1,i+2...)組成一條直線,由於當前點已出棧,j的實際序號為j-1;
     * 3.判斷直線後面的點(序號為i+2,由於當前點已出棧,實際序號為i+1開始),是否在這邊直線上
     * @param allPoint
     * @return
     * </pre>
     */
    private static boolean hasPointInLine(List<Point> allPoint)
    {
        List<Point> allTempPoint = new ArrayList<Point>(allPoint);
        Iterator<Point> iterator = allTempPoint.iterator();
        while (iterator.hasNext())
        {
            Point point = iterator.next();
            iterator.remove();
            
            int size = allTempPoint.size();
            for (int i = 0; i < size; i++)
            {
                Line line = new Line(point, allTempPoint.get(i));
                if (i + 1 < size)
                {
                    List<Point> allLeftPoint = allTempPoint.subList(i + 1, size);
                    if (hasPointInLine(line, allLeftPoint))
                    {
                        return true;
                    }
                }
            }
        }
        
        return false;
    }
    
    /**
     * <pre>
     * 直線上是否有該點
     * 
     * @param line
     * @param otherPoint
     * @return
     * </pre>
     */
    private static boolean hasPointInLine(Line line, List<Point> otherPoint)
    {
        for (Point point : otherPoint)
        {
            if (line.containsPoint(point))
            {
                return true;
            }
        }
        return false;
    }
    
    /**
     * 取距離原點最小的點
     * 
     * <pre>
     * @param allPoint
     * @return
     * </pre>
     */
    private static Point getFirstPoint(List<Point> allPoint)
    {
        List<Point> allTempPoint = new ArrayList<Point>(allPoint);
        Collections.sort(allTempPoint);
        
        return allTempPoint.get(0);
    }
}
4、單元測試類

/*
 * <pre>
 * 文 件 名:  PolygonSortTest.java
 * 描    述:  <描述>
 * 修改時間:  2016-4-17
 * </pre> 
 */
package com.justinsoft.polygon;

import static org.junit.Assert.assertTrue;

import org.junit.Test;

/**
 * <pre>
 * <一句話功能簡述>
 * 
 * </pre>
 */
public class PolygonSortTest
{
    
    /**
     * Test method for {@link com.justinsoft.polygon.PolygonSort#findConvexPolygon(java.lang.String)}.
     */
    @Test
    public void testFindConvexPolygon1()
    {
        String input = "1,2,3";
        String result = PolygonSort.findConvexPolygon(input);
        
        assertTrue("ERROR".equalsIgnoreCase(result));
    }
    
    /**
     * Test method for {@link com.justinsoft.polygon.PolygonSort#findConvexPolygon(java.lang.String)}.
     */
    @Test
    public void testFindConvexPolygon2()
    {
        String input = "0,0,1,1,0,1";
        String result = PolygonSort.findConvexPolygon(input);
        
        assertTrue("0,0,0,1,1,1".equalsIgnoreCase(result));
    }
    
    /**
     * Test method for {@link com.justinsoft.polygon.PolygonSort#findConvexPolygon(java.lang.String)}.
     */
    @Test
    public void testFindConvexPolygon3()
    {
        String input = "0,0,1,1,0,1,1,0";
        String result = PolygonSort.findConvexPolygon(input);
        
        assertTrue("0,0,0,1,1,1,1,0".equalsIgnoreCase(result));
    }
    
    /**
     * Test method for {@link com.justinsoft.polygon.PolygonSort#findConvexPolygon(java.lang.String)}.
     */
    @Test
    public void testFindConvexPolygon4()
    {
        String input = "0,0,1,1,0,3,3,0";
        String result = PolygonSort.findConvexPolygon(input);
        
        assertTrue("ERROR".equalsIgnoreCase(result));
    }
}