一直聽大佬們說:凸包、凸包、凸包
一直不會。。。。。
然后。。。。
今天考試,考了一道計算幾何的簡單題。。。。
這,,,還是學一下吧。。
然后考試現場學習一下凸包算法。
先理解一下凸包是啥東西。
看看這張圖
解釋一下凸包是什么
如果你有一堆點(原諒我畫的很凌亂)
那么,找到一個點集
依次連接這些點
使他們形成一個凸多邊形
並且所有的點都包括在這個多邊形的內部或者邊上
這個多邊形就是一個凸包(我寫的肯定一點也不嚴謹)
不管怎么樣,就先這樣理解一下吧。。。。。。
凸包是啥應該不難理解,那么,給你一堆點,怎么求凸包?
這種東西。。。。。
先大概說一下把。。。
首先找到最靠近左下的那個點,這個點一定在凸包上(不難理解吧。。。畫個圖就知道了)
以這個點為極點,其他點按照極角排序
然后按照順序依次訪問所有點,判斷可行性
這樣子干說真是虛無縹緲的東西。。。。。。
畫圖來解釋
這是一片點。
找到最靠近左下的一個點
其他的點按照極角排序
然后把1丟到凸包的棧里面,准備開始掃描
檢查2號點是否在1的一側,(檢查一下是不是凸多邊形)
這里檢查到2號可行,先加入到棧中
檢查到3更加靠近外側(如果加入3號就會形成凹多邊形,顯然3在凸包中,而2不在)
然后把2號點彈出棧,判斷1號和3號節點的關系(同判斷2號)
依次這么判斷,最后所有凸包上的點都會在棧中
這樣子算法的步驟很顯然了。
繼續解決一些細節上的問題(貌似就一個把。。。。)
怎么計算一個節點是否在前一個點的一側。。。。
(我說的好不專業。。。我自己都不知道該怎么說一些名詞。。。就將就着理解一下吧。。。)
我們先拿幾個點出來
其中1,2,3是當前在凸包的棧中的點,4號節點是需要判斷的點
那么,我們需要從棧中拿最上方的兩個點(2和3節點)
把他們連接起來,再把2和4連接起來(怎么連接?我是不會說直接用向量的坐標表示就可以了)
計算一下兩個向量的叉積。。
哈,叉積。。。
解釋一下吧。。
假設2到3的向量是a(x1,y1)
2到4的向量是b(x2,y2)
那么,計算一下它們的叉積,也就是x1y2-x2y1
換種方法來表示就是。
|a|·|b|·sin<a,b>
(所以說叉積也可以用來求出三角形的面積~這個以后還會用到的)
如果,這兩個向量的叉積≥0 證明這兩個向量平行或者夾角是個銳角
也就證明了3號節點此時一定不再凸包上(因為連接2和4之后3在凸包內側了)
把3號節點彈出棧,繼續重復上面的步驟即可。
感覺我說的有點小復雜誒。。。。
這個東東多畫點圖就會理解的
如果還是不太清楚,可以看一看代碼。
struct Node
{
int x,y;
}p[MAX],S[MAX];//p儲存節點的位置,S是凸包的棧
inline bool cmp(Node a,Node b)//比較函數,對點的極角進行排序
{
double A=atan2((a.y-p[1].y),(a.x-p[1].x));
double B=atan2((b.y-p[1].y),(b.x-p[1].x));
if(A!=B)return A<B;
else return a.x<b.x; //這里注意一下,如果極角相同,優先放x坐標更小的點
}
long long Cross(Node a,Node b,Node c)//計算叉積
{
return 1LL*(b.x-a.x)*(c.y-a.y)-1LL*(b.y-a.y)*(c.x-a.x);
}
void Get()//求出凸包
{
p[0]=(Node){INF,INF};int k;
for(int i=1;i<=n;++i)//找到最靠近左下的點
if(p[0].y>p[i].y||(p[0].y==p[i].y&&p[i].x<p[0].x))
{p[0]=p[i];k=i;}
swap(p[k],p[1]);
sort(&p[2],&p[n+1],cmp);//對於剩余點按照極角進行排序
S[0]=p[1],S[1]=p[2];top=1;//提前在棧中放入節點
for(int i=3;i<=n;)//枚舉其他節點
{
if(top&&Cross(S[top-1],p[i],S[top])>=0)
top--;//如果當前棧頂不是凸包上的節點則彈出
else S[++top]=p[i++];//加入凸包的棧中
}
//底下這個玩意用來輸出凸包上點的坐標
//for(int i=0;i<=top;++i)
// printf("(%d,%d)\n",S[i].x,S[i].y);
}
接下來找一道簡單點的例題
HDU 1392
這道題目就是求出凸包然后計算周長,很簡單的題目,去試試吧。。