web-dev-qa-db-ja.com

有向グラフですべてのサイクルを見つける

特定のノードから/への有向グラフ内のすべてのサイクルを見つける(反復する)にはどうすればよいですか?

たとえば、次のようなものが必要です。

A->B->A
A->B->C->A

ただし:B-> C-> B

187
user7305

私は検索でこのページを見つけました。サイクルは強く連結されたコンポーネントと同じではないため、検索を続け、最後に、有向グラフのすべての(基本)サイクルをリストする効率的なアルゴリズムを見つけました。これはドナルドB.ジョンソンによるもので、この論文は次のリンクにあります。

http://www.cs.tufts.edu/comp/150GA/homeworks/hw1/Johnson%2075.PDF

Java実装は次の場所にあります。

http://normalisiert.de/code/Java/elementaryCycles.Zip

Johnson =のアルゴリズムのMathematicaデモンストレーションを見つけることができます ここ 、実装は右からダウンロードできます( "ダウンロード著者コード」 )。

注:実際、この問題には多くのアルゴリズムがあります。それらのいくつかはこの記事にリストされています:

http://dx.doi.org/10.1137/0205007

記事によると、Johnsonのアルゴリズムは最速のものです。

98
eminsenay

ここでバックトラッキングを使用した深さ優先検索が機能するはずです。ブール値の配列を保持して、以前にノードにアクセスしたかどうかを追跡します。新しいノードを使い果たして(既にアクセスしたノードにヒットすることなく)移動した場合は、バックトラックして別のブランチを試してください。

グラフを表す隣接リストがある場合、DFSは簡単に実装できます。たとえば、adj [A] = {B、C}は、BとCがAの子であることを示します。

たとえば、以下の擬似コード。 「開始」は、開始元のノードです。

dfs(adj,node,visited):  
  if (visited[node]):  
    if (node == start):  
      "found a path"  
    return;  
  visited[node]=YES;  
  for child in adj[node]:  
    dfs(adj,child,visited)
  visited[node]=NO;

開始ノードで上記の関数を呼び出します。

visited = {}
dfs(adj,start,visited)
31

まず第一に-あなたが本当にすべてのサイクルを見つけようとするのは望ましくありません。なぜなら、1があれば、それらの無限の数があるからです。たとえば、ABA、ABABAなど。または、2サイクルを8サイクルなどに結合することも可能です。意味のあるアプローチは、いわゆる単純なサイクルをすべて探すことです。開始/終了ポイント。その後、必要に応じて、単純なサイクルの組み合わせを生成できます。

有向グラフ内のすべての単純なサイクルを見つけるためのベースラインアルゴリズムの1つは次のとおりです。グラフ内のすべての単純なパス(それ自体と交差しないパス)の深さ優先走査を行います。現在のノードのスタックに後続ノードがあるたびに、単純なサイクルが発見されます。スタック上の要素で構成され、識別された後続要素で始まり、スタックの最上部で終わります。すべての単純なパスの深さ優先走査は深さ優先検索に似ていますが、現在スタック上にあるノード以外の訪問済みノードをストップポイントとしてマーク/記録しません。

上記のブルートフォースアルゴリズムは非常に非効率的であり、それに加えてサイクルの複数のコピーを生成します。ただし、パフォーマンスを改善し、サイクルの重複を回避するためにさまざまな拡張機能を適用する複数の実用的なアルゴリズムの出発点です。少し前に、これらのアルゴリズムが教科書やウェブで簡単に入手できないことを知って驚いた。そこで、いくつかの研究を行い、このような4つのアルゴリズムと、オープンソースJavaライブラリの無向グラフのサイクルに1つのアルゴリズムを実装しました: http://code.google.com/p/niographs/

ところで、私は無向グラフについて述べたので、それらのアルゴリズムは異なります。スパニングツリーを構築すると、ツリーの一部ではないすべてのエッジが、ツリー内のいくつかのエッジと共に単純なサイクルを形成します。このようにして見つかったサイクルは、いわゆるサイクルベースを形成します。 2つ以上の異なる基本サイクルを組み合わせることで、すべての単純なサイクルを見つけることができます。詳細については、例えばこれ: http://dspace.mit.edu/bitstream/handle/1721.1/68106/FTL_R_1982_07.pdf

20

この問題を解決するために見つけた最も簡単な選択は、networkxというpython libを使用することでした。

この質問のベストアンサーで述べたジョンソンのアルゴリズムを実装していますが、実行は非常に簡単です。

要するに、次のものが必要です。

import networkx as nx
import matplotlib.pyplot as plt

# Create Directed Graph
G=nx.DiGraph()

# Add a list of nodes:
G.add_nodes_from(["a","b","c","d","e"])

# Add a list of edges:
G.add_edges_from([("a","b"),("b","c"), ("c","a"), ("b","d"), ("d","e"), ("e","a")])

#Return a list of cycles described as a list o nodes
list(nx.simple_cycles(G))

回答:[['a'、 'b'、 'd'、 'e']、['a'、 'b'、 'c ']]

enter image description here

16
fernandosjp

明確にするために:

  1. 強く接続されたコンポーネントは、グラフ内のすべての可能なサイクルではなく、少なくとも1つのサイクルを持つサブグラフをすべて検索します。例えば強く接続されたすべてのコンポーネントを取得し、各コンポーネントを1つのノード(コンポーネントごとのノード)に折りたたみ/グループ化/マージすると、サイクルのないツリー(実際にはDAG)が得られます。各コンポーネント(基本的に少なくとも1つのサイクルを持つサブグラフ)には、内部でさらに多くの可能なサイクルを含めることができるため、SCCはすべての可能なサイクルを見つけず、少なくとも1つのサイクルを持つすべてのグループを見つけます。サイクルし、それらをグループ化すると、グラフにはサイクルがなくなります。

  2. 他の人が述べたように、グラフでall単純なサイクルを見つけるには、Johnsonのアルゴリズムが候補です。

5
Eran Medan

私はかつてこれをインタビューの質問として与えられました。これはあなたに起こったと思われ、あなたは助けを求めてここに来ています。問題を3つの質問に分けると、簡単になります。

  1. 次の有効なルートをどのように決定しますか
  2. ポイントが使用されたかどうかをどのように判断しますか
  3. 同じポイントをまたがるのをどうやって避けますか

問題1)イテレータパターンを使用して、ルート結果を反復する方法を提供します。次のルートを取得するためのロジックを配置する適切な場所は、おそらくイテレーターの「moveNext」です。有効なルートを見つけるには、データ構造によって異なります。私にとっては、有効なルートの可能性に満ちたSQLテーブルだったので、ソースを指定して有効な宛先を取得するクエリを作成する必要がありました。

