3

最初に Hoey-Shamos アルゴリズムを実装しましたが、将来の保守性には複雑すぎて (これについては何も言えません)、正しく報告されていなかったので、最適化されたブルート フォース アルゴリズムを使用します。

私の質問は、このコードを使用できるように最適化するにはどうすればよいですか?

現状では、私のコードにはネストされた for ループが含まれており、同じリストを 2 回繰り返しています。

編集: 行を HashSet に変換し、2 つの foreach ループを使用しました... 10,000 のスキャンを約 45 秒短縮しました。まだ十分ではありません。

foreach (Line2D g in lines)
{                   
foreach (Line2D h in lines)
{
    if (g.intersectsLine(h))
    {
        return false;
    }
}                  

 } // end 'lines' for each loop

「intersectsLine()」メソッドが強制的に false を返すようにしても (テスト目的で)、10,000 レコードをスキャンするのに 8 秒かかります (700,000 レコードあります)。これは長すぎるため、このコードを最適化する必要があります。

他のすべての行と比較した後、リストから行を削除しようとしましたが、精度の問題があり (理由はわかりません)、速度の増加はほとんど目立ちません。

これが私の intersectsLine メソッドです。ここで別のアプローチを見つけましたが、すべてのメソッド呼び出しなどで遅くなるようです。勾配を計算するのは、計算に時間がかかりすぎるようには思えません (間違っていたら訂正してください)。

public bool intersectsLine(Line2D comparedLine)
{

//tweakLine(comparedLine);
if (this.Equals(comparedLine) ||
    P2.Equals(comparedLine.P1) ||
    P1.Equals(comparedLine.P2))
{
    return false;
}

double firstLineSlopeX, firstLineSlopeY, secondLineSlopeX, secondLineSlopeY;

firstLineSlopeX = X2 - X1;
firstLineSlopeY = Y2 - Y1;

secondLineSlopeX = comparedLine.X2 - comparedLine.X1;
secondLineSlopeY = comparedLine.Y2 - comparedLine.Y1;

double s, t;
s = (-firstLineSlopeY * (X1 - comparedLine.X1) + firstLineSlopeX * (Y1 - comparedLine.Y1)) / (-secondLineSlopeX * firstLineSlopeY + firstLineSlopeX * secondLineSlopeY);
t = (secondLineSlopeX * (Y1 - comparedLine.Y1) - secondLineSlopeY * (X1 - comparedLine.X1)) / (-secondLineSlopeX * firstLineSlopeY + firstLineSlopeX * secondLineSlopeY);

if (s >= 0 && s <= 1 && t >= 0 && t <= 1)
{
    //console.WriteLine("s = {0}, t = {1}", s, t);
    //console.WriteLine("this: " + this);
    //console.WriteLine("other: " + comparedLine);
    return true;
}

return false; // No collision */

}

編集:大きなボトルネックは大きなポリゴンです! 100 を超えるラインを含む実行中のポリゴンを除外したところ、700,000 以上のポリゴンすべてが 5:10 で実行されました。許容範囲内です!このコードを実行する前に、ポリゴンを計算する価値があるかどうかを確認する方法はありますか? XMin、Xmax、YMin、および YMax プロパティにアクセスできますか?

ポリゴンをそれぞれ 1000 ライン未満に制限して、別のテストを実行しました。1時間強かかりました。

ポリゴンのすべての制限を削除しましたが、現在 36 時間実行されています...まだ結果はありません。

私が持っているいくつかのアイデア:

-行のハッシュセットを生成するときに、交差の候補を持つ別のハッシュセット/リストを用意します。交差する可能性がある場合にのみ、このリストに行を追加します。しかし、どうすれば可能性を排除/追加できますか? 他のラインと交差する可能性のあるラインが 3 つしかない場合、4000 ラインではなく 3 ラインに対して 4000 ラインを比較します。これだけでも大きな違いが生じる可能性があります。

- 最初と最後のポイントを除いて、同じポイントが 2 回発生する場合は、ネストされた for ループの実行を省略します。

編集:


ポリゴンに関する情報: 合計 700,000

