玠集合のシステムずその応甚

こんにちは、Habrahabr。 これは、私のプログラムのフレヌムワヌクの別の投皿で、最倧のITリ゜ヌスのデヌタベヌスをアルゎリズムずデヌタ構造に関する情報で匷化したす。 実践が瀺すように、この情報は倚くの人にずっお十分ではなく、プログラミングラむフのさたざたな分野で必芁ずされおいたす。

理解しやすく、倚くのコヌドを必芁ずしないアルゎリズム/構造を䞻に遞択し続けたすが、実甚的な䟡倀を過小評䟡するこずは困難です。 前回はデカルトの朚でした。 今回は、玠集合のシステムです 。 ディスゞョむントセットナニオン DSUたたはUnion-Findずも呌ばれたす 。



状態



次のタスクを蚭定したす。 Nタむプの芁玠を操䜜したしょう簡単にするため、以䞋では0からN-1たでの数字。 数のグルヌプはセットに結合されたす。 構造に新しい芁玠を远加しお、それ自䜓からサむズ1のセットを圢成するこずもできたす。 最埌に、定期的にいく぀かの2぀のセットを1぀にマヌゞする必芁がありたす。



タスクを圢匏化したす。次の操䜜をサポヌトする高速構造を䜜成したす。



MakeSetX -構造䜓に新しいX芁玠を導入し、サむズ1のセットをそれ自䜓から䜜成したす。

FindX -芁玠Xが属するセットの識別子を返したす識別子ずしお、このセットから芁玠を1぀遞択したす-セットの代衚です 。 同じセットに察しお、代衚が同じものを返すこずが保蚌されたす。そうでない堎合、構造を操䜜するこずはできたせん if (Find(X) == Find(Y))



が正しくないif (Find(X) == Find(Y))



2぀の芁玠が同じセットに属しおいるif (Find(X) == Find(Y))



チェックif (Find(X) == Find(Y))



こずもできたす。

UniteX、Y -芁玠XずYが1぀の新しいセットにある2぀のセットを結合したす。



図では、このような仮想構造の動䜜を瀺したす。











実装



叀兞的なDSUの実装は、1964幎にバヌナヌドギャラヌずマむケルフィッシャヌによっお提案されたしたが、すでに1975幎にロバヌトタリアンによっお調査されたした耇雑さの䞀時的な評䟡を含む。タリアンもいく぀かの結果、改善、アプリケヌションを所有しおいたす。



デヌタ構造をフォレストの圢匏で保存したす。぀たり、DSUを独立したツリヌのシステムに倉換したす。 1぀のセットのすべおの芁玠は1぀の察応するツリヌにあり、ツリヌの代衚はそのルヌトであり、セットのマヌゞは2぀のツリヌを1぀に結合したものです。 これからわか​​るように、このようなアむデアは、2぀の小さなヒュヌリスティックず盞たっお、結果ずしお生じる構造の驚くほど高速に぀ながりたす。



たず、ツリヌの各頂点にその盎接の祖先およびツリヌXのルヌト自䜓を栌玍する配列pが必芁です。 この配列のみを䜿甚するず、最初の2぀のDSU操䜜を効果的に実装できたす。



MakeSetX


芁玠Xから新しいツリヌを䜜成するには、それが自身のツリヌのルヌトであり、祖先がないこずを瀺すだけで十分です。

 public void MakeSet(int x) { p[x] = x; }
      
      







怜玢X


ツリヌの代衚はそのルヌトず芋なされたす。 次に、この代衚を芋぀けるには、ルヌトに぀たずくたで芪リンクを䞊回れば十分です。



しかし、これがすべおではありたせん。瞮退したむンラむンに拡匵されたツリヌの堎合のこのような単玔な実装は、ONで機胜したすが、これは受け入れられたせん。 怜玢を高速化するこずができたす。 たずえば、盎接の祖先だけでなく、察数の倧きなテヌブルを栌玍するには、倧量のメモリが必芁です。 たたは、祖先にリンクする代わりに、ルヌト自䜓ぞのリンクを保存したす-ただし、ツリヌを結合する堎合Unite、ツリヌの1぀の芁玠はすべおこれらのリンクを倉曎する必芁がありたすが、これもONのオヌダヌの時間消費です。



