软件构造lab1凸包算法

本文对lab1中使用到的凸包算法进行阐述。

凸包

先来看凸包的定义。此处摘自百度百科:

        点集Q的凸包(convex hull)是指一个最小凸多边形,满足Q中的点或者在多边形边上或者在其内。图1由红色线段表示的多边形就是点集Q={p0,p1,...p12}的凸包。

        简单来讲,一组平面上的点,求一个包含所有点的最小的凸多边形,这就是凸包问题了。这可以形象地想成这样:在地上放置一些不可移动的木桩,用一根绳子把他们尽量紧地圈起来,并且为凸边形,这就是凸包了。

凸包算法:

        我在实验中使用的算法步骤如下:

  1. 如果点数少于等于三个,可以直接返回这些点。
  2. 如果点数多于三,则对这些点进行处理。首先取纵坐标最小的点,然后以这个点为原点计算出新的坐标。
  3. 以新的坐标计算每个角的幅角,并按照幅角由小到大的顺序对点进行排序。如果幅角相等,则将与原点距离更近的排在前面。
  4. 对处理完的点,易知第一个和第二个点是在凸包中的。连接这两个点,计算第三个点与这个位置的相对位置。如果点在直线的左边,跳转到5),否则跳转到6)
  5. 这个点是凸包中的点。连接这个点和上一个点,产生接下来作为基准的线,然后继续处理。
  6. 这个点的上一个点不是凸包中的点。把这个点从凸包中剔除,同时连接上上个点和上上上个点,产生接下来作为基准的线,然后继续处理
  7. 当处理完最后一个点后,剩下来的点就是凸包中的点。

        具体实现过程中,要处理给到的点集。由于给的是Set类,这个类不能简单遍历所有元素,要调用迭代器或使用另一种for循环。我选择将Set的点集修改为List类,这样就有了固定的顺序,方便后续操作处理。

        在判断凸包的点时,可以使用栈,从而记录之前处理后的数据。这样能在回退到之前的点时直接从栈中取数据,不需要再额外记录。

        代码如下

    public static Set<Point> convexHull(Set<Point> points) {
    	Set<Point> BeginPoints = new HashSet<Point>();
    	Set<Point> FinalPoints = new HashSet<Point>();
    	List<Point> TempPoints2 = new ArrayList<>();
    	
    	BeginPoints.addAll(points);
    	int Number = BeginPoints.size();
    	
    	if(Number <= 3)//少于或只有三个点可以直接返回
    		return BeginPoints;
    	else
    	{
    		Iterator<Point> it1 = BeginPoints.iterator();
    		Point yLowest = it1.next();
			TempPoints2.add(yLowest);
    		Point TempPoint = null;
    		while(it1.hasNext())//取纵坐标最小的点
    		{
    			TempPoint = it1.next();
    			TempPoints2.add(TempPoint);
    			if(TempPoint.y() < yLowest.y())
    				yLowest = TempPoint;
    		}
    		
    		List<Point> TempPoints1 = new ArrayList<>();
    		double[] alpha = new double[Number];//存放幅角
    		double[] distance = new double[Number];//存放距离
    		int[] index = new int[Number];//存放排序后的序列
    		for (int i = 0; i < Number ; i++)//把所有点的坐标平移,使得纵坐标最小的点为原点
    		{
    			TempPoint = new Point(TempPoints2.get(i).x() - yLowest.x(), TempPoints2.get(i).y() - yLowest.y());
    			
    			TempPoints1.add(TempPoint);
    		}
    		
    		for (int i = 0; i < Number ; i++)//计算幅角alpha和与原点距离distance
    		{
    			distance[i] = Math.pow(Math.pow(TempPoints1.get(i).x(),2)+Math.pow(TempPoints1.get(i).y(),2),0.5);
    			alpha[i] = Math.atan2(TempPoints1.get(i).y(), TempPoints1.get(i).x());
    			index[i] = i;
    		}
    		
    		for (int i = 0; i < Number - 1 ; i++)//按由小到大顺序排列
    		{
    			for(int j = 0; j < Number - i - 1; j++)
    			{
    				if(alpha[j] > alpha[j+1])
    				{
    					int tempIndex = index[j];
    					index[j] = index[j+1];
    					index[j+1] = tempIndex;
    					
    					double tempdegree = alpha[j];
    					alpha[j] = alpha [j+1];
    					alpha[j+1] = tempdegree;
    				}
    				else if(alpha[j] == alpha[j+1])
    				{
    					if(distance[j] > distance[j+1])//幅角相等,把距离更近的排在前面
    					{
    						int tempIndex = index[j];
        					index[j] = index[j+1];
        					index[j+1] = tempIndex;
        					
        					double tempdegree = alpha[j];
        					alpha[j] = alpha [j+1];
        					alpha[j+1] = tempdegree;
    					}
    				}
    			}
    		}//得到幅角alpha从小到大排列的顺序,存放在index[Number]中
    		
    		//开始确定凸包的各个点
    		Stack<Integer> stack = new Stack<>();
    		Stack<Double> DegreeStack = new Stack<>();
    		double beta, CurrentBearing;
    		
    		CurrentBearing = alpha[1];//初始幅角
    		stack.push(index[0]);//前两个点入栈
    		stack.push(index[1]);
    		for (int i = 2; i < Number ; i++)
    		{
    			TempPoint = TempPoints1.get(index[i]);//取出判断栈顶要用的下一个点
    			//以栈顶点作为原点,计算下一个点的幅角
    			beta =  Math.atan2(TempPoint.y() -  TempPoints1.get(stack.peek()).y(), TempPoint.x() -  TempPoints1.get(stack.peek()).x());
    			if(beta < 0) beta = 360.0 + beta / Math.PI *180;//修改幅角大小为正数
    			
    			if(beta < CurrentBearing)//下一个点在右侧
    				{
    					stack.pop();//则栈顶不在凸包内,出栈
    					//以新的栈顶作为原点,计算下一个点的幅角
    					beta = Math.atan2(TempPoint.y() -  TempPoints1.get(stack.peek()).y(), TempPoint.x() -  TempPoints1.get(stack.peek()).x());
    					while(beta < DegreeStack.peek())//下一个点还在右侧
    					{
    						stack.pop();//则新栈顶仍不在凸包内,出栈
    						DegreeStack.pop();//修改角度栈栈顶
    						//以新的栈顶作为原点,计算下一个点的幅角
        					beta = Math.atan2(TempPoint.y() -  TempPoints1.get(stack.peek()).y(), TempPoint.x() -  TempPoints1.get(stack.peek()).x());
    					}
    					stack.push(index[i]);//把判断点入栈
        				CurrentBearing = beta;//修改新的幅角
    				}	
    			else if(beta > CurrentBearing) //下一个点在左侧
    				{
    					stack.push(index[i]);//把判断点入栈
    					DegreeStack.push(CurrentBearing);//现有幅角变为上一个幅角
    					CurrentBearing = beta;//修改新的幅角
    				
    				}
    			else //下一个点在直线上
    				{
    					stack.pop();//取出直线上的点
    					stack.push(index[i]);//把判断点入栈
    				}
    		}
    		while(stack.empty() == false)//取出栈中所有的点
    		{
    			FinalPoints.add(TempPoints2.get(stack.pop()));
    		}
    		return FinalPoints;
    	}
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值