問題2)取得した各ノードをコレクションにプッシュします。これは、構築中のコレクションをオンザフライで調べることで、ポイントを「二重に戻す」かどうかを簡単に確認できることを意味します。

問題3)ある時点で2倍になった場合、コレクションからポップして「バックアップ」できます。その後、その時点から再び「前進」を試みます。

ハック:Sql Server 2008を使用している場合、データをツリーで構造化すると、これをすばやく解決するために使用できるいくつかの新しい「階層」があります。

3
slf

無向グラフの場合、最近公開された論文(無向グラフのサイクルとst-pathsの最適なリスト)は、漸近的に最適なソリューションを提供します。ここで読むことができます http://arxiv.org/abs/1205.2766 またはここで http://dl.acm.org/citation.cfm?id=2627951 Iそれはあなたの質問に答えていないことを知っていますが、あなたの質問のタイトルは方向に言及していないので、それはまだGoogle検索に役立つかもしれません

2
daureg

バックエッジのあるDFSベースのバリアントは実際にサイクルを検出しますが、多くの場合、minimalサイクルではありません。一般に、DFSはサイクルがあるというフラグを提供しますが、実際にサイクルを見つけるには十分ではありません。たとえば、2つのエッジを共有する5つの異なるサイクルを想像してください。 DFSのみを使用してサイクルを識別する簡単な方法はありません(バックトラッキングバリアントを含む)。

ジョンソンのアルゴリズムは確かにすべてのユニークな単純なサイクルを提供し、時間と空間の複雑さは良好です。

ただし、最小サイクル(頂点を1サイクル以上通過する可能性があり、最小サイクルの検出に関心があることを意味する)を見つけたいだけで、グラフがそれほど大きくない場合は、以下の簡単な方法を試してみてください。これは非常にシンプルですが、ジョンソンのものに比べてかなり遅いです。