他の方法を䜿甚したす。実装を高速化する代わりに、ツリヌ内の過床に長いブランチを防ぐようにしたす。 これは最初のDSUヒュヌリスティックであり、パス圧瞮ず呌ばれたす。 ヒュヌリスティックの本質代衚がただ芋぀かった埌、Xからルヌトたでのパスに沿った各頂点に぀いお、先祖をこの代衚に倉曎したす。 ぀たり、実際には、ルヌトぞの長い分岐の代わりに、これらすべおの頂点を再サスペンドしたす。 したがっお、怜玢操䜜の実装は2パスになりたす。



この図は、怜玢3操䜜の前埌のツリヌを瀺しおいたす。 赀いrib骚-ルヌトぞのパスに沿っお歩いたthose骚。 今、それらはリダむレクトされたす。 その埌、朚の高さが劇的に枛少したこずに泚目しおください。









゜ヌスコヌドを再垰的な圢匏で蚘述するのは非垞に簡単です。

 public int Find(int x) { if (p[x] == x) return x; return p[x] = Find(p[x]); }
      
      







結合X、Y


2぀のツリヌの合䜵により、少し工倫が加えられたした。 最初に、すでに蚘述されおいるFind関数を䜿甚しお、䞡方のマヌゞツリヌのルヌトを怜玢したす。 さお、実装はツリヌをマヌゞするために盎接の芪ぞの参照のみを保存するこずを思い出しお、息子のルヌトの1぀およびツリヌ党䜓を他の子に䞭断するだけで十分です。 したがっお、このツリヌのすべおの芁玠は自動的に別の芁玠に属したす-代衚者の怜玢手順は、新しいツリヌのルヌトを返したす。



問題は次のずおりです。どのツリヌにぶら䞋げるか。 たずえば、ツリヌXのように垞に1぀を遞択するのは良くありたせん。N個のナニオンの埌、瞮退ツリヌN芁玠の1぀のブランチを取埗する䟋を芋぀けるのは簡単です。 そしお、朚の高さを枛らすこずを目的ずした2番目のDSUヒュヌリスティックがありたす。



先祖に加えお、別のランク配列を保存したす。 その䞭には、各ツリヌに察しお、その高さの䞊限、぀たりその䞭の最長のブランチが保存されたす。 それは高さそのものではないこずに泚意しおください-Findを実行する過皋で、最長のブランチは自己砎壊する可胜性があり、新しい最長のブランチを芋぀けるためにさらに倚くの反埩を費やすこずはあたりにも高䟡です。 したがっお、ランク配列の各ルヌトには、そのツリヌの高さ以䞊であるこずが保蚌された数倀が曞き蟌たれたす。



マヌゞの決定は簡単になりたした。DSUで長すぎるブランチを防ぐために、䞋䜍のツリヌを䞊䜍のツリヌからぶら䞋げたす。 それらの高さが等しい堎合-誰から誰を掛けるかは関係ありたせん。 しかし、埌者の堎合、新しく䜜成されたルヌトはランクを䞊げるこずを忘れおはなりたせん。



これは、兞型的なUniteがどのように生成されるかですたずえば、パラメヌタヌ8ず19で









 public void Unite(int x, int y) { x = Find(x); y = Find(y); if (rank[x] < rank[y]) p[x] = y; else { p[y] = x; if (rank[x] == rank[y]) ++rank[x]; } }
      
      







ただし、実際には、ランクの詐欺に远加のONメモリを費やすこずはできたせん。 再懞濁のルヌトをランダムに遞択するだけで十分です-驚くべきこずに、この゜リュヌションは、実際には元のランキングの実装ず非垞に匹敵する速床を提䟛したす。 この蚘事の著者は、生涯にわたっおランダム化DSUを䜿甚しおきたしたが、倱敗するこずは䞀床もありたせんでした。



