はじめに
前回すこしふれた2次元凸包アルゴリズムについて説明し、C++による実装例を示す。アルゴリズム
今回紹介するアルゴリズムは逐次添加法と呼ばれ、O(n\log{n})の計算量を持つ1。入力:A=\{P_1,P_2,\cdots,P_n\}(添字を固有番号と呼ぶことにする)
出力:凸包 C(A)
- Aに属する点を x 座標の小さい順にならべる。この点列を A^{\prime}=\{P_{(1)},P_{(2)},\cdots,P_{(n)}\} と書き、添字を整列番号と呼ぶことにする。x 座標が同じ値を持つ場合は固有番号の大きいものから並べる。整列番号から固有番号へ変換するテーブル T_{{\rm sorted}\rightarrow{\rm original}} を保持しておく。
- A^{\prime} の最初の3点から三角形を作り、これを C(\{P_{(1)},P_{(2)},P_{(3)}\}) とする。時計回りにたどるときの点列 V_{\rm clockwise} と反時計回りにたどるときの点列 V_{\rm counterclockwise} を保持する。たとえば、時計回りにまわったとき整列番号 a の次に整列番号 b の点がくるならV_{\rm clockwise}(a)=bとなるようにすればよい。
- 整列番号 (3) から (n) までの点を順に追加する。各点の追加ごとにアルゴリズム X(後述)を実行する。
- 出力値 C(\{P_{(1)},P_{(2)},\cdots,P_{(n)}\}) を得る。V_{\rm clockwise} あるいは V_{\rm counterclockwise} をたどれば、凸包を得ることができる。
ここで、n は点の総数、M は十分大きな量、\epsilon は十分小さな量である。添字 i として固有番号を採用することにより、たとえ x 座標値が同じであっても、仮想的に x 座標値の小さな順に点を添加していくことができる。たとえば、x座標値が同じ3点を考えてみる(下図)。括弧で囲んだ数字は整列番号、囲んでいない番号は固有番号である。x 座標が同じ値を持つ場合は固有番号の大きいものから並べたので、各点の摂動量(青線)と摂動後の点(赤丸)は図のようになる。整列番号の小さい点は摂動量も小さくなることが分る。
次にアルゴリズム X について述べる。
入力:C(\{P_{(1)},P_{(2)},\cdots,P_{(k)}\})、P_{(k+1)}、V_{\rm clockwise}、V_{\rm counterclockwise}、T_{{\rm sorted}\rightarrow{\rm original}}
出力:C(\{P_{(1)},P_{(2)},\cdots,P_{(k+1)}\})
- P_{(s)}\leftarrow P_{(k)}
- 3点 P_{(k+1)},P_{(s)},P_{(t)} を考える。ここで、t=V_{\rm counterclockwise}(s)である。
- 3点をこの順にたどったとき右に曲がるなら s\leftarrow t として2へ、左に曲がるなら4へ(下図参照)。
- 左に曲がった整列番号(u)を保持する。
- P_{(s)}\leftarrow P_{(k)}
- 3点 P_{(k+1)},P_{(s)},P_{(t)} を考える。ここで、t=V_{\rm clockwise}(s)である。
- 3点をこの順にたどったとき左に曲がるなら s\leftarrow t として6へ、右に曲がるな8へ。
- 右に曲がった整列番号(l)を保持する。
- V_{\rm counterclockwise}(l)=k+1、V_{\rm counterclockwise}(k+1)=u とする。
- V_{\rm clockwise}(u)=k+1、V_{\rm clockwise}(k+1)=l とする。
右に曲がる、左に曲がるという判定は先のページで取り上げた判定式 F を使えば良い。
- F(P_{(i)},P_{(j)},P_{(k)}) を考える。
- T_{{\rm sorted}\rightarrow{\rm original}} を使って、整列番号を固有番号に変換する。
\begin{eqnarray}
a&=&T_{{\rm sorted}\rightarrow{\rm original}}(i) \nonumber \\
b&=&T_{{\rm sorted}\rightarrow{\rm original}}(j) \nonumber \\
c&=&T_{{\rm sorted}\rightarrow{\rm original}}(k) \nonumber
\end{eqnarray}
- 固有番号のリスト L=(a,b,c) を大きい順に並べ替え、リスト L^{\prime}=(a^{\prime},b^{\prime},c^{\prime}) を作成する。並べ替えの際、奇置換なら p=-1、偶置換なら p=1 とする。
- F(P_{a^{\prime}},P_{b^{\prime}},P_{c^{\prime}}) の正負を判定する。その結果にpの値を乗じたものが F(P_{a},P_{b},P_{c}) の符号となる。正なら左に折れ、負なら右に折れる。
C++による実装
ソースはここ。main
関数は以下の通りである。
- 16行目から39行目:乱数による点の生成を行っている。
- 43行目:テンプレートクラス
ConvexHull2d
のオブジェクトを作成している。ここではテンプレート引数としてint
を指定したが、boost::multiprecision::cpp_intを指定することもできる。 - 44行目:アルゴリズムを実行している。
- 46行目以降:最初に生成した点と凸包を描画している。
- 57行目:凸包を構成する点を取り出している。
デモ
開発環境
- Mac OS X 10.8.4
- プロセッサ:3.06 GHz Intel Core 2 Duo
- メモリ:4GB
- Xcode4.6.2 with Apple LLVM 4.2(C++ Language Dialect → C++11, C++ Standard Library → libc++)
- boost-1.53.0(Apple LLVM 4.2でコンパイルしたもの。こちら)
- opencv-2.4.5(Apple LLVM 4.2でコンパイルしたもの。こちら)
参考文献
- 計算幾何プログラミング, 杉原厚吉, 岩波書店
0 件のコメント:
コメントを投稿