1,000 以上のポイントを持つ 4,000 以上のポリゴンがあります

70,000 ポイント以上のポリゴンが 2 つある


少し工夫すれば、これを 15 分程度に短縮することは可能だと思います。

4

3 に答える 3

6

現在のブルート フォース アルゴリズムは O(n^2) です。他の 700,000 のポリゴンは言うまでもなく、ほぼ 100 億回の操作の一部である 2 つの 70,000 のライン ポリゴンについて。明らかに、単なるコードの最適化では十分ではないため、過度に複雑にすることなく O(n^2) を下げることができる何らかのアルゴリズム最適化が必要です。

O(n^2) は、ブルート フォース アルゴリズムのネストされたループに由来し、それぞれが によって制限されn、O(n*n) になります。これを改善する最も簡単な方法は、内側のループを減らす方法を見つけて、 によって束縛されたり依存したりしないようにすることですn。したがって、検索する必要があるのは、完全なリストの一部のみをスキャンする必要があるように、行の内側のリストを順序付けまたは再順序付けして外側の行をチェックする方法です。

私が取っているアプローチは、2 つの線分が交差する場合、それらの X 値の範囲が互いに重なる必要があるという事実を利用しています。これは交差するという意味ではありませんが、X 範囲がオーバーラップしない場合、交差することはできないため、相互にチェックする必要はありません。(これは Y 値の範囲にも当てはまりますが、一度に 1 つのディメンションしか活用できません)。

このアプローチの利点は、これらの X 範囲を使用して線の端点を並べ替え、線の交差をチェックする開始点と停止点として使用できることです。

具体的にendpointEntryは、ラインの 2 点の X 値の上限または下限を表すカスタム クラス ( ) を定義します。これらのエンドポイントはすべて同じリスト構造に入れられ、X 値に基づいて並べ替えられます。

次に、ブルート フォース アルゴリズムと同様に、リスト全体をスキャンする外側のループを実装します。ただし、内部ループはかなり異なります。ラインのリスト全体を再スキャンして交差をチェックする代わりに、ソートされたエンドポイント リストを外側のループのラインの X 値の高いエンドポイントからスキャンを開始し、同じラインの X 値の低いエンドポイントの下を通過したときにスキャンを終了します。このようにして、X 値の範囲が外側のループのラインと重なるラインのみをチェックします。

OK、これが私のアプローチを示す ac# クラスです。

class CheckPolygon2
{
    // internal supporting classes
    class endpointEntry
    {
        public double XValue;
        public bool isHi;
        public Line2D line;
        public double hi;
        public double lo;
        public endpointEntry fLink;
        public endpointEntry bLink;
    }
    class endpointSorter : IComparer<endpointEntry>
    {
        public int Compare(endpointEntry c1, endpointEntry c2)
        {
            // sort values on XValue, descending
            if (c1.XValue > c2.XValue) { return -1; }
            else if (c1.XValue < c2.XValue) { return 1; }
            else // must be equal, make sure hi's sort before lo's
                if (c1.isHi && !c2.isHi) { return -1; }
                else if (!c1.isHi && c2.isHi) { return 1; }
                else { return 0; }
        }
    }