C実装

 public void Unite(int x, int y) { x = Find(x); y = Find(y); if (rand.Next() % 2 == 0) Swap(ref x, ref y); p[x] = y; }
      
      







性胜



2぀のヒュヌリスティックの䜿甚により、各操䜜の速床はツリヌの構造ず、以前に実行された操䜜のリストのツリヌの構造に倧きく䟝存したす。 唯䞀の䟋倖はMakeSetです-その動䜜時間は明らかにO1です:-)他の2぀の堎合、速床は非垞に明癜です。



Robert Taryanは1975幎に泚目すべき事実を蚌明したした。サむズNのフォレストでのFindずUniteの䞡方の操䜜時間はOαNです。

数孊のαNの䞋には、逆アッカヌマン関数、぀たりfN= AN、Nの逆関数がありたす。 アッカヌマンの関数 AN、Mは、巚倧な成長率を持぀こずが知られおいたす。 たずえば、 A4、4= 2 2 2 65536 -3の堎合、この数倀は非垞に倧きくなりたす。 䞀般に、考えられるすべおのNの実甚的な倀に察しお、その逆アッカヌマン関数は5を超えたせん。したがっお、それを定数ずしお取り、 OαN≅O1ず仮定できたす。



だから私たちは持っおいたす

MakeSetX-O1。

怜玢X-O1は償华されたす。

結合X、Y-O1償华枈み。

メモリ消費-ON。



党然悪くない:-)



実甚的なアプリケヌション



DSUにはさたざたな甚途がありたす。 それらのほずんどは、 オフラむンで問題を解決するこずに関連しおいたす。぀たり、プログラムが受け取る構造に関するク゚リのリストが事前にわかっおいる堎合です。 ここでは、これらのタスクのいく぀かず、゜リュヌションの簡単なアむデアを瀺したす。



最小重量のスケルトン


重み付き゚ッゞを持぀無向接続グラフが䞎えられたす。 結果ずしおツリヌを取埗し、このツリヌの゚ッゞの総重量が最小になるように、そこからいく぀かの゚ッゞをスロヌしたす。



このタスクが発生する既知の堎所の1぀は異なる方法で解決されたすが、むヌサネットネットワヌク内の冗長接続をブロックしお、起こり埗るパケットサむクルを回避するこずです。 この目的のために䜜成されたプロトコルは広く知られおおり、䞻芁な倉曎の半分がシスコによっお行われおいたす。 詳现に぀いおは、 スパニングツリヌプロトコルを参照しおください。



この図は、最小スケルトンが匷調衚瀺された重み付きグラフを瀺しおいたす。









この問題を解決するためのクラスカルのアルゎリズムすべおの゚ッゞを重みの昇順で゜ヌトし、DSUを䜿甚しお珟圚の最小重みのフォレストを維持したす。 最初に、DSUはN個のツリヌで構成され、それぞれに1぀の頂点がありたす。 rib骚に沿っお昇順に行きたす。 珟圚の゚ッゞが異なるコンポヌネントを結合しおいる堎合、それらを1぀にマヌゞし、コアの芁玠ずしお゚ッゞを蚘憶したす。 コンポヌネントの数が1に達するずすぐに、目的のツリヌを構築したした。



TarjanのLCAオフラむン怜玢アルゎリズム


ツリヌず次の圢匏のク゚リのセットを指定したす。指定された頂点uおよびvに぀いお 、最も近い共通の祖先最小共通の祖先、LCAを返したす。 ク゚リのセット党䜓は事前にわかっおいたす。 タスクはオフラむンで䜜成されたす。



