判断凸多边形并排序算法

在平面直角坐标系中,给定一个点序列,判断这些点是否能够构成凸多边形,

并且按照顺时针方向输出这些点。

其他要求:
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));
    }
}





猜你喜欢

转载自blog.csdn.net/dobuy/article/details/51176060