0)
思路很好理解,但是写的过程中很容易写错!
①:sort排序时,其他点相对最左下角的点,先按极角由小到大排序,极角相同,按距离由近到远排序。(会留下最后进栈的,也就是留下距离远的,是对的)
②:Graham()里的while条件语句中,points[i]是和已经进栈的sstack[top]、sstack[top-1]比较,而不是points[top]、points[top-1]!
③:Graham()里的while条件语句while ((1<top) && (mulity(points[i], sstack[top], sstack[top - 1])>0)); ,之前在后面多加一个 ; 而出现各种奇怪答案 ,不要小看这一个分号,编译器不报错,而且会先认为是一个执行语句并执行,while则不判断就认为是TRUE,执行一遍while里的内容,然后再正常判断while里的条件语句是真是假!!
关于细节:
①:如果在Graham()里的while条件语句中出现三点(points[i]、sstack[top]、sstack[top-1])一线的情况时,若写mulity()>0,则top++,points[i]入栈,即三个点都入栈;如果写while()中写mulity()>=0,则三点一线时,top位置的点出栈,即第二个点出栈,最终只保留该直线第一个点和最后一个点。这两种情况最后得出的凸包的周长是相同的,但是凸包上的点是不同的,后者有舍弃,前者没有,所以注意这里可以出变型的题。
②:在sort的cmp自定义排序函数中,如果极角相同,应该距离近的在前面是有必要的。考虑当向量方向相同,距离短的在前(假设是B点),长的(假设是C点)在后,这样在Graham里的while循环判断中,如果恰好出现这样的极端情况:最左下角的点(假设是A)位于栈里的top-1的位置,B点位于top,C点位于i,则top--,将点A出栈,将点B进栈,然后计算距离时,这一段是计算点A到点C,正解。如果是距离远的在前,那么应当是点B位于i,点C位于top,那么while判断里因为极角相同则top--,点C出栈,点B进栈,计算这一段距离时,则是A到B,错的。围成的凸包也不对。
③:关于叉乘、判断方向以及如何进出“栈”,等等,都在代码中有详细注释,就不再一一赘述。
1)
#include <iostream> #include <stdio.h> #include <string.h> #include <algorithm> #include <math.h> using namespace std; const int maxn = 110; struct Point{ int x; int y; }points[maxn], sstack[maxn]; int top; int n; int mulity(Point p1, Point p2, Point p0){//注意,这里直接返回计算结果就行,不要再判断什么情况下返回1什么情况下返回0,因为这是重用度比较高的函数,是位于“底层”的,直接返回结果到上层中判断远比自己加一个判断要好得多! return (p1.x - p0.x)*(p2.y - p0.y) - (p2.x - p0.x)*(p1.y - p0.y);//向量p0p1 到 向量p0p2,叉乘大于0,意味着,右手定则右手四指指向p0p1的方向握向p0p2的方向时,大拇指朝外 } double dis(Point a, Point b){ return sqrt((a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y)); } bool cmp(Point p1, Point p2){//p0p1 到 p0p2 //向量方向相同,距离短的在前(假设是B点),长的(假设是C点)在后,这样在Graham里的while循环判断中,如果恰好出现这样的极端情况:最左下角的点(假设是A)位于栈里的top-1的位置,B点位于top,C点位于i,则top--,将点A出栈,将点B进栈,然后计算距离时,这一段是计算点A到点C,正解。如果是距离远的在前,那么应当是点B位于i,点C位于top,那么while判断里因为极角相同则top--,点C出栈,点B进栈,计算这一段距离时,则是A到B,错的。围成的凸包也不对。 int k = mulity(p1, p2, points[0]); if (k>0){ return 1; } else if (k == 0 && dis(p1, points[0]) <= dis(p2, points[0])){ return 1; } return 0; } void Graham(){ top = -1; for (int i = 0; i<n; i++){ //遇到i,比较i和当前栈顶元素top,谁在外围谁留下,即mulity(p1,p2,p0),右手由p0p1 握向 p0p2,拇指朝里,则叉乘<=0,所以p2靠外所以压栈,而p1在内所以出栈。 while ((1<top) && (mulity(points[i], sstack[top], sstack[top - 1])>0)){//mulity()>=0也是可以的 //while的判断语句,调试了一上午!一个是因为sstack之前用points代替,不和栈里的数做比较明显是错的,但这个地方确实很容易写错,高危!!!;另一个是因为多打了一个分号,则按while判断句按陈述语句先执行,然后因为检测到while里的判断句被执行了,所以while()内的语句一定先执行一次然后才进入下一次while里的条件语句进行判断要不要再进while循环!! top--;//若将i与top-1两点相连,则当前top点在内侧。所以当前top点出栈。退回到上一个top点与top-1,继续比较,直到当i入栈时,已入栈的点不会被包裹在内侧。 } //i点可入栈,并且不会破坏当前栈中所有点可构成凸包(即不会出现,连接栈中任意两点使得栈中任意一点会在连线内侧的情况) sstack[++top] = points[i]; } } int main() { while (~scanf("%d", &n) && n != 0){ //输入数据,并且找到左下角的点 scanf("%d%d", &points[0].x, &points[0].y); int ld_id = 0;//left down id for (int i = 1; i<n; i++){ scanf("%d%d", &points[i].x, &points[i].y); if (points[ld_id].y>points[i].y){ ld_id = i; } else if (points[ld_id].y == points[i].y&&points[ld_id].x>points[i].x){ ld_id = i; } } if (n == 1){ printf("%.2f\n", 0.00); continue; } else if (n == 2){ printf("%.2f\n", dis(points[0], points[1])); continue; } //把左下角的点放到数组第一个 Point temp = points[0]; points[0] = points[ld_id]; points[ld_id] = temp; //其他点按照与p0的极角由小到大排序,极角相同按距离由近到远排序,这样在Graham里的循环判断找凸包时,会将先进栈的最近的点踢出去,将距离最远的点留下,这样做所求的这一段距离才是对的,如果最后留下最近的点则形成的凸包漏掉了这个最远的点,所求距离自然也是过短的 sort(points + 1, points + (n), cmp); //for(int i=0;i<n;i++) //cout<<points[i].x<<" "<<points[i].y<<endl; Graham(); double res = 0;//凸包周长 //机智的0到top的相加.. for (int i = 0; i <= top; i++){ res += dis(sstack[i], sstack[(i + 1) % (top + 1)]); } printf("%.2f\n", res); } }2)
Description
Input
Output
Sample Input
9 12 7 24 9 30 5 41 9 80 7 50 87 22 9 45 1 50 7 0
Sample Output
243.06