ツリヌを深く怜玢するこずから始めたしょう。 ク゚リ<u、v>を怜蚎しおください。 深さの怜玢を、芁求頂点の1぀たずえばuが以前に怜玢で既に蚪れおいお、怜玢の珟圚の頂点がvであり、サブツリヌv党䜓がちょうど調べられた状態にしたす。 明らかに、芁求に察する応答は、頂点v自䜓たたはその祖先のいずれかです。 さらに、ルヌトぞのパスに沿った各祖先vは、それが答えである頂点uの特定のクラスを生成したす。このクラスは、そのような祖先の「巊偎の」既に衚瀺されたツリヌブランチずたったく同じです。

図では、頂点がクラスに分割されたツリヌを芋るこずができたすが、癜い頂点はただ衚瀺されおいたせん。







このような頂点のクラスは亀差したせん。぀たり、それらはDSUでサポヌトされたす。 深さ怜玢がサブツリヌから戻るずすぐに、このサブツリヌのクラスを珟圚の頂点のクラスずマヌゞしたす。 そしお、答えを探すために、 祖先配列をサポヌトしたす-各頂点に察しお、この頂点のクラスを生成した祖先の固有者。 代衚のこの配列のセル倀は、ツリヌをマヌゞするずきに曞き換えを忘れおはなりたせん。 しかし、今では、完党に凊理された各頂点vを詳现に怜玢するプロセスで、ク゚リリストですべおの<u、v>を芋぀けるこずができたす。ここで、uは既に凊理され、答えを出力したす Ancestor[Find(u)]



。



マルチグラフの接続コンポヌネント


マルチグラフ頂点のペアが耇数の盎接゚ッゞで接続できるグラフが䞎えられ、「いく぀かの゚ッゞを削陀」および「グラフに珟圚接続されおいるコンポヌネントの数」ずいう圢匏のク゚リが受信されたす。ク゚リのリスト党䜓が事前にわかっおいたす。



解決策はささいなこずです。 たず、すべおの削陀リク゚ストを実行し、最終的なグラフのコンポヌネント数を蚈算しお、それを蚘憶したす。 結果のグラフをDSUにプッシュしたす。 ここで、逆の順序で削陀芁求に進みたす。叀いグラフからの各゚ッゞの削陀は、DSUに栌玍された「フラッシュバックグラフ」の2぀のコンポヌネントのマヌゞの可胜性を意味したす。 この堎合、珟圚接続されおいるコンポヌネントの数は1぀枛少したす。



画像のセグメンテヌション


画像を考えおみたしょう-ピクセルの長方圢の配列。 グリッドグラフずしお衚すこずができたす。各頂点ピクセルは、4぀の最も近い隣接する゚ッゞで盎接接続されおいたす。 タスクは、たずえば同じ色の画像内の同じセマンティックゟヌンを匷調衚瀺し、2぀のピクセルが同じゟヌンにあるかどうかをすばやく刀断できるようにするこずです。 たずえば、叀い癜黒のフィルムはペむントされたす。たず、グレヌの色調がほが同じ領域を遞択する必芁がありたす。



この問題を解決するには2぀の方法があり、どちらもDSUを䜿甚したす。 最初のオプションでは、隣接するピクセルの各ペア間ではなく、色が十分近いピクセル間にのみ゚ッゞを描画したす。 その埌、グラフの通垞の長方圢の走査がDSUを埋め、接続されたコンポヌネントのセットを取埗したす-それらは同じ色の画像領域です。



2番目のオプションはより柔軟です。 rib骚を完党に削陀するわけではありたせんが、それぞれのcolor骚に、色の違いやその他の远加の芁因に基づいお蚈算された重みを割り圓おたす-特定の各セグメンテヌションタスクに固有です。 埗られたグラフでは、たずえば同じクラスカルアルゎリズムを䜿甚しお、最小の重みのフォレストを芋぀けるだけで十分です。 実際には、珟圚の接続゚ッゞがDSUに蚘録されるのではなく、珟時点で2぀のコンポヌネントが別の特別な重み関数の暙準によっおあたり倉わらないもののみが蚘録されたす。



ラビリンスゞェネレヌション


タスク1぀の入力ず1぀の出力を持぀迷路を生成したす。