    public bool CheckForCrossing(List<Line2D> lines)
    {
        List<endpointEntry> pts = new List<endpointEntry>(2 * lines.Count);

        // Make endpoint objects from the lines so that we can sort all of the
        // lines endpoints.
        foreach (Line2D lin in lines)
        {
            // make the endpoint objects for this line
            endpointEntry hi, lo;
            if (lin.P1.X < lin.P2.X)
            {
                hi = new endpointEntry() { XValue = lin.P2.X, isHi = true, line = lin, hi = lin.P2.X, lo = lin.P1.X };
                lo = new endpointEntry() { XValue = lin.P1.X, isHi = false, line = lin, hi = lin.P1.X, lo = lin.P2.X };
            }
            else
            {
                hi = new endpointEntry() { XValue = lin.P1.X, isHi = true, line = lin, hi = lin.P1.X, lo = lin.P2.X };
                lo = new endpointEntry() { XValue = lin.P2.X, isHi = false, line = lin, hi = lin.P2.X, lo = lin.P1.X };
            }
            // add them to the sort-list
            pts.Add(hi);
            pts.Add(lo);
        }

        // sort the list
        pts.Sort(new endpointSorter());

        // sort the endpoint forward and backward links
        endpointEntry prev = null;
        foreach (endpointEntry pt in pts)
        {
            if (prev != null)
            {
                pt.bLink = prev;
                prev.fLink = pt;
            }
            prev = pt;
        }

        // NOW, we are ready to look for intersecting lines
        foreach (endpointEntry pt in pts)
        {
            // for every Hi endpoint ...
            if (pt.isHi)
            {
                // check every other line whose X-range is either wholly 
                // contained within our own, or that overlaps the high 
                // part of ours.  The other two cases of overlap (overlaps 
                // our low end, or wholly contains us) is covered by hi 
                // points above that scan down to check us.

                // scan down for each lo-endpoint below us checking each's 
                // line for intersection until we pass our own lo-X value
                for (endpointEntry pLo = pt.fLink; (pLo != null) && (pLo.XValue >= pt.lo); pLo = pLo.fLink)
                {
                    // is this a lo-endpoint?
                    if (!pLo.isHi)
                    {
                        // check its line for intersection
                        if (pt.line.intersectsLine(pLo.line))
                            return true;
                    }
                }
            }
        }

        return false;
    }
}

このアルゴリズムの実際の実行の複雑さはわかりませんが、ほとんどの非病的ポリゴンでは O(n*SQRT(n)) に近く、十分に高速であると思われます。


内部ループ ロジックの説明:

内側のループは、外側のループと同じソート順で endPoints リストをスキャンするだけです。ただし、リスト内の現在外側のループがある場所 (ある行の hiX ポイント) から外側のループの場所からスキャンを開始し、endPoint 値がその同じ行の対応する loX 値を下回るまでのみスキャンします。

ここで注意が必要なforeach(..in pts)のは、リストのサブリストを列挙する方法も、別の列挙の現在の位置に基づいて列挙を開始する方法もないため、列挙子 (外側のループの) ではこれを実行できないことです。代わりに、Forward および Backward Links (fLink および bLink) プロパティを使用して、リストの並べ替え順序を保持しながら、リストを列挙せずにインクリメンタルにスキャンできる二重リンク リスト構造を作成しました。

for (endpointEntry pLo = pt.fLink; (pLo != null) && (pLo.XValue >= pt.lo); pLo = pLo.fLink)

これを分解すると、古いスタイルのforループ指定子には、初期化、条件、インクリメント/デクリメントの 3 つの部分があります。初期化式は、リスト内の現在のポイントの前方リンクでendpointEntry pLo = pt.fLink;初期化されます。pLoつまり、降順でソートされた、リスト内の次のポイントです。

次に、内側のループの本体が実行されます。次に、インクリメント/デクリメントpLo = pLo.fLinkが適用されます。これは、内側のループの現在のポイント ( pLo) を、フォワード リンク ( ) を使用して次の下位のポイントに設定するだけでpLo.fLink、ループを進めます。

最後に、新しいポイントが null でない限り (つまり、リストの最後にいることを意味します)、新しいポイントがまだ以上で(pLo != null) && (pLo.XValue >= pt.lo)ある限りループするループ条件をテストした後、ループします。XValue外側のループの現在のポイントの低い X 値。この 2 番目の条件により、内側のループが、外側のループの終点の線と重なっている線のみを確認することが保証されます。


より明確になったのは、代わりに endPoints List を配列として扱うことで、この fLink-bLink の不器用さ全体を回避できた可能性があるということです。

  1. リストをエンドポイントで埋めます
  2. リストを XValue で並べ替える
  3. 列挙子iの代わりにインデックス反復子 ( ) を使用して、リストを降順でループします。foreach
  4. (A) 2 番目の反復子 ( ) を使用して、下に渡された時点でj開始して終了する、リストの内部ループ。ipt.Lo

それはもっと簡単だと思います。必要に応じて、そのような単純化されたバージョンを投稿できます。

于 2013-10-04T03:09:27.150 に答える