对简单多边形P而言,其小凸包pc是这样一个多边形,如果存在包含p的另一多边形p0,则pc是p0的子集,因此pc是包含p的最小的凸多边形。
- 硬币算法
首先介绍的算法称为硬币算法,具有逻辑简单,容易实现,并且效率较高的优点。其描述如下:
1.找一个必在凸包上的点,通常取纵坐标最小的点,记为P0。
2.连结 P0 与其他点,按照顺时针方向对这些线排序。(左转判定:这是经典的计算几何学问题,判断向量 p1=(x1,y1)到 p2=(x2,y2)是否做左转,只需要判断 x1*y2-x2*y1 的正负,如果结果为正,则从 p1 到 p2 做左转。)
2.连结 P0 与其他点,按照顺时针方向对这些线排序。(左转判定:这是经典的计算几何学问题,判断向量 p1=(x1,y1)到 p2=(x2,y2)是否做左转,只需要判断 x1*y2-x2*y1 的正负,如果结果为正,则从 p1 到 p2 做左转。)
现在就看看斯卡兰斯奇三硬币算法:
1.预处理:将各点排序。
2.在 P0、P1、P2 上分别放置一枚硬币;
把这三枚硬币分别命名为“后” 、 “中” 、 “前”。
3.反复
如果三枚硬币按“后-中-前”的顺序“做左转”
拿起“后”,放在“前”的前面;
将原先的“后”改名为“前” ;
将原先的“前”改名为“中” ;
将原先的“中”改名为“后” ;
否则
拿起“中”,放在“后”的后面;
移除刚才“中”所在的点;
将原先的“中”改名为“后” ;
将原先的“后”改名为“中” ;
直到“前”盖在 P0 上,且三枚硬币“做左转”
4.按照编号大小顺此连结剩下的点(编号最大的点连回 P0),
得到的多边形就是给定点集的凸包。
1.预处理:将各点排序。
2.在 P0、P1、P2 上分别放置一枚硬币;
把这三枚硬币分别命名为“后” 、 “中” 、 “前”。
3.反复
如果三枚硬币按“后-中-前”的顺序“做左转”
拿起“后”,放在“前”的前面;
将原先的“后”改名为“前” ;
将原先的“前”改名为“中” ;
将原先的“中”改名为“后” ;
否则
拿起“中”,放在“后”的后面;
移除刚才“中”所在的点;
将原先的“中”改名为“后” ;
将原先的“后”改名为“中” ;
直到“前”盖在 P0 上,且三枚硬币“做左转”
4.按照编号大小顺此连结剩下的点(编号最大的点连回 P0),
得到的多边形就是给定点集的凸包。
算法实现:
private void coin()//硬币算法实现
{
myar.Clear();//每次进行对myar的数据进行清零,myar为存储硬币算法的点数据的ArrayList
int p = 0;
for (int i = 0; i < thear.Count; i++)
{
if (((zuobiao)thear[p]).y > ((zuobiao)thear[i]).y)
//找出y最小的极限点,zuobiao为一个包含int x和int y字段的类
p = i;
}
myar.Add((zuobiao)thear[p]);
//将这个点最先存储进myar中,thear为从文件中读取的点数据的Arraylist
foreach (zuobiao zb in thear)
{
myar.Add(zb);//将其他数据加入myar中
}
canshu.p0x = ((zuobiao)thear[p]).x;//记录极限点的数据,canshu为记录参数的公共变量
canshu.p0y = ((zuobiao)thear[p]).y;
myar.RemoveAt(p + 1);//将极限点被重复存储的删去
CoinSort coinsort = new CoinSort();//对点按顺时针方向排序
myar.Sort(coinsort);
int hou, zhong, qian;//hou,zhong,qian代表硬币“后”,“中”,“前”
hou = 0;
zhong = 1;
qian = 2;
while (qian < myar.Count)//“前”达到最末则停止
{
if (isright(myar[hou], myar[zhong], myar[qian]))
//判断是否在“前”是否与“后”“中”形成右拐
{
hou++;//形成则全部向前移动
zhong++;
qian++;
}
else
{
myar.RemoveAt(zhong);
//否则删除“中”,因为队列中少了一项,因此原先“前”所表示的次序成为现在“前”的下一个,需要减回来
zhong--;//“后”“中”向后退
qian--;
hou--;
}
}
zuobiao temp = new zuobiao();
zuobiao temp2 = new zuobiao();
temp.x = canshu.p0x;
temp.y = canshu.p0y;
myar.Add(temp);
temp2.x = ((zuobiao)myar[0]).x;
temp2.y = ((zuobiao)myar[0]).y;
myar.Add(temp2);//将极限点和最终点写在myar最后使绘图时构成闭合图形
}
为了判断能否形成“右拐”,既“前”点是否在由“中”“后”两点形成的向量的右侧。
我提供的方法如下:
private bool isright(object a, object b, object c)//判断点是否在右侧
{
if (((((zuobiao)b).x - ((zuobiao)a).x) * (((zuobiao)c).y - ((zuobiao)a).y) - (((zuobiao)c).x - ((zuobiao)a).x) * (((zuobiao)b).y - ((zuobiao)a).y)) < 0)
return true;
else
return false;
}
2.串行算法
串行算法的描述是:找出集中点x坐标值的最小值和最大值的两个极限点pmin和pmax,显然此二值极点位于土包边界上。用直线连接pmin和pmax,则点集中的点部分位于上方部分位于下方,显然点集的最小凸包右这两部分组成,既从pmin到pmax的上下凸壳。将两者相加就是点集的最小凸包。
以计算上凸壳为例,下凸包算法相同。只考虑直线以上的点。上凸壳必然包含距离直线最远的居于直线上方的点,如果所有点都不在直线上方,则上凸壳由pmin和pmax直接确定。设位于直线上距离直线距离最大的点为pm,则类似的用直线连接pmin和pm以及pm和pmax,再次进行寻找线上距离最远点,若pmin到pm和pm到pmax都找不到,则凸壳由pmin,pm,pmax直接确定,否则,再重复以上步骤进行划分,直到所有直线上都不在有点,上凸壳就可以由这些点唯一确定。
上凸包算法如下:
private void serialup(int a, int b)//计算上凸包
{
double maxdistence = -1;//初始化点到直线的最小距离为-1
int maxnum = 0;//到直线距离最大点的编号
int upnumber = 0;//在直线上的点数
for (int i = 1; i < b - a; i++)
{
if (!(((zuobiao)myar[a + i]).y < ((zuobiao)myar[a]).y && ((zuobiao)myar[a + i]).y < ((zuobiao)myar[b]).y) && !isright(myar[a], myar[b], myar[a + i]))
//若点不在起终点下方并且在直线上方
{
upnumber++;//计数器+1
double k = 1.0 * (((zuobiao)myar[b]).y - ((zuobiao)myar[a]).y) / (((zuobiao)myar[b]).x - ((zuobiao)myar[a]).x);//计算斜率
double nowdis = System.Math.Abs((((zuobiao)myar[a + i]).x) * k - ((zuobiao)myar[a + i]).y + ((zuobiao)myar[a]).y - k * ((zuobiao)myar[a]).x) / Math.Sqrt(k * k + 1);
//计算点到直线距离距离
if (nowdis > maxdistence)//记录最大距离和最远点编号
{
maxdistence = nowdis;
maxnum = a + i;
}
}
}
if (upnumber > 1)//直线上不止一个点,则分割
{
serialup(a, maxnum);
serialup(maxnum, b);
}
else if (upnumber == 1)//若直线上只有一个点,直接存储该点和终点
{
newar.Add((zuobiao)myar[maxnum]);
newar.Add((zuobiao)myar[b]);
return;
}
else//若直线上没有点,则直接存储终点
{
newar.Add((zuobiao)myar[b]);
return;
}
}
private void serialdown(int a, int b)//下凸包计算类似上凸包
{
double maxdistence = -1;
int maxnum = 0;
int upnumber = 0;
for (int i = 1; i < b - a; i++)
{
if (!(((zuobiao)myar[a + i]).y > ((zuobiao)myar[a]).y && ((zuobiao)myar[a + i]).y > ((zuobiao)myar[b]).y) && isright(myar[a], myar[b], myar[a + i]))
{
double k = 1.0 * (((zuobiao)myar[b]).y - ((zuobiao)myar[a]).y) / (((zuobiao)myar[b]).x - ((zuobiao)myar[a]).x);
double nowdis = System.Math.Abs((((zuobiao)myar[a + i]).x) * k - ((zuobiao)myar[a + i]).y + ((zuobiao)myar[a]).y - k * ((zuobiao)myar[a]).x) / Math.Sqrt(k * k + 1);
upnumber++;
if (nowdis > maxdistence)
{
maxdistence = nowdis;
maxnum = a + i;
}
}
}
if (upnumber > 1)
{
serialdown(maxnum, b);//存点顺序同上凸包相反
serialdown(a, maxnum);
}
else if (upnumber == 1)
{
newar.Add((zuobiao)myar[b]);
newar.Add((zuobiao)myar[maxnum]);
return;
}
else
{
newar.Add((zuobiao)myar[b]);
return;
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/037ee3ac5ace269d04d33a5dd7158d5f.png)