゜リュヌションアルゎリズム

入り口ず出口を陀くすべおの壁が蚭眮された状態から始めたしょう。

アルゎリズムの各ステップで、ランダムな壁を遞択したす。 間にあるセルがただ接続されおいない堎合DSUの異なるコンポヌネントにある堎合、それを砎棄したすコンポヌネントをマヌゞしたす。

特定の収束状態たでプロセスを続行したす。たずえば、入力ず出力が接続されおいる堎合などです。 たたは、1぀のコンポヌネントが残っおいる堎合。



シングルパスアルゎリズム



FindXには、2぀ではなく1぀のルヌトぞのパスを必芁ずするが、同じたたはほが同じ皋床のパフォヌマンスを保持する実装オプションがありたす。 それらは、パス圧瞮ずは察照的に、ツリヌの高さを枛らすための他の戊略を実装したす。



オプション1 パス分割 。 䞊䜍Xからルヌトぞの途䞭で、各頂点の芪接続を祖先から祖先祖父の祖先にリダむレクトしたす。



オプション番号2 パスの半分 。 ただし、パス分割の考え方を取り入れお、パスに沿ったすべおの頂点の接続ではなく、リダむレクト先の接続のみ、぀たり「祖父」の接続をリダむレクトしたす。



図では同じツリヌが䜿甚され、怜玢3ク゚リが実行されたす。 䞭倮では、結果は右偎のパス分割を䜿甚しお衚瀺されたす-パスは半分になりたす。





機胜実装



ばらばらのセットのシステムには1぀の倧きな欠点がありたす。それは、呜什型スタむルで実装されるため、どのような圢匏でもUndo操䜜をサポヌトしたせん。 各操䜜がその堎で構造を倉曎せず、必芁な倉曎が行われたわずかに倉曎された新しい構造を返す堎合、機胜スタむルのDSUを実装する方がはるかに䟿利です叀い構造ず新しい構造のメモリのほずんどは共通です。 このようなデヌタ構造は、英語の甚語では氞続ず呌ばれ、デヌタの䞍倉性の抂念が支配する玔粋な関数型プログラミングで広く䜿甚されおいたす。



DSUアルゎリズムの玔粋に必須のアむデアのため、その機胜の実装は、パフォヌマンスを維持しながら、長い間考えられないように思われたした。 しかし、2007幎に、Sylvain ConchonずJean-ChristopheFilliâtreは、圌らの研究で、Unite操䜜が倉曎された構造を返す必芁な機胜的倉圢を提瀺したした。 正盎に蚀うず、完党に機胜しおいるわけではなく、呜什型の割り圓おを䜿甚しおいたすが、実装内に安党に隠されおおり、氞続的なDSUむンタヌフェむスは玔粋に機胜しおいたす。



実装の䞻なアむデアは、「氞続配列」を実装するデヌタ構造の䜿甚です。これらは、配列ず同じ操䜜をサポヌトしたすが、メモリを倉曎せず、倉曎された構造を返したす。 このような配列は、䜕らかのツリヌを䜿甚しお簡単に実装できたすが、この堎合、それを䜿甚した操䜜にはOlog 2 N時間かかり、DSUの堎合、そのような掚定はすでに過床に倧きくなりたす。



技術的な詳现に぀いおは、読者は元の蚘事を参照しおください 。



文孊



この蚘事の範囲内の互いに玠な集合のシステムは、有名な本-Kormen、Leiserson、Rivest、Stein "Algorithmsconstruction and analysis。"で議論されおいたす。 たた、操䜜の実行に玄αN時間かかるずいう蚌拠もありたす。



Maxim Ivanovのサむトで 、C ++でのDSUの完党な実装を芋぀けるこずができたす。



Union-Find Problemの埅機フリヌ䞊列アルゎリズムの蚘事では、DSU実装の䞊列バヌゞョンに぀いお説明しおいたす。 スレッドロックはありたせん。



ご枅聎ありがずうございたした:-)



All Articles