现在介绍平面上顶点集合凸包的Bentley-Faust-Preparata (BFP)方法,与前面讲解的Graham Scan和Andrew's Monotone Chain方法不同之处是,预先并不需要将整个顶点集合一起按照X、Y轴数值排序。这种方法虽然是种近似方法,但时间复杂度降低,对于大数据量顶点的处理要求比较适合。
基本方法是,取代整个顶点集合的依次X、Y轴排序,而首先依X轴数值大小划分几个区间,在每个区间中找到Ymin,Ymax顶点,以所有区间的这些顶点为预选,采用Andrew's Monotone Chain算法中使用过的“左转”方式来判断凸包条件是否成立,最后构成上下两个凸包子集,将其合并得到结果。其近似的原因是,每个区间可能不只有Ymin,Ymax等顶点作为预选。倘若将区间数量增大,则算法的准确性上升。
参考代码:
// 输入: 没有排序的顶点数组P[],数组中的顶点个数n,准备将X轴分开成 k个区间
// 输出: 生成的凸包顶点集合H[]
// 返回的数值: 凸包H[]中的顶点个数
int CDEMAlgorithm::nearHull_2D( Point* P, int n, int k, Point* H )
{
int minmin=0, minmax=0; //准备记录X轴数值最小情况下Y轴数值最小和最大顶点索引
int maxmin=0, maxmax=0; //准备记录X轴数值最大情况下Y轴数值最小和最大顶点索引
double xmin = P[0].x, xmax = P[0].x; //初始化X轴最小和最大数值
Point* cP; // 被处理的顶点
int bot=0, top=(-1); // 指示栈底和栈顶
for (int i=1; i<n; i++) { //对所有顶点循环处理
cP = &P[i];
if (cP->x <= xmin) {
if (cP->x < xmin) { // 有更小的X轴数值出现
xmin = cP->x;
minmin = minmax = i; //记录下顶点索引
}
else { // 若相同的X轴最小数值出现
if (cP->y < P[minmin].y)
minmin = i; //记录下顶点索引,此时Y轴更小
else if (cP->y > P[minmax].y)
minmax = i; //记录下顶点索引,此时Y轴更大
}
}
if (cP->x >= xmax) { // 有更大或相同的X轴数值出现
if (cP->x > xmax) { // 有比xmax还大的顶点出现
xmax = cP->x;
maxmin = maxmax = i; //记录顶点索引
}
else { //若相同的X轴最大数值出现
if (cP->y < P[maxmin].y)
maxmin = i; //记录下顶点索引,此时Y轴更小
else if (cP->y > P[maxmax].y)
maxmax = i; //记录下顶点索引,此时Y轴更大
}
}
}
if (xmin == xmax) { // 一种极端情况出现:X轴最小与最大数值相同,即所有数值等同
H[++top] = P[minmin]; //记录下此顶点
if (minmax != minmin) // 如果Y轴的数值不同
H[++top] = P[minmax]; //记录下此顶点
return top+1; // 返回顶点个数,这种特殊情况下是1或2个
}
Bin* B = new Bin[k+2]; // 初始化区间数据结构
B[0].min = minmin; B[0].max = minmax; // 设置第一个区间
B[k+1].min = maxmin; B[k+1].max = maxmax; // 设置最后一个区间
for (int b=1; b<=k; b++) {
B[b].min = B[b].max = -99; // 初始赋值
}
for (int b, i=0; i<n; i++) { //循环处理每个顶点
cP = &P[i];
if (cP->x == xmin || cP->x == xmax) // 如果是整个集合中的X轴最小或最大数值
continue; //继续下次循环
if (isLeft( P[minmin], P[maxmin], *cP) < 0) { // 当前顶点处于低线之下
b = (int)( k * (cP->x - xmin) / (xmax - xmin) ) + 1;//得Bin结构的序号
if (B[b].min == -99) // 这个Bin结构中还没有赋Y轴上的最低顶点
B[b].min = i; //记录索引
else if (cP->y < P[B[b].min].y) //若比已经记录的最低数值还低
B[b].min = i; // 新的最低顶点
continue;
}
if (isLeft( P[minmax], P[maxmax], *cP) > 0) { //当前顶点处于高线之上
b = (int)( k * (cP->x - xmin) / (xmax - xmin) ) + 1; //得Bin结构的序号
if (B[b].max == -99) // 这个Bin结构中还没有赋Y轴上的最高顶点
B[b].max = i; //记录索引
else if (cP->y > P[B[b].max].y) //若比已经记录的最高数值还高
B[b].max = i; //新的最高顶点
continue;
}
}
//和前面讲解的Andrew's Monotone Chain算法中一样,使用 “左转”方式来判断凸包条件是否成立,分别构成上下两个子集
for (int i=0; i <= k+1; ++i)//对每个区间循环处理
{
if (B[i].min == -99) // 如果区间中没有最低顶点记录的话,继续下次循环
continue;
cP = &P[ B[i].min ]; // 取出当前区间中的最低顶点
while (top > 0) // 当栈中至少有2个元素的时候,top > 0
{
// 看是否满足“左转”条件
if (isLeft( H[top-1], H[top], *cP) > 0)
break; // 此顶点是满足条件的新凸包顶点
else
top--; // 由于顶点破坏凸包条件,需要弹出栈顶元素,直到满足
}
H[++top] = *cP; // 将这个顶点压入栈
}
//下面,计算上半部分的凸包顶点集合
if (maxmax != maxmin) //如果X轴数值最大情况下Y轴有不同顶点存在
H[++top] = P[maxmax]; //将X轴数值与Y轴数值最大的顶点压入栈
bot = top; //记住准备增加元素到栈前已经存在的元素个数
for (int i=k; i >= 0; --i) //对每个区间循环处理
{
if (B[i].max == -99) //如果区间中没有最高顶点记录的话,继续下次循环
continue;
cP = &P[ B[i].max ]; //取出当前区间中的最高顶点
while (top > bot) // top还是比开始记住的bot大,表明栈中至少有2个元素
{
// 看是否满足“左转”条件
if (isLeft( H[top-1], H[top], *cP) > 0)
break; //此顶点是满足条件的新凸包顶点
else
top--; //由于顶点破坏凸包条件,需要弹出栈顶元素,直到满足
}
H[++top] = *cP; //将这个顶点压入栈
}
if (minmax != minmin) //如果X轴数值最小情况下Y轴有不同顶点存在
H[++top] = P[minmin]; //把这最后一个顶点压入栈
delete B; // 释放
return top+1; //返回输出的凸包数组中的顶点个数
}