淺談凸包之Andrew 與 Graham
前言
腦補知識點:
1.向量的內積(數量積,點乘):
公式:a· b = |a| * |b| cos<a, b>=a.x* b.y + b.x * a.y
2.向量的外積(向量積,差乘):
公式:|c|= |a|*|b|*sin<a, b> = a.x * b.y - b.x * a.y
點在多邊形內判定
多邊形: 就是二維平面上被一系列首尾相接、閉合的折線段圍成的區域 在程式中一般用定點陣列表示 其中各個定點按照逆時針順序排序
問: 給你一個點 如何判斷它是在多邊形內 呢?
1.射線法 :從判斷點出發,任意引一條射線 如果和邊界相交奇數次 就在多邊形內 偶數次 在外面
2.轉角法: 基本思想 看多邊形相對這個點轉了多少度 具體說來就是我們把每個轉角加起來 如果為360 在內 否則在外(具體程式碼劉汝佳的書上有 也可自行寫一個)
凸包
凸包 : 把給定的點包圍在內部的、面積最小的凸多邊形。
兩種演算法:andrew演算法 和 graham
演算法總思想:
找一個凸包上的點,把這個點放到第一個點的位置P0。然後把P1~Pm 按照P0Pi的方向排序
Graham
來源: 百度
Graham掃描法
基本思想:通過設定一個關於候選點的堆疊s來解決凸包問題。
操作:輸入集合Q中的每一個點都被壓入棧一次,非CH(Q)(表示Q的凸包)中的頂點的點最終將被彈出堆疊,當演算法終止時,堆疊S中僅包含CH(Q)中的頂點,其順序為個各頂點在邊界上出現的逆時針方向排列的順序。
注:下列過程要求|Q|>=3,它呼叫函式TOP(S)返回處於堆疊S 頂部的點,並呼叫函式NEXT-TO –TOP(S)返回處於堆疊頂部下面的那個點。但不改變堆疊的結構。
GRAHAM-SCAN(Q)
1 設P0 是Q 中Y 座標最小的點,如果有多個這樣的點則取最左邊的點作為P0;
2 設<P1,P2,……,Pm>是Q 中剩餘的點,對其按逆時針方向相對P0 的極角進行排序,如果有數個點有相同的極角,則去掉其餘的點,只留下一個與P0 距離最遠的那個點;
3 PUSH(p0 , S)
4 PUSH(p1 , S)
5 PUSH(p3 , S)
6 for i ← 3 to m
7 do while 由點NEXT-TOP-TOP(S),TOP(S)和Pi 所形成的角形成一次非左轉
8 do POP(S)
9 PUSH(pi , S)
10return S
首先,找一個凸包上的點,把這個點放到第一個點的位置P0。然後把P1~Pm 按照P0Pi的方向排序,可以用向量積(叉積)判定。
做好了預處理後開始對堆疊中的點<p3,p4,...,pm>中的每一個點進行迭代,在第7到8行的while迴圈把發現不是凸包中的頂點的點從堆疊中移去。(原理:沿逆時針方向通過凸包時,在每個頂點處應該向左轉。因此,while迴圈每次發現在一個頂點處沒有向左轉時,就把該頂點從堆疊中彈出。)當演算法向點pi推進、在已經彈出所有非左轉的頂點後,就把pi壓入堆疊中。
圖片來源:http://kmplayer.iteye.com/blog/604405
#include<iostream>//凸包求點
#include<stack>
#include<algorithm>
#include<string.h>
#include<math.h>
#include<stdio.h>
#define INF 9999999999
#define eNs 1e-6
#define MAX 105
using namespace std;
struct node
{
int x,y;
};
node N[MAX],cHull[MAX],N0,stk[MAX];
int m,n;
int cnt;
int cross(node a,node b,node c)
{
return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
}
int dis(node a,node b)
{
return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}
bool cmN(node a,node b)
{
int t=cross(N0,a,b);
return t>0||(t==0 && dis(N0,a)<dis(N0,b));
}
void convexHull()
{
int i,j,k;
m=0;
cnt=0;
for(k=0,i=0;i<n;i++)
if(N[i].y<N[k].y||(N[i].y==N[k].y && N[i].x<N[k].x) )
k=i;
N0=N[k];
N[k]=N[0];
N[0]=N0;
sort(N+1,N+n,cmN);
stk[0]=N[0];
stk[1]=N[1];
int top=1;
for(i=2;i<n;i++)
{
while(top && cross(stk[top-1],stk[top],N[i])<=0)
{
top--;
}
stk[++top]=N[i];
}
m=top+1;
}
bool ccmp(node a,node b){
if(a.y==b.y)return a.x<b.x;
return a.y>b.y;
}
int cmp(node a,node b)
{
if(a.x != b.x)
return a.x <b.x;
else
return a.y < b.y;
}
int main()//就是一模板提
{
int t;
cin>>t;
while(t--)
{
int k;
int i,j;
cin>>n;
for(i=0;i<n;i++)
cin>>N[i].x>>N[i].y;
convexHull();
int xx=stk[0].x,yy=stk[0].y;
int tag=0;
for(i=1;i<m;i++)
if(stk[i].y>stk[tag].y || (stk[i].y==stk[tag].y && stk[i].x<stk[tag].x))
tag=i;
// printf("%d %d\n",k,m);
// for(i=tag;i>=0;i--)
// printf("%d %d\n",stk[i].x,stk[i].y);
// for(i=m-1;i>tag;i--)
// printf("%d %d\n",stk[i].x,stk[i].y);
sort(stk,stk+m,cmp);
for(int i=0;i<m;i++)
cout<<stk[i].x<<" "<<stk[i].y<<endl;
}
return 0;
}
Andrew演算法
我學的這個: 他是graham演算法的變種 比graham要快 具體實現: 1.把所有點按照x從小到大進行排序 (x同則y用y進行排序) 2.刪除重複序列後得到序列p1,p2.。。, 3.讓後把p1 p2放入凸包中 從p3開始。當新點在凸包“前進”方向的左邊時繼續,否則依此刪除最近加入凸包的點 直到新點在左邊 這個演算法你可以理解為逆時針畫圓 每次半個圓例題:
圈水池
時間限制:3000 ms | 記憶體限制:65535 KB 難度:4- 描述
- 有一個牧場,牧場上有很多個供水裝置,現在牧場的主人想要用籬笆把這些供水裝置圈起來,以防止不是自己的牲畜來喝水,各個水池都標有各自的座標,現在要你寫一個程式利用最短的籬笆將這些供水裝置圈起來!(籬笆足夠多,並且長度可變)
- 輸入
- 第一行輸入的是N,代表用N組測試資料(1<=N<=10)
第二行輸入的是m,代表本組測試資料共有m個供水裝置(3<=m<=100)
接下來m行代表的是各個供水裝置的橫縱座標 - 輸出
- 輸出各個籬笆經過各個供水裝置的座標點,並且按照x軸座標值從小到大輸出,如果x軸座標值相同,再安照y軸座標值從小到大輸出
- 樣例輸入
-
1 4 0 0 1 1 2 3 3 0
- 樣例輸出
-
0 0 2 3 3 0
- 來源
- 上傳者
- 張潔烽
#include<bits/stdc++.h>
using namespace std;
struct point
{
int x,y;
};
point operator +(point A,point B)//
{
point C;
C.x = A.x+B.x;
C.y = A.y+B.y;
return C;
}
point operator -(point A,point B)
{
point C;
C.x = A.x-B.x;
C.y = A.y-B.y;
return C;
}
int Cross(point A,point B)//叉積
{
return A.x*B.y-A.y*B.x;
}
int cmp(point A,point B)
{
return A.x==B.x ? A.y<B.y : A.x<B.x;
}
int ConvexHull(point *p,int n,point *ch)//Andrew
{
int m = 0;
for(int i = 0;i < n;i++)
{
while(m>1&&Cross(ch[m-1] - ch[m-2],p[i]-ch[m-2]) <= 0) m--; //如果發現更好的點把之前凸包內的點吐出
ch[m++] = p[i];
}
int k = m;
for(int i=n-2;i >= 0;i--)
{
while(m > k&&Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2]) <= 0) m--;
ch[m++] = p[i];
}
if(n>1)
m--;
//cout << m << endl;
return m;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n;
scanf("%d",&n);
point p[105],ch[105];
for(int i = 0;i < n;i++)
scanf("%d%d",&p[i].x,&p[i].y);
sort(p,p+n,cmp);
int m = ConvexHull(p,n,ch);
// printf("m: %d\n",m);
sort(ch,ch+m,cmp);
for(int i = 0;i < m;i++)
printf("%d %d\n",ch[i].x,ch[i].y);
}
}