凸包算法不難理解,寫代碼的時候主要是幾何上的判定條件很容易寫錯。
算法的總體思想是:
1.給所有的點排序,找出極點(縱座標最小的點,如果縱座標一樣,取橫座標最小的點)
2.除了極點之外,所有其他的點排序,排序的方式是與極點之間的夾角從小到大(如果夾角一樣大,取距離極點近的排在前面)
3.把極點和第二個點放進棧,判斷第三個點是否在這兩個點的左邊,如果在左邊,進棧,否則,讓第二個點出棧,一直進行到最後一個點
4.把所有與極點、最後一個點共線(三點一線,並且夾在兩點中間)的點也放進棧裏
關於棧的用法:
我原本用的是stack,但是這個容器不好取倒數第二個數,就換成了vector,用一個top變量來模擬棧頂。
在類中使用sort函數的話,自定義的比較函數不能放在類裏面,否則會報錯reference to non-static member function must be called: sort
vector<int> polar_point;
int polar_x, polar_y;
bool isPolar(vector<int> &a, vector<int> &b){
if(a[1]==b[1]){//如果縱座標相等,判斷橫座標
return a[0]<b[0];
}
return a[1]<b[1];//判斷縱座標
}
double dist(vector<int> &a,vector<int> &b){
return pow(double(pow((a[0]-b[0]),2)+pow((a[1]-b[1]),2)),0.5);
}
bool smallerAngle(vector<int> &a, vector<int> &b){
if(abs(atan2(a[1]-polar_y,a[0]-polar_x)-atan2(b[1]-polar_y,b[0]-polar_x))<1e-8){//這邊注意不能比較浮點數是否相等,只能取一個範圍
return dist(a,polar_point)<dist(b,polar_point);
}
return atan2(a[1]-polar_y,a[0]-polar_x)<atan2(b[1]-polar_y,b[0]-polar_x);
}
bool isLeft(vector<int> cur, vector<int> polar, vector<int> top){
int cross = (cur[0]-polar[0])*(top[1]-polar[1])-(top[0]-polar[0])*(cur[1]-polar[1]);//x1y2-x2y1 計算叉積
if(cross>0)return false;
else if(cross<0)return true;
else{//叉積等於0的時候,有可能是反向的,要判斷一下
bool codirect = ((cur[0]-polar[0])*(top[0]-polar[0]))>0||((cur[1]-polar[1])*(top[1]-polar[1]))>0;//不能只用一個座標x來判斷,x有可能是0
return codirect;
}
}
bool isLine(vector<int> &a,vector<int> &b, vector<int> &c){
bool coline = ((c[1]-a[1])*(a[0]-b[0]))==((a[1]-b[1])*(c[0]-a[0]));
bool inbetween = (b[0]-a[0])*(c[0]-a[0])<0||(b[1]-a[1])*(c[1]-a[1])<0;//不能只用一個座標x來判斷,x有可能是0
return coline&&inbetween;//(y3-y1)*(x1-x2)==(y1-y2)*(x3-x1) a在bc中間
}
class Solution {
public:
vector<vector<int>> outerTrees(vector<vector<int>>& points) {
int n = points.size();//點的個數
if(n==0||n==1||n==2||n==3){return points;}//簡單情況直接處理
sort(points.begin(),points.end(),isPolar);//排序找極點
polar_point = points[0];//找到了極點
polar_x = polar_point[0];polar_y = polar_point[1];
sort(points.begin()+1,points.end(),smallerAngle);//基於極點排序
vector<vector<int>> res;
res.push_back(polar_point);
int i = 1;
res.push_back(points[i++]);
int top = 1;
while(i<n){//遍歷所有的點
if(isLeft(points[i],res[top-1],res[top])){//如果掃描到的點構成凸包
res.push_back(points[i++]);
top++;
}
else{//如果掃描到的點不構成凸包
res.pop_back();
top--;
}
}
i = 1;
while(i<n-1){//可能會漏掉的一部分點,介於極點與最後一個點之間的點,也是需要的
if(isLine(points[i],points[0],points[n-1])&&find(res.begin(),res.end(),points[i])==res.end()){//要判斷是否已經在答案裏了
res.push_back(points[i]);
}
i++;
}
return res;
}
};