web-dev-qa-db-ja.com

単一にリンクされたリストが循環/循環であるかどうかを見つけるための効率的なアルゴリズムは何ですか?

単一リンクリストが循環/循環であるかどうかを確認するにはどうすればよいですか?検索を試みましたが、満足のいく解決策が見つかりませんでした。可能であれば、疑似コードまたはJava実装を提供できますか?

例えば:
135714575、2番目の5は実際にはリストの3番目の要素です。

38
harshit

標準的な答えは、最初に2つの反復子を取り、最初の反復子を1回インクリメントし、2番目の反復子を2回インクリメントすることです。それらが同じオブジェクトを指しているかどうかを確認してください。次に、2回増分しているものが最初のヒットに到達するか、最後に到達するまで繰り返します。

このアルゴリズムは、完全な円であるだけでなく、リスト内のすべての循環リンクを検出します。

疑似コード(Javaではなく、テストされていません-頭の上から)

bool hasCircle(List l)
{
   Iterator i = l.begin(), j = l.begin();
   while (true) {
      // increment the iterators, if either is at the end, you're done, no circle
      if (i.hasNext())  i = i.next(); else return false;

      // second iterator is travelling twice as fast as first
      if (j.hasNext())  j = j.next(); else return false;
      if (j.hasNext())  j = j.next(); else return false;

      // this should be whatever test shows that the two
      // iterators are pointing at the same place
      if (i.getObject() == j.getObject()) { 
          return true;
      } 
   }
}
77
Lou Franco

フロイドのアルゴリズム と呼ばれる単純なアルゴリズムは、リンクリストの最初の要素から開始する2つのポインターaとbを持つことです。次に、各ステップでaを1回、bを2回インクリメントします。リストの最後(ループなし)またはa == b(リンクされたリストにループが含まれる)に到達するまで繰り返します。

別のアルゴリズムは ブレントのアルゴリズム です。

15
Mark Byers

私が知っている3つの主な戦略:

  1. リストのトラバースを開始し、アクセスしたすべてのノードを追跡します(たとえば、マップにアドレスを格納します)。新しいノードにアクセスするたびに、すでにアクセスしたかどうかを確認します。すでにノードにアクセスしたことがある場合は、明らかにループがあります。ループがない場合、最終的には最後に到達します。 O(N)追加情報を格納するためのスペースの複雑さのため、これは素晴らしいことではありません。

  2. 亀/ウサギのソリューション。リストの先頭から2つのポインターを開始します。最初のポインタである「カメ」は、反復ごとに1つのノードに進みます。もう1つのポインタである "Hare"は、反復ごとに2つのノードを前進させます。ループがない場合、ウサギとカメは両方ともリストの最後に到達します。ループがある場合、ノウサギはある時点で亀に合格し、それが発生するとループがあることがわかります。これはO(1)スペースの複雑さと非常に単純なアルゴリズムです。

  3. アルゴリズムを使用して、リンクリストを逆にします。リストにループがある場合、リストを元に戻そうとすると、リストの最初に戻ります。ループがない場合は、それを逆転させて終わりに到達します。これはO(1)スペースの複雑さですが、アルゴリズムがやや醜いです。

8

私はあなたのノードを数え、再び* headに行きます。

4
Batman

Tortoise-Hareアルゴリズム を使用します。

2
leppie

次のアプローチはどうですか:

標準のアルゴリズムに従って、リンクリストを昇順で並べ替えます。ソート前:4-2-6-1-5ソート後:1-2-4-5-6

ソートしたら、各ノードのデータを確認し、次のようなリンクノードのデータと比較します。

if(currentcode-> data> currentnode-> link-> data)すなわち循環= true;

どの比較でも、ソートされたリンクリストの「currentnode-> data」のいずれかが「currentcode-> link-> data」より大きい場合、現在のノードが以前のノード(循環)を指していることを意味します。

みんな、私はコードをテストするための設定を持っていません。このコンセプトが機能するかどうか今すぐ教えてください。

2
newbie

アルゴリズムは次のとおりです。

  1. 最初のノードへのポインタを格納します
  2. 各ノードポインターをこのポインターと比較してリストをトラバースします
  3. NULLポインターが発生した場合、循環的にリンクされていないリスト
  4. トラバース中に最初のノードに遭遇した場合、その循環的にリンクされたリスト
1
Asha

これは、さまざまなソリューションをコピーできる素敵なサイトです。

シングルリンクリストのループを見つける

これはそのサイトの勝者です

// Best solution
function boolean hasLoop(Node startNode){
  Node slowNode = Node fastNode1 = Node fastNode2 = startNode;
  while (slowNode && fastNode1 = fastNode2.next() && fastNode2 = fastNode1.next()){
    if (slowNode == fastNode1 || slowNode == fastNode2) return true;
    slowNode = slowNode.next();
  }
  return false;
}

この解決策は、1967年にロバートW.フロイドによって「非決定論的アルゴリズム」で発表された「フロイドの循環発見アルゴリズム」です。「亀と野ウサギアルゴリズム」とも呼ばれます。

0
Markus Lausberg

1つのノードから開始し、それを記録してから、ヌルポインターまたは開始したノードに到達するまで、リスト全体を反復処理します。

何かのようなもの:

Node start = list->head;
Node temp = start->next;
bool circular = false;
while(temp != null && temp != start)
{
   if(temp == start)
   {
     circular = true;
     break;
   }
   temp = temp->next;
}
return circular

これはO(n)です。これは、単一リンクリストで取得できる最高のものです(私が間違っている場合は修正してください)。

または、リスト内のサイクル(中央など)を見つけるには、次のようにします。

Node[] array; // Use a vector or ArrayList to support dynamic insertions
Node temp = list->head;
bool circular = false;
while(temp != null)
{
   if(array.contains(temp) == true)
   {
     circular = true;
     break;
   }
   array.insert(temp);
   temp = temp->next;
}
return circular

動的配列の挿入時間のため、これは少し遅くなります。

0
samoz

これを試して

/* Link list Node */

struct Node {int data; struct Node * next;};

/ *この関数は、リンクリストが循環している場合はtrueを返し、それ以外の場合はfalseを返します。 */bool isCircular(struct Node * head){//(head == NULL)がtrueを返す場合、空のリンクリストは循環です。

// Next of head
struct Node *node = head->next;

// This loop would stope in both cases (1) If
// Circular (2) Not circular
while (node != NULL && node != head)
   node = node->next;

// If loop stopped because of circular
// condition
return (node == head);

}

0
Dwivedi Ji

@samozは私の見解では答えを持っています!疑似コードがありません。のようなものになります

yourlistはリンクされたリストです

allnodes = hashmap
while yourlist.hasNext()
   node = yourlist.next()
   if(allnodes.contains(node))
      syso "loop found"
      break;
   hashmap.add(node)

申し訳ありませんが、コードは非常に疑似的です(より多くのスクリプトを実行してからJava最近))

0
leo

ループから終了することはありません。次の解決策でも実行できます。

bool hasCircle(List l)
{
   Iterator i = l.begin(), j = l.begin();
   while (true) {
      // increment the iterators, if either is at the end, you're done, no circle
      if (i.hasNext())  i = i.next(); else return false;

      // second iterator is travelling twice as fast as first
      if (j.hasNext())  j = j.next(); else return false;
      if (j.hasNext())  j = j.next(); else return false;

      // this should be whatever test shows that the two
      // iterators are pointing at the same place
      if (i.getObject() == j.getObject()) { 
          return true;
      } 
      if(i.next()==j)
          break;
    }
}
0
ajay