问题以下: leetcode 587ios
在一个二维的花园中,有一些用 (x,y) 坐标表示的树。因为安装费用十分昂贵,你的任务是先用最短的绳子围起全部的树。只有当全部的树都被绳子包围时,花园才能围好栅栏。你须要找到正好位于栅栏边界上的树的坐标。
算法
解法一:暴力解法app
咱们知道凸包的性质,凸包必定是【最外围】的那些点圈成,因此假设有n个点,那么最多能够构造出n(n−1)2条边,算法以下:
1. 选定一条边,遍历其余n-2个点,若是全部点都在该条边的一侧,则加入凸包集合。
2. 不断重复步骤1,直到全部边都被遍历过。this
如何判断一个点 p3 是在直线 p1p2 的左边仍是右边呢?(坐标:p1(x1,y1),p2(x2,y2),p3(x3,y3))咱们能够计算:
当上式结果为正时,p3在直线 p1p2 的左侧;当结果为负时,p3在直线 p1p2 的右边。spa
#include <iostream> #include <iostream> #include <algorithm> #include <unordered_map> #include <unordered_set> #include <set> #include <vector> #include <map> #include <set> #include <queue> #include <string> using namespace std; int calcuTriangle(int x1, int y1, int x2, int y2, int x3, int y3) { return x1 * y2 + x3 * y1 + x2 * y3 - x3 * y2 - x2 * y1 - x1 * y3; } //O(n^3) set<vector<int>> tubao_baoli(vector<vector<int>>& tree) { set<vector<int>> ret; if (tree.size() <= 1) return ret; for (int i = 0; i < tree.size(); i++) { for (int j = i + 1; j < tree.size(); j++) { int onesize = 0, othersize = 0; for (int k = 0; k < tree.size(); k++) { if (k == i || k == j) continue; int tmp = calcuTriangle(tree[i][0], tree[i][1], tree[j][0], tree[j][1], tree[k][0], tree[k][1]); if (tmp > 0) onesize ++; else if (tmp < 0) othersize ++; } if ( onesize == 0 || othersize == 0) //若是某一边没有点,当前两个点都是凸点 { ret.insert(vector<int> {tree[i][0], tree[i][1]}); ret.insert(vector<int> {tree[j][0], tree[j][1]}); } } } return ret; } int main() { int n = 5; vector<vector<int>> trees{{1,1},{2,0},{4,2},{3,3},{2,2},{2,4}}; set<vector<int>> ret = tubao_baoli(trees); for (auto it : ret) cout<<it[0]<<" "<<it[1]<<endl; return 0; }
解法二:Graham扫描法调试
第一步:找y最小的点(若是相等就找其中x最小的)做为基准点P0。code
第二步:把全部点的坐标平移一下,使 P0 做为原点,如上图。orm
第三步:计算各个点相对于 P0 的幅角 α (弧度),按从小到大的顺序对各个点排序。blog
当 α 相同时,分条件考虑:排序
若是当前弧度是最大弧度,说明在最左边边界,距离 P0 比较远的排在前面。
若是当前弧度是最小弧度,说明在最右边边界,距离 P0 比较近的排在前面。
若是在中间,那就均可以。
例如上图获得的结果为 P1,P2,P3,P4,P5,P6,P7,P8。咱们由几何知识能够知道,结果中第一个点 P1 和最后一 个点 P8 必定是凸包上的点。
第四步:咱们已经知道了凸包上的第一个点 P0 和第二个点 P1,咱们把它们放在栈里面。如今从步骤3求得的那个结果里,把 P1 后面的全部点拿出来作当前点,遍历进行第五步。
第五步:链接栈顶的第二个点和栈顶的那个点,获得直线 L 。
看当前点是在直线 L 的右边仍是左边。
若是在直线的右边就说明如今栈顶的点不是凸点,将栈顶的点弹出。当前点不变。
若是在直线上,或者在直线的左边,说明当前栈顶的点是知足条件的,把当前点入栈。遍历后一个点。
第六步:当把步骤3求得的结果里的全部点都遍历完了,还剩在栈里面的点就是凸点。导出便可。
代码以下:
#include <iostream> #include <iostream> #include <algorithm> #include <unordered_map> #include <unordered_set> #include <set> #include <vector> #include <map> #include <set> #include <queue> #include <stack> #include <string> #include <math.h> using namespace std; struct Point { int x; int y; Point() : x(0), y(0) {} Point(int a, int b) : x(a), y(b) {} }; class MYPoint{ public: MYPoint(int a, int b, double c):x(a), y(b),hudu(c){} int x; int y; double hudu; static double maxHuDu; bool operator<(const MYPoint& b) const { if (this->hudu == b.hudu) { if (this->hudu == maxHuDu) //说明是左侧的边界。从远到近 return (pow(this->x, 2) + pow(this->y, 2)) > (pow(b.x, 2) + pow(b.y, 2)); else //若是是右侧边界,从近到远。若是不是边界,均可以(也使用从近到远) return (pow(this->x, 2) + pow(this->y, 2)) < (pow(b.x, 2) + pow(b.y, 2)); } return this->hudu < b.hudu; } }; double MYPoint::maxHuDu = INT_MIN; class Solution { public: vector<Point> outerTrees(vector<Point>& points) { if (points.size() <= 3) return points; //首先找P0点(y最小的点) int minY = INT_MAX, minYpos; for (int i = 0; i < points.size(); i++) { if (points[i].y < minY) { minY = points[i].y; minYpos = i; } else if (points[i].y == minY && points[i].x < points[minYpos].x) { minY = points[i].y; minYpos = i; } } //cout<<"标准点:"<<points[minYpos].x<<"-"<<points[minYpos].y<<endl; vector<Point> ret; //返回的凸点 vector<MYPoint> mypoints; for (int i = 0; i < points.size(); i++) { if (i == minYpos) continue; double yy = points[i].y - points[minYpos].y, xx = points[i].x - points[minYpos].x; double hudu = atan2(yy, xx); MYPoint::maxHuDu = dou_max(MYPoint::maxHuDu, hudu); //要把最大弧度存下来。以后排序的一个关键点! mypoints.push_back(MYPoint(points[i].x - points[minYpos].x, points[i].y - points[minYpos].y, hudu)); //存的是相对位置。最后要补偿回来。 } sort(mypoints.begin(), mypoints.end()); // int jishu = 1; // for (auto it : mypoints) // cout<<"P"<<jishu++<<" "<<it.x<<" "<<it.y<<" "<<it.hudu<<endl; //第二个步骤 vector<MYPoint> stk; stk.push_back(MYPoint(0, 0, 0)); //压入P0(坐标[0,0]) stk.push_back(mypoints[0]); //压入P1 for (int i = 1; i < mypoints.size(); i++) { //若是在左边或者在线上 就入栈 if (calcuTriangle(stk[stk.size() - 2].x, stk[stk.size() - 2].y, stk.back().x, stk.back().y, mypoints[i].x, mypoints[i].y)) { stk.push_back(mypoints[i]); //cout<<"P"<<i + 1 << " "<<mypoints[i].x<<" "<<mypoints[i].y<<" 知足"<<endl<<endl; } else //不然,弹出栈顶 { stk.pop_back(); //cout<<"P"<<i + 1 << " "<<mypoints[i].x<<" "<<mypoints[i].y<<" 不知足"<<endl<<endl; i--; //当前点下一轮仍是做为判断点 } } cout<<endl; //最后,栈内剩下的都是凸点 for (auto it : stk) ret.push_back(Point(it.x + points[minYpos].x, it.y + points[minYpos].y)); //以前都是算的相对位置。最后返回要加上来。 return ret; } bool calcuTriangle(int x1, int y1, int x2, int y2, int x3, int y3) { //cout<<x1<<" "<<y1<<" "<<x2<<" "<<y2<<" "<<x3<<" "<<y3<<endl; return x1 * y2 + x3 * y1 + x2 * y3 - x3 * y2 - x2 * y1 - x1 * y3 >= 0; //判断是否在左边 } double dou_max(double a, double b) { return (a > b) ? a : b; } }; int main() { //vector<vector<int>> trees{{1,1},{2,0},{4,2},{3,3},{2,2},{2,4}}; //vector<vector<int>> trees{{3,0},{4,0},{5,0},{6,1},{7,2},{7,3},{7,4},{6,5},{5,5},{4,5},{3,5},{2,5},{1,4},{1,3},{1,2},{2,1},{4,2},{0,3}}; //vector<vector<int>> trees{{0,0},{0,1},{0,2},{1,2},{2,2},{3,2},{3,1},{3,0},{2,0}}; //vector<vector<int>> trees{{0,2},{0,4},{0,5},{0,9},{2,1},{2,2},{2,3},{2,5},{3,1},{3,2},{3,6},{3,9},{4,2},{4,5},{5,8},{5,9},{6,3},{7,9},{8,1},{8,2},{8,5},{8,7},{9,0},{9,1},{9,6}}; vector<vector<int>> trees{{1,0},{0,0},{2,0},{3,0}}; vector<Point> points; for (int i = 0; i < trees.size(); i++) points.push_back(Point(trees[i][0], trees[i][1])); vector<Point> ret = Solution().outerTrees(points); for (auto it : ret) cout<<it.x<<" "<<it.y<<endl; return 0; }
关于leetcode 587的问题:
上面的代码跑587在
[[0,0],[0,1],[0,2],[1,2],[2,2],[3,2],[3,1],[3,0],[2,0]] 会报错。
可是单独调试这个实例又是正确的答案。因此很困惑。