【筆記篇】最良心的計算幾何學習筆記(一)

border="0" width="330" height="86" src="//music.163.com/outchain/player?type=2&id=579954&auto=1&height=66">

世界以痛吻我, 我卻報之以歌。

開新坑…
雖然不知道這坑要填多久…
文章同步上傳到github…
有想看的可以去看看→_→

*溫馨提示:
1. 看本文之前請務必學習或回顧數學-必修2解析幾何數學-必修4平面向量有關內容…
2. 本文中的代碼基本都是口胡的, 不過還是用了IDE, 只保證能過編譯, 不保證正確OvO…

一 浮點數相關…

衆所周知, 浮點數的運算是有誤差的…所以有時候會出現一些很蛋疼的事情.. 比如兩個該相等的東西會得出不相等的結果… 爲了避免這種事情的發生, 我們要允許一些誤差… 我們定義

const double eps=1e-9;

當然這裏如果要求精度的話也可以開long double… 而且這個1e-9也是不一定的…
有些題目開1e-7 1e-8 1e-10都是不一定的… (詛咒所有卡精度的出題人吃方便麪只有調料包)
這個eps有什麼用呢…
這樣我們就可以自定義一下實數的比較…
然而double作爲基本變量類型是不能重載==運算符的, 這樣就要寫個函數, 而且像作者這麼zz的人很可能會忘記調用OvO

inline int dcmp(const double &a){
    if(fabs(a)<eps) return 0;   //絕對值不超過eps即視爲0(卡精度的來源)
    return a<0?-1:1;            //a<0 返回-1 a>0 返回1
}

這樣我們關於double的東西就基本寫完了..

二 平面向量相關

數學-必修4了沒? 沒有快去看…
根據平面向量基本定理, 每個向量a⃗  都可以寫成xe1+ye2 的形式…
這裏的e1,e2 可以是任意的, 但是爲了方便我們通常取正交基底(就是e1e2 啦~)
每個向量都可以由數對(x,y) 唯一確定…
這樣我們就可以這麼定義一個向量:

struct vec{
    double x,y;
};

很顯然, 這個也可以表示一個點.
所以我們可以加一句這個, 當然也可以不加..

typedef vec point;

在此基礎上, 我們就來定義各類運算.
1. 爲了方便, 我們可以在vec結構體里加一個構造函數…

c++
struct vec{
double x,y;
vec(int p,int q):x(p),y(q){}
vec(){x=y=0.0;} //默認構造函數還是要有的..
};

  1. 向量的模?
    表示爲|v⃗ | , 就是向量的長度啦~

    inline double len(const vec &a){
    return sqrt(a.x*a.x+a.y*a.y);
    }
  2. 比較基礎的, 向量的四則運算.
    這個由定義顯然

    vec operator +(const vec &a,const vec &b){
    return vec(a.x+b.x,a.y+b.y);
    }
    vec operator -(const vec &a,const vec &b){
    return vec(a.x-b.x,a.y-b.y);
    }
    vec operator *(const vec &a,const double &b){
    return vec(a.x*b,a.y*b);
    }
    vec operator *(const double &b,const vec &a){ //變量類型不同, 不能直接用交換律..
    return vec(a.x*b,a.y*b);
    }
    vec operator /(const vec &a,const double &b){
    return vec(a.x/b,a.y/b);
    }
  3. 然後就是點積(數量積 內積 標積)和叉積(向量積 外積 矢積)了..
    不過似乎並不考慮叉積的方向..(大概是因爲二維計算幾何的緣故吧..)
    (不過好像叉積的定義是“矢量叉積定義爲由(0,0)、p1、p2和p1+p2所組成的平行四邊形的帶符號的面積”?)

    double operator ^(const vec &a,const vec &b){ //點積就用^就行了 反正應該是用不到異或的..
    return a.x*b.x+a.y*b.y;
    }
    double operator *(const vec &a,const vec &b){
    return a.x*b.y-a.y*b.x;
    }

點積的話根據必修四, 可以判斷垂直.. 若p⃗ q⃗ =0 , 則p⃗ q⃗  .

叉積的一個重要的作用就是判斷向量的順逆時針關係..

  • p⃗ ×q⃗ >0 , 則p⃗ q⃗  的順時針方向;
  • p⃗ ×q⃗ <0 , 則p⃗ q⃗  的逆時針方向;
  • p⃗ ×q⃗ =0 , 則p⃗ q⃗  , 但可能同向也可能反向.
    有一些小小的注意事項(雖然不一定有用)..
  • 據我猜測計算幾何中沒什麼用的(但數學考試有用啊←_←)
    a⃗ b⃗ =|a||b|cosθ,a⃗ ×b⃗ =|a||b|sinθ
  • 點積是滿足交換律的(顯然), 而叉積是不滿足交換律的(也顯然), 不過叉積滿足反交換律,
    a⃗ ×b⃗ =b⃗ ×a⃗ 
  • 點積和叉積都滿足加法的分配律.
  • 顯然不能連續點積, 叉積不滿足結合律.
  • 其他的似乎更沒啥用而且我也看不懂 想研究的可以點上方傳送門去baidu看..