absolutely最小サイクルを見つける最も簡単な方法の1つは、隣接行列を使用して、すべての頂点間の最小パスを見つけるためにフロイドのアルゴリズムを使用することです。このアルゴリズムは、ジョンソンのアルゴリズムほど最適ではありませんが、非常に単純で、その内部ループは非常にタイトであるため、小さなグラフ(<= 50-100ノード)で使用するのは理にかなっています。時間の複雑さはO(n ^ 3)、スペースの複雑さは親トラッキングを使用する場合はO(n ^ 2)、使用しない場合はO(1)です。まず最初に、サイクルがある場合の質問に対する答えを見つけましょう。アルゴリズムは非常に単純です。以下はScalaのスニペットです。

  val NO_Edge = Integer.MAX_VALUE / 2

  def shortestPath(weights: Array[Array[Int]]) = {
    for (k <- weights.indices;
         i <- weights.indices;
         j <- weights.indices) {
      val throughK = weights(i)(k) + weights(k)(j)
      if (throughK < weights(i)(j)) {
        weights(i)(j) = throughK
      }
    }
  }

もともと、このアルゴリズムは、重み付きエッジグラフを操作して、すべてのノードペア間のすべての最短パスを検索します(したがって、重み引数)。正しく機能するためには、ノード間に有向エッジがある場合は1を指定する必要があり、そうでない場合はNO_Edgeを指定する必要があります。アルゴリズムの実行後、このノードが値に等しい長さのサイクルに参加するよりもNO_Edgeより小さい値がある場合、メインの対角線をチェックできます。同じサイクルの他のすべてのノードは同じ値を持ちます(メイン対角線上)。

サイクル自体を再構築するには、親の追跡機能を備えたアルゴリズムのわずかに修正されたバージョンを使用する必要があります。

  def shortestPath(weights: Array[Array[Int]], parents: Array[Array[Int]]) = {
    for (k <- weights.indices;
         i <- weights.indices;
         j <- weights.indices) {
      val throughK = weights(i)(k) + weights(k)(j)
      if (throughK < weights(i)(j)) {
        parents(i)(j) = k
        weights(i)(j) = throughK
      }
    }
  }

親マトリックスは、頂点間にEdgeがある場合は最初にEdgeセルにソース頂点インデックスを含み、それ以外の場合は-1を含む必要があります。関数が戻った後、各エッジについて、最短パスツリーの親ノードへの参照があります。そして、実際のサイクルを簡単に回復できます。

全体として、すべての最小サイクルを見つけるために次のプログラムがあります

  val NO_Edge = Integer.MAX_VALUE / 2;

  def shortestPathWithParentTracking(
         weights: Array[Array[Int]],
         parents: Array[Array[Int]]) = {
    for (k <- weights.indices;
         i <- weights.indices;
         j <- weights.indices) {
      val throughK = weights(i)(k) + weights(k)(j)
      if (throughK < weights(i)(j)) {
        parents(i)(j) = parents(i)(k)
        weights(i)(j) = throughK
      }
    }
  }

  def recoverCycles(
         cycleNodes: Seq[Int], 
         parents: Array[Array[Int]]): Set[Seq[Int]] = {
    val res = new mutable.HashSet[Seq[Int]]()
    for (node <- cycleNodes) {
      var cycle = new mutable.ArrayBuffer[Int]()
      cycle += node
      var other = parents(node)(node)
      do {
        cycle += other
        other = parents(other)(node)
      } while(other != node)
      res += cycle.sorted
    }
    res.toSet
  }

そして、結果をテストするための小さなメインメソッド

  def main(args: Array[String]): Unit = {
    val n = 3
    val weights = Array(Array(NO_Edge, 1, NO_Edge), Array(NO_Edge, NO_Edge, 1), Array(1, NO_Edge, NO_Edge))
    val parents = Array(Array(-1, 1, -1), Array(-1, -1, 2), Array(0, -1, -1))
    shortestPathWithParentTracking(weights, parents)
    val cycleNodes = parents.indices.filter(i => parents(i)(i) < NO_Edge)
    val cycles: Set[Seq[Int]] = recoverCycles(cycleNodes, parents)
    println("The following minimal cycle found:")
    cycles.foreach(c => println(c.mkString))
    println(s"Total: ${cycles.size} cycle found")
  }

