web-dev-qa-db-ja.com

リンクリストに2つのメモリロケーションのみを使用するサイクルがあるかどうかを判断する方法

誰もが、リストをたどるのに2つの変数のみを使用してリンクリストがループするかどうかを見つけるアルゴリズムを知っていますか。オブジェクトのリンクリストがあるとします。どのタイプのオブジェクトでもかまいません。 1つの変数にリンクリストのヘッドへのポインターがあり、リストを走査するために他の1つの変数のみが与えられます。

したがって、私の計画は、ポインター値を比較して、同じポインターがあるかどうかを確認することです。リストのサイズは有限ですが、巨大な場合があります。両方の変数を先頭に設定してから、他の変数でリストをトラバースし、常に他の変数と等しいかどうかを確認できますが、ループにヒットしても抜けることはありません。リストを走査してポインタ値を比較するさまざまな速度に関係していると考えています。何かご意見は?

44
jeffD

Floyd's Cycle-Finding AlgorithmakaThe Tortoise and the Hare Algorithmを使用することをお勧めします。それはO(n)複雑さを持ち、あなたの要件に合うと思います。

サンプルコード:

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;
}

ウィキペディアの詳細: フロイドのサイクル発見アルゴリズム

46

カメとウサギ アルゴリズムを使用できます。

ウィキペディアにも説明があり、「 フロイドのサイクル発見アルゴリズム 」または「カメとウサギ」と呼ばれています

17
martinus

絶対に。実際、1つの解決策は、両方のポインターを使用してリストを走査し、一方を他方の2倍の速度で移動させることです。

リストの任意の場所を指す「遅い」ポインターと「速い」ポインターで開始します。横断ループを実行します。いつでも「高速」ポインターが低速ポインターと一致するようになった場合、循環リンクリストがあります。

int *head = list.GetHead();
if (head != null) {
    int *fastPtr = head;
    int *slowPtr = head;

    bool isCircular = true;

    do 
    {
        if (fastPtr->Next == null || fastPtr->Next->Next == null) //List end found
        {
            isCircular = false;
            break;
        }

        fastPtr = fastPtr->Next->Next;
        slowPtr = slowPtr->Next;
    } while (fastPtr != slowPtr);

    //Do whatever you want with the 'isCircular' flag here
}
9

私はこれを自分で解決しようとしましたが、別の(効率は劣りますが、依然として最適な)ソリューションを見つけました。

このアイデアは、一方向にリンクされたリストを線形時間で反転させることに基づいています。これは、リストを反復処理する各ステップで2つのスワップを実行することで実行できます。 qが前の要素(最初はnull)で、pが現在の要素である場合、swap(q、p-> next)swap(p、q)はリンクを逆にし、2つのポインターを同時に進めます。スワップは、XORを使用して行うことができ、3番目のメモリ位置を使用する必要がなくなります。

リストにサイクルがある場合、反復中のある時点で、ポインターが既に変更されたノードに到達します。どのノードであるかを知ることはできませんが、繰り返しを続け、いくつかの要素を2回交換すると、リストの先頭に再び到達します。

リストを2回反転することで、リストの結果は変わらず、リストの元のヘッドに到達したかどうかに基づいてサイクルがあるかどうかを確認できます。

3
user101596
int isListCircular(ListNode* head){
    if(head==NULL)
        return 0;
    ListNode *fast=head, *slow=head;
    while(fast && fast->next){
        if(fast->next->next==slow)
            return 1;
        fast=fast->next->next;
        slow=slow->next;
    }
    return 0;
}
2
rajya vardhan
boolean findCircular(Node *head)
{
    Node *slower, * faster;
    slower = head;
    faster = head->next;
    while(true) {
        if ( !faster || !faster->next)
            return false;
        else if (faster == slower || faster->next == slower)
            return true;
        else
            faster = faster->next->next;
    }
}
1
user5297378

この問題を次のステップに進めるには、サイクルを特定します(つまり、サイクルが存在するだけでなく、リスト内のどこにあるのか)。 Tortoise and Hareアルゴリズムも同じように使用できますが、リストの先頭を常に追跡する必要があります。このアルゴリズムの実例は here にあります。

0
ND_27