三 [跟多邊形沒關係的]各種常見的簡單的判斷

  1. 兩點之間距離公式 先放在這裏

    inline double dis(const vec &a,const vec &b){
        return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
    }

    其實寫多了之後可能會更喜歡寫len(a-b)…

  2. 兩個向量的順逆時針關係 見↑↑↑

  3. 折線段的拐向判斷
    其實跟上面沒有太大區別..
    兩條有公共頂點線段p0p1p1p2 (因爲是折線啊),
    很顯然我們可以表示兩個向量v1=p2p0,v2=p1p0 然後求v1×v2

    • v1×v2>0 , 則折線往右拐;
    • v1×v2<0 , 則折線往左拐;
    • v1×v2=0 , 則p0,p1,p2 三點共線.
  4. 判斷點q 是否在以p0,p1 爲對角頂點的矩形(不加說明則默認矩形邊與座標軸平行)內
    這個簡單啊 只要分別判斷q 的橫縱座標比p0,p1 的最小值大, 最大值小(當然是可以相等的)即可..

    inline bool ptInRect(const vec &q,const vec &p0,const vec &p1){
    return min(p0.x,p1.x)<=q.x&&q.x<=max(p0.y,p1.y)
        &&min(p0.y,p1.y)<=q.y&&q.y<=max(p0.y,p1.y);
    }
  5. 判斷點q 是否在線p0p1
    根據2. , 很顯然的可以判斷(qp0)×(p1p0)==0
    但這樣就夠了嗎? 並不是… 因爲q 可能在延長線or反向延長線上…
    所以我們要保證q 在以p0,p1 爲對角頂點的矩形內…(你以爲我爲什麼要寫3. ?)

    inline bool ptOnSeg(const vec &q,const vec &p0,const vec &p1){
    return (p1-p0)*q==0&&ptInRect(q,p0,p1);
    }

    這樣就可以咯..

  6. 判斷線段p0p1 和直線q0q1 是否相交
    如果線段與直線相交, 那麼線段跨立直線..
    線段跨立直線

    首先 如圖,我們可以得出v1=p0q0,v2=q1q0,v3=p1q0 .
    由圖可以看出, 我們要讓p0p1 放到直線q0q1 的兩側, 就要讓v1×v2v2×v3 同號(或者有一方爲0)

    所以有(v1×v2)(v2×v3)0 , 也就是((p0q0)×(q1q0))((q1q0)×(p1q0))0 .

     inline bool segCutLine(const vec &p0,const vec &p1,const vec &q0,const vec &q1){
        return ((p0-q0)*(q1-q0))*((q1-q0)*(p1-q0))>=0;
     }
  7. 判斷兩條線段p0p1,q0q1 是否相交

    這個我們可以分爲兩步

    • 快速排斥實驗
      兩個線段的端點爲對角頂點的兩個矩形如果不相交, 那麼這兩條線段顯然不相交.

    • 跨立實驗
      明確一點: 如果兩線段相交, 則兩線段必然相互跨立對方.
      我們就可以根據上面的跨立方式得出要判斷((p0q0)×(q1q0))((q1q0)×(p1q0))>0 (p0p1q0q1)((q0p0)×(p1q0))((p1q0)×(q1p0))>0 (q0q1p0p1)
      而當前面這一坨等於0的時候呢? 我們可以得知共線, 而由於已經通過了快速排斥實驗, 所以端點一定在另一條線段上. 所以我們只要讓((p0q0)×(q1q0))((q1q0)×(p1q0))0((q0p0)×(p1q0))((p1q0)×(q1p0))0 就可以了 (其實好囉嗦啊)

      inline bool segCutSeg(const vec &p0,const vec &p1,const vec &q0,const vec &q1){
      if(min(p0.x,p1.x)>max(q0.x,q1.x)||min(q0.x,q0.y)>max(p0.x,p1.x)
          ||min(p0.y,p1.y)>max(q0.y,q1.y)||min(q0.y,q1.y)>max(p0.y,p1.y)) return false; //快速排斥實驗
      return ((p0-q0)*(q1-q0))*((q1-q0)*(p1-q0))>=0&&((q0-p0)*(p1-p0))*((p1-p0)*(q1-p0))>=0; //跨立實驗
      }
  8. 判斷矩形是否在矩形中
    只需要簡單的比較邊界就行了.

    inline bool rectInRect(const vec &p0,const vec &p1,const vec &q0,const vec &q1){
    return min(p0.x,p1.x)<min(q0.x,q1.x)&&max(p0.x,p1.x)>max(q0.x,q1.x)
        &&min(p0.y,p1.y)<min(q0.y,q1.y)&&max(p0.y,p1.y)>max(q0.y,q1.y);
    }
  9. 判斷圓是否在矩形中
    圓在矩形中的充要條件是圓心在矩形內而且圓的半徑小於到矩形四邊距離的最小值.
    話說圓要怎麼表示? 一個點表示圓心 一個double表示半徑嘛= =

    inline bool cirInRect(const vec &o,const double &r,const vec &p0,const vec &p1){
    return ptInRect(o,p0,p1)&&min(
            min(abs(min(p0.x,p1.x)-o.x),abs(max(p0.x,p1.x)-o.x)),
        min(abs(min(p0.y,p1.y)-o.y),abs(max(p0.y,p1.y)-o.y)))<r; //這麼寫好像清楚一點?
    }
  10. 判斷點是否在圓中
    與圓心的距離小於半徑啊~

    inline bool ptInCir(const vec &p,const vec &o,const double &r){
    return dis(p,o)<r;
    }
  11. 判斷線段、折線、多邊形是否在圓中
    圓是個凸集, 所以我們依次判斷每個端點是否在圓內即可..
    代碼就不給了OvO

  12. 判斷圓O1 是否在圓O2
    首先嘛 肯定要r1<r2 … 其次呢 要有|O1O2|<=r2r1 (其實好像可以和上一條一起判.. 因爲負數顯然不會大於距離..) (等號成立時兩圓內切(突然不知道算不算在圓內了..先算是吧…(大不了到時候摳掉嘛)))

    inline bool cirInCir(const vec &o1,const double &r1,const vec &o2,const double &r2){
    return dis(o1,o2)<=r2-r1;
    }
  13. p 到直線q0q1 的距離,利用叉積求出平行四邊形的面積, 然後算出底邊長. 一比得到高即可.

     inline bool ptDisLine(const vec &p,const vec &q0,const vec &q1){
     return (p-q0)*(q1-q0)/len(q1-q0);
     }
  14. p 到線段q0q1 的距離,先判斷垂足是否在線段上.
    是則同上, 不是則找距離兩個端點中近的那個
    如何判斷呢?
    垂足不在線段上
    如圖所示, 如果垂足在直線外, 那麼夾角θ 一定是鈍角, 則cosθ<0
    那麼e1e2=|e1||e2|cosθ<0 ,
    而且這樣求出的θ 的頂點就是較近的點..
    兩邊一求就好了…

    double ptDisSeg(const point &p,const point &a,const point &b){
        if(dcmp((p-a)^(b-a))<0) return dist(p,a);
        if(dcmp((p-b)^(a-b))<0) return dist(p,b);
        return fabs((p-a)*(b-a))/dist(b,a);
    }
  15. 兩條相交直線(線段)的交點. (所以要先判斷是否相交…)
    好像兩種方法?
    一種是利用解析幾何推導.
    對於直線l:Ax+By+C=0 和直線上的兩點(x0,y0),(x1,y1)
    斜率k=AB=y0y1x0x1 ,
    那我們不妨設A=y0y1,B=x1x0 , 代入方程得到C=x0y1x1y0
    然後回到問題, 設兩條直線分別是

    l1:a1x+b1y+c1=0,l2:a1x+b1y+c2=0.

    這樣(x,y)就是聯立得到方程組的解.
    根據相關知識, 我們可以得到
    x=b2c1b1c2b1a2b2a1y=a1c2a2c1b1a2b2a1

    而這些量都可以用叉積的形式表示出來..
    所以就可以這麼寫:
     inline vec lineCutLineNode(const vec &p0,const vec &p1,const vec &q0,const vec &q1){
        double a1,b1,c1,a2,b2,c2,d;
        a1=p1.y-p0.y; b1=p0.x-p1.x; c1=p0*p1;
        a2=q1.y-q0.y; b2=q0.x-q1.x; c2=q0*q1;
        d=a1*b2-a2*b1;
        return vec((b2*c1-b1*c2)/d,(a1*c2-a2*c1)/d);
     }

    這種方法略微麻煩, 但是精度比較好.
    另一種方法是利用叉積的比值.

    ​ 利用參數方程的知識(數學 選修2-1 今天剛學噠XD), 我們可以用一個定點和一個方向向量確定一條直線.
    ​ 令兩條直線

    l1=P+tv⃗ ,l2=Q+tw⃗ 

    ​ 然後因爲是交點, 所以同時滿足兩條直線的方程, 令交點在兩條直線的參數分別爲λμ , 則有
    P+λv⃗ =Q+μw⃗ 

    ​ 兩邊分別叉乘w⃗ 
    (P+λv⃗ )×w⃗ =(Q+μw⃗ )×w

    ​ 去括號,
    P×w⃗ +λv⃗ ×w⃗ =Q×w⃗ +μw⃗ ×w⃗ 

    ​ 又w⃗  一定與自己共線, 所以w⃗ ×w⃗ =0 , 這樣就沒有\mu了, 移項, 合併同類項,
    (QP)×w⃗ =λv⃗ ×w⃗ 

    ​ 係數除到另一邊去, 得
    λ=(QP)×w⃗ v⃗ ×w⃗ 

​ 那麼這樣就都是已知量了, 代入進去即可.
​ 最後要求的交點就是P+λv⃗  啦~

 留一道例題:[poj1269](http://poj.org/problem?id=1269)

完整的代碼在github裏面喲~

發佈了72 篇原創文章 · 獲贊 17 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章