そして出力は

The following minimal cycle found:
012
Total: 1 cycle found
2
Kirill Frolov

グラフ内のすべての基本回路を検索する場合は、JAMES C. TIERNANによる1970年以降の論文にあるECアルゴリズムを使用できます。

非常にオリジナルなECアルゴリズムをphpで実装することができました(間違いがないことを期待してください)。ループがあればそれも見つけることができます。この実装(元のクローンを作成しようとする)の回路は、ゼロ以外の要素です。ここでのゼロは、存在しないことを意味します(ご存じのとおり、null)。

それとは別に、アルゴリズムをより独立させる他の実装が続きます。これは、ノードが負の数値からでもどこからでも開始できることを意味します(例:-4、-3、-2、..など)。

どちらの場合も、ノードは連続している必要があります。

元の論文 James C. Tiernan Elementary Circuit Algorithm を調べる必要があるかもしれません

<?php
echo  "<pre><br><br>";

$G = array(
        1=>array(1,2,3),
        2=>array(1,2,3),
        3=>array(1,2,3)
);


define('N',key(array_slice($G, -1, 1, true)));
$P = array(1=>0,2=>0,3=>0,4=>0,5=>0);
$H = array(1=>$P, 2=>$P, 3=>$P, 4=>$P, 5=>$P );
$k = 1;
$P[$k] = key($G);
$Circ = array();


#[Path Extension]
EC2_Path_Extension:
foreach($G[$P[$k]] as $j => $child ){
    if( $child>$P[1] and in_array($child, $P)===false and in_array($child, $H[$P[$k]])===false ){
    $k++;
    $P[$k] = $child;
    goto EC2_Path_Extension;
}   }

#[EC3 Circuit Confirmation]
if( in_array($P[1], $G[$P[$k]])===true ){//if PATH[1] is not child of PATH[current] then don't have a cycle
    $Circ[] = $P;
}

#[EC4 Vertex Closure]
if($k===1){
    goto EC5_Advance_Initial_Vertex;
}
//afou den ksana theoreitai einai asfales na svisoume
for( $m=1; $m<=N; $m++){//H[P[k], m] <- O, m = 1, 2, . . . , N
    if( $H[$P[$k-1]][$m]===0 ){
        $H[$P[$k-1]][$m]=$P[$k];
        break(1);
    }
}
for( $m=1; $m<=N; $m++ ){//H[P[k], m] <- O, m = 1, 2, . . . , N
    $H[$P[$k]][$m]=0;
}
$P[$k]=0;
$k--;
goto EC2_Path_Extension;

#[EC5 Advance Initial Vertex]
EC5_Advance_Initial_Vertex:
if($P[1] === N){
    goto EC6_Terminate;
}
$P[1]++;
$k=1;
$H=array(
        1=>array(1=>0,2=>0,3=>0,4=>0,5=>0),
        2=>array(1=>0,2=>0,3=>0,4=>0,5=>0),
        3=>array(1=>0,2=>0,3=>0,4=>0,5=>0),
        4=>array(1=>0,2=>0,3=>0,4=>0,5=>0),
        5=>array(1=>0,2=>0,3=>0,4=>0,5=>0)
);
goto EC2_Path_Extension;

#[EC5 Advance Initial Vertex]
EC6_Terminate:
print_r($Circ);
?>

次に、これは他の実装であり、gotoおよび配列値なしのグラフからより独立していますが、代わりに配列キーを使用し、パス、グラフおよび回路は配列キーとして保存されます(必要に応じて配列値を使用し、必要なものを変更するだけです)行)。グラフの例は-4から始まり、その独立性を示しています。

<?php

$G = array(
        -4=>array(-4=>true,-3=>true,-2=>true),
        -3=>array(-4=>true,-3=>true,-2=>true),
        -2=>array(-4=>true,-3=>true,-2=>true)
);


$C = array();


EC($G,$C);
echo "<pre>";
print_r($C);
function EC($G, &$C){

    $CNST_not_closed =  false;                          // this flag indicates no closure
    $CNST_closed        = true;                         // this flag indicates closure
    // define the state where there is no closures for some node
    $tmp_first_node  =  key($G);                        // first node = first key
    $tmp_last_node  =   $tmp_first_node-1+count($G);    // last node  = last  key
    $CNST_closure_reset = array();
    for($k=$tmp_first_node; $k<=$tmp_last_node; $k++){
        $CNST_closure_reset[$k] = $CNST_not_closed;
    }
    // define the state where there is no closure for all nodes
    for($k=$tmp_first_node; $k<=$tmp_last_node; $k++){
        $H[$k] = $CNST_closure_reset;   // Key in the closure arrays represent nodes
    }
    unset($tmp_first_node);
    unset($tmp_last_node);


    # Start algorithm
    foreach($G as $init_node => $children){#[Jump to initial node set]
        #[Initial Node Set]
        $P = array();                   // declare at starup, remove the old $init_node from path on loop
        $P[$init_node]=true;            // the first key in P is always the new initial node
        $k=$init_node;                  // update the current node
                                        // On loop H[old_init_node] is not cleared cause is never checked again
        do{#Path 1,3,7,4 jump here to extend father 7
            do{#Path from 1,3,8,5 became 2,4,8,5,6 jump here to extend child 6
                $new_expansion = false;
                foreach( $G[$k] as $child => $foo ){#Consider each child of 7 or 6
                    if( $child>$init_node and isset($P[$child])===false and $H[$k][$child]===$CNST_not_closed ){
                        $P[$child]=true;    // add this child to the path
                        $k = $child;        // update the current node
                        $new_expansion=true;// set the flag for expanding the child of k
                        break(1);           // we are done, one child at a time
            }   }   }while(($new_expansion===true));// Do while a new child has been added to the path

            # If the first node is child of the last we have a circuit
            if( isset($G[$k][$init_node])===true ){
                $C[] = $P;  // Leaving this out of closure will catch loops to
            }

            # Closure
            if($k>$init_node){                  //if k>init_node then alwaya count(P)>1, so proceed to closure
                $new_expansion=true;            // $new_expansion is never true, set true to expand father of k
                unset($P[$k]);                  // remove k from path
                end($P); $k_father = key($P);   // get father of k
                $H[$k_father][$k]=$CNST_closed; // mark k as closed
                $H[$k] = $CNST_closure_reset;   // reset k closure
                $k = $k_father;                 // update k
        }   } while($new_expansion===true);//if we don't wnter the if block m has the old k$k_father_old = $k;
        // Advance Initial Vertex Context
    }//foreach initial


}//function

?>

ECを分析して文書化しましたが、残念ながら文書はギリシャ語です。

1
Melsi

ノードXから開始し、すべての子ノードを確認します(無向の場合、親ノードと子ノードは同等です)。これらの子ノードをXの子としてマークします。このような子ノードAから、A、X 'の子であるというマークを付けます。X'は2ステップ先としてマークされます。)後でXを押し、Xの子としてマークする場合、Xは3ノードサイクルにあることを意味します。その親へのバックトラックは簡単です(現状では、アルゴリズムはこれをサポートしていないため、X 'を持つ親を見つけることができます)。

注:グラフが無向であるか、双方向のエッジがある場合、このアルゴリズムは、同じエッジを1サイクルで2回トラバースしたくないと仮定すると、より複雑になります。

1
Brian

ノードを横断するために少し再帰的な関数を作成することはできませんか?

readDiGraph( string pathSoFar, Node x)
{

    if(NoChildren) MasterList.add( pathsofar + Node.name ) ; 

    foreach( child ) 
    {
       readDiGraph( pathsofar + "->" + this.name, child) 
    }
}

大量のノードがある場合、スタックが不足します

1
astronought

DAGのすべてのサイクルを見つけるには、2つのステップ(アルゴリズム)が含まれます。

最初のステップは、Tarjanのアルゴリズムを使用して、強く接続されたコンポーネントのセットを見つけることです。

  1. 任意の頂点から開始します。
  2. その頂点からのDFS。各ノードxについて、dfs_index [x]とdfs_lowval [x]の2つの数値を保持します。 dfs_index [x]はそのノードがいつアクセスされたかを格納しますが、dfs_lowval [x] = min(dfs_low [k])で、kはdfsスパニングツリーのxの直接の親ではないxのすべての子です。
  3. 同じdfs_lowval [x]を持つすべてのノードは、同じ強く接続されたコンポーネントにあります。

2番目のステップは、接続されたコンポーネント内のサイクル(パス)を見つけることです。私の提案は、Hierholzerのアルゴリズムの修正版を使用することです。

アイデアは次のとおりです。

  1. 任意の開始頂点vを選択し、vに戻るまでその頂点からエッジの軌跡をたどります。v以外の頂点でスタックすることはできません。頂点w wを残す未使用のEdgeがなければなりません。この方法で形成されたツアーはクローズドツアーですが、最初のグラフのすべての頂点とエッジをカバーするわけではありません。
  2. 現在のツアーに属しているが、ツアーの一部ではない隣接するエッジを持つ頂点vが存在する限り、vから戻るまで未使用のエッジをたどって、vから別のトレイルを開始し、この方法で形成されたツアーに参加します前のツアー。

テストケースを使用したJava実装へのリンクは次のとおりです。

http://stones333.blogspot.com/2013/12/find-cycles-in-directed-graph-dag.html

1
stones333

開始ノードsからのDFS、トラバーサル中にDFSパスを追跡し、sへのパスでノードvからエッジを見つけた場合、パスを記録します。 (v、s)はDFSツリーのバックエッジであるため、sを含むサイクルを示します。

0
Xceptional

ジョンソンのアルゴリズムよりも効率的であると思われる次のアルゴリズムを見つけました(少なくとも大きなグラフの場合)。ただし、Tarjanのアルゴリズムと比較した場合のパフォーマンスについてはわかりません。
さらに、これまで三角形についてのみチェックアウトしました。興味のある方は、千葉典繁と西関貴雄の「樹木と部分グラフのリストアルゴリズム」を参照してください( http://dx.doi.org/10.1137/0214017

0
Shadow

置換サイクルに関する質問については、こちらをご覧ください: https://www.codechef.com/problems/PCYCLE

このコードを試すことができます(サイズと数字を入力してください):

# include<cstdio>
using namespace std;

int main()
{
    int n;
    scanf("%d",&n);

    int num[1000];
    int visited[1000]={0};
    int vindex[2000];
    for(int i=1;i<=n;i++)
        scanf("%d",&num[i]);

    int t_visited=0;
    int cycles=0;
    int start=0, index;

    while(t_visited < n)
    {
        for(int i=1;i<=n;i++)
        {
            if(visited[i]==0)
            {
                vindex[start]=i;
                visited[i]=1;
                t_visited++;
                index=start;
                break;
            }
        }
        while(true)
        {
            index++;
            vindex[index]=num[vindex[index-1]];

            if(vindex[index]==vindex[start])
                break;
            visited[vindex[index]]=1;
            t_visited++;
        }
        vindex[++index]=0;
        start=index+1;
        cycles++;
    }

    printf("%d\n",cycles,vindex[0]);

    for(int i=0;i<(n+2*cycles);i++)
    {
        if(vindex[i]==0)
            printf("\n");
        else
            printf("%d ",vindex[i]);
    }
}
0

互いに素なセットのリンクリストを使用したJavascriptソリューション。実行時間を短縮するために、フォレストをばらばらにするようにアップグレードできます。

var input = '5\nYYNNN\nYYYNN\nNYYNN\nNNNYN\nNNNNY'
console.log(input);
//above solution should be 3 because the components are
//{0,1,2}, because {0,1} and {1,2} therefore {0,1,2}
//{3}
//{4}

//MIT license, authored by Ling Qing Meng

//'4\nYYNN\nYYYN\nNYYN\nNNNY'

//Read Input, preformatting
var reformat = input.split(/\n/);
var N = reformat[0];
var adjMatrix = [];
for (var i = 1; i < reformat.length; i++) {
    adjMatrix.Push(reformat[i]);
}

//for (each person x from 1 to N) CREATE-SET(x)
var sets = [];
for (var i = 0; i < N; i++) {
    var s = new LinkedList();
    s.add(i);
    sets.Push(s);
}

//populate friend potentials using combinatorics, then filters
var people =  [];
var friends = [];
for (var i = 0; i < N; i++) {
    people.Push(i);
}
var potentialFriends = k_combinations(people,2);
for (var i = 0; i < potentialFriends.length; i++){
    if (isFriend(adjMatrix,potentialFriends[i]) === 'Y'){
        friends.Push(potentialFriends[i]);
    }
}


//for (each pair of friends (x y) ) if (FIND-SET(x) != FIND-SET(y)) MERGE-SETS(x, y)
for (var i = 0; i < friends.length; i++) {
    var x = friends[i][0];
    var y = friends[i][1];
    if (FindSet(x) != FindSet(y)) {
        sets.Push(MergeSet(x,y));
    }
}


for (var i = 0; i < sets.length; i++) {
    //sets[i].traverse();
}
console.log('How many distinct connected components?',sets.length);



//Linked List data structures neccesary for above to work
function Node(){
    this.data = null;
    this.next = null;
}

function LinkedList(){
    this.head = null;
    this.tail = null;
    this.size = 0;

    // Add node to the end
    this.add = function(data){
        var node = new Node();
        node.data = data;
        if (this.head == null){
            this.head = node;
            this.tail = node;
        } else {
            this.tail.next = node;
            this.tail = node;
        }
        this.size++;
    };


    this.contains = function(data) {
        if (this.head.data === data) 
            return this;
        var next = this.head.next;
        while (next !== null) {
            if (next.data === data) {
                return this;
            }
            next = next.next;
        }
        return null;
    };

    this.traverse = function() {
        var current = this.head;
        var toPrint = '';
        while (current !== null) {
            //callback.call(this, current); put callback as an argument to top function
            toPrint += current.data.toString() + ' ';
            current = current.next; 
        }
        console.log('list data: ',toPrint);
    }

    this.merge = function(list) {
        var current = this.head;
        var next = current.next;
        while (next !== null) {
            current = next;
            next = next.next;
        }
        current.next = list.head;
        this.size += list.size;
        return this;
    };

    this.reverse = function() {
      if (this.head == null) 
        return;
      if (this.head.next == null) 
        return;

      var currentNode = this.head;
      var nextNode = this.head.next;
      var prevNode = this.head;
      this.head.next = null;
      while (nextNode != null) {
        currentNode = nextNode;
        nextNode = currentNode.next;
        currentNode.next = prevNode;
        prevNode = currentNode;
      }
      this.head = currentNode;
      return this;
    }


}


/**
 * GENERAL HELPER FUNCTIONS
 */

function FindSet(x) {
    for (var i = 0; i < sets.length; i++){
        if (sets[i].contains(x) != null) {
            return sets[i].contains(x);
        }
    }
    return null;
}

function MergeSet(x,y) {
    var listA,listB;
    for (var i = 0; i < sets.length; i++){
        if (sets[i].contains(x) != null) {
            listA = sets[i].contains(x);
            sets.splice(i,1);
        }
    }
    for (var i = 0; i < sets.length; i++) {
        if (sets[i].contains(y) != null) {
            listB = sets[i].contains(y);
            sets.splice(i,1);
        }
    }
    var res = MergeLists(listA,listB);
    return res;

}


function MergeLists(listA, listB) {
    var listC = new LinkedList();
    listA.merge(listB);
    listC = listA;
    return listC;
}

//access matrix by i,j -> returns 'Y' or 'N'
function isFriend(matrix, pair){
    return matrix[pair[0]].charAt(pair[1]);
}

function k_combinations(set, k) {
    var i, j, combs, head, tailcombs;
    if (k > set.length || k <= 0) {
        return [];
    }
    if (k == set.length) {
        return [set];
    }
    if (k == 1) {
        combs = [];
        for (i = 0; i < set.length; i++) {
            combs.Push([set[i]]);
        }
        return combs;
    }
    // Assert {1 < k < set.length}
    combs = [];
    for (i = 0; i < set.length - k + 1; i++) {
        head = set.slice(i, i+1);
        tailcombs = k_combinations(set.slice(i + 1), k - 1);
        for (j = 0; j < tailcombs.length; j++) {
            combs.Push(head.concat(tailcombs[j]));
        }
    }
    return combs;
}
0
Ling Qing Meng