Як виявити цикл у пов'язаному списку?


434

Скажімо, у вас на Java пов'язана структура списку. Складається з Вузлів:

class Node {
    Node next;
    // some user data
}

і кожен Вузол вказує на наступний вузол, за винятком останнього Вузла, який має нуль для наступного. Скажімо, існує можливість, що список може містити цикл - тобто кінцевий Node замість нуля має посилання на один із вузлів у списку, який був до нього.

Який найкращий спосіб написання

boolean hasLoop(Node first)

що повернеться, trueякщо даний вузол є першим у списку з циклом, falseінакше? Як ви могли написати так, щоб це займало постійну кількість місця та розумну кількість часу?

Ось малюнок того, як виглядає список із циклом:

alt текст


50
Нічого б ... Я хотів би попрацювати на цього роботодавця finite amount of space and a reasonable amount of time?:)
кодифікація

10
@SLaks - циклу не потрібен цикл назад до першого вузла. Він може петлювати назад на половину.
джуджума

109
Відповіді нижче варто прочитати, але такі питання інтерв'ю жахливі. Ви або знаєте відповідь (тобто ви бачили варіант алгоритму Флойда), або не знаєте, і це нічого не робить для перевірки ваших міркувань чи спроможності проектувати.
GaryF

3
Якщо чесно, більшість "знаючих алгоритмів" виглядає так - якщо ви не займаєтесь дослідженнями рівня!
Ларрі

12
@GaryF І все ж було б виявити, що знати, що вони робитимуть, коли не знають відповіді. Наприклад, які кроки вони б зробили, з ким би вони працювали, що б вони зробили, щоб подолати брак знань алгоритмів?
Кріс Найт

Відповіді:


538

Ви можете використовувати алгоритм пошуку циклу Флойда , також відомий як алгоритм черепахи та зайця .

Ідея полягає у тому, щоб мати два посилання на список і переміщувати їх з різною швидкістю . Перемістіть один вперед по 1вузлу, а другий - по 2вузлах.

  • Якщо зв'язаний список має цикл, вони обов'язково зустрінуться.
  • Інакше будь-яка з двох посилань (або їх next) стане null.

Функція Java, що реалізує алгоритм:

boolean hasLoop(Node first) {

    if(first == null) // list does not exist..so no loop either
        return false;

    Node slow, fast; // create two references.

    slow = fast = first; // make both refer to the start of the list

    while(true) {

        slow = slow.next;          // 1 hop

        if(fast.next != null)
            fast = fast.next.next; // 2 hops
        else
            return false;          // next node null => no loop

        if(slow == null || fast == null) // if either hits null..no loop
            return false;

        if(slow == fast) // if the two ever meet...we must have a loop
            return true;
    }
}

29
Також потрібно зробити нульову перевірку fast.nextперед тим, як nextзнову зателефонувати :if(fast.next!=null)fast=fast.next.next;
cmptrgeekken

12
вам слід перевірити не тільки (повільно == швидкий), але: (повільний == швидкий || повільний.наступний == швидкий), щоб запобігти стрибку швидкого через повільний
Олег Разгуляєв,

13
я помилявся: швидкий не може перескочити повільно, тому що для перестрибування повільно на наступному кроці швидкий повинен мати таку ж позицію, як і повільний :)
Олег Разгуляєв,

4
Перевірка на повільний == null є зайвою, якщо у списку немає лише одного вузла. Ви також можете позбутися одного дзвінка на Node.next. Ось простіша та швидша версія циклу: pastie.org/927591
Kay

22
Ви дійсно повинні цитувати свої посилання. Цей алгоритм був винайдений Робертом Флойдом у 60-х, він відомий як алгоритм пошуку циклу Флойда, він же. Алгоритм черепахи та зайця.
Джошперрі

127

Ось уточнення рішення Fast / Slow, яке правильно обробляє списки непарних довжин та покращує чіткість.

boolean hasLoop(Node first) {
    Node slow = first;
    Node fast = first;

    while(fast != null && fast.next != null) {
        slow = slow.next;          // 1 hop
        fast = fast.next.next;     // 2 hops 

        if(slow == fast)  // fast caught up to slow, so there is a loop
            return true;
    }
    return false;  // fast reached null, so the list terminates
}

2
Приємно і лаконічно. Цей код можна оптимізувати, перевіривши, чи повільний == швидкий || (fast.next! = null && slow = fast.next); :)
arachnode.net

11
@ arachnode.net Це не оптимізація. Якщо slow == fast.nextтоді slowдорівнюватиме fastна наступну ітерацію; це економить лише одну ітерацію максимум за рахунок додаткового тесту на кожну ітерацію.
Джейсон C

@ ana01 slowне може ставати недійсним раніше, fastоскільки він слідує тим самим шляхом посилань (якщо тільки у вас не відбувається одночасна зміна списку, і в цьому випадку всі ставки відключені).
Дейв Л.

Як цікаво, як це працює для непарних чисел? Ви все ще не можете передавати черепаху у списки, пов’язані з непарною довжиною?
theGreenCabbage

1
@theGreenCabbage Кожна ітерація петлі заєць стає на 1 крок попереду черепахи. Отже, якщо заєць відстає на 3 кроки, то для наступної ітерації потрібно два хмелі, а черепаха - один скачок, а тепер заєць відстає на 2 кроки. Після наступної ітерації заєць відстає на 1 стрибок, і тоді його точно наздогнали. Якщо заєць взяв 3 хмелі, поки черепаха взяв один, то він міг би пропустити, тому що набирав би по 2 кожного разу, але оскільки він набирає лише по 1 кожній ітерації, він не може пропустити повз.
Дейв Л.

52

Краще алгоритму Флойда

Річард Брент описав альтернативний алгоритм виявлення циклу , який дуже схожий на зайця та черепаху [цикл Флойда], за винятком того, що повільний вузол тут не рухається, а пізніше "телепортується" в положення швидкого вузла при фіксованому інтервали.

Опис доступний тут: http://www.siafoo.net/algorithm/11 Брент стверджує, що його алгоритм на 24-36% швидший, ніж алгоритм циклу Флойда. O (n) часова складність, O (1) просторова складність.

public static boolean hasLoop(Node root){
    if(root == null) return false;

    Node slow = root, fast = root;
    int taken = 0, limit = 2;

    while (fast.next != null) {
        fast = fast.next;
        taken++;
        if(slow == fast) return true;

        if(taken == limit){
            taken = 0;
            limit <<= 1;    // equivalent to limit *= 2;
            slow = fast;    // teleporting the turtle (to the hare's position) 
        }
    }
    return false;
}

Ця відповідь приголомшлива!
valin077

1
Дуже сподобалась ваша відповідь, включив її у мій блог - k2code.blogspot.in/2010/04/… .
kinshuk4

Навіщо потрібно перевіряти slow.next != null? Наскільки я бачу, slowце завжди позаду або дорівнює fast.
TWiStErRob

Це я робив давно, коли почав вивчати алгоритми. Редагував код. Дякую :)
Ashok Bijoy Debnath

50

Альтернативне рішення для Черепахи та Кролика, не дуже приємне, оскільки я тимчасово змінюю список:

Ідея полягає в тому, щоб пройтися за списком і повернути його назад, як ви йдете. Потім, коли ви вперше дістаєтесь до вже відвіданого вузла, його наступний вказівник буде вказувати "назад", викликаючи повторення повторення first, де воно закінчується.

Node prev = null;
Node cur = first;
while (cur != null) {
    Node next = cur.next;
    cur.next = prev;
    prev = cur;
    cur = next;
}
boolean hasCycle = prev == first && first != null && first.next != null;

// reconstruct the list
cur = prev;
prev = null;
while (cur != null) {
    Node next = cur.next;
    cur.next = prev;
    prev = cur;
    cur = next;
}

return hasCycle;

Код тесту:

static void assertSameOrder(Node[] nodes) {
    for (int i = 0; i < nodes.length - 1; i++) {
        assert nodes[i].next == nodes[i + 1];
    }
}

public static void main(String[] args) {
    Node[] nodes = new Node[100];
    for (int i = 0; i < nodes.length; i++) {
        nodes[i] = new Node();
    }
    for (int i = 0; i < nodes.length - 1; i++) {
        nodes[i].next = nodes[i + 1];
    }
    Node first = nodes[0];
    Node max = nodes[nodes.length - 1];

    max.next = null;
    assert !hasCycle(first);
    assertSameOrder(nodes);
    max.next = first;
    assert hasCycle(first);
    assertSameOrder(nodes);
    max.next = max;
    assert hasCycle(first);
    assertSameOrder(nodes);
    max.next = nodes[50];
    assert hasCycle(first);
    assertSameOrder(nodes);
}

Чи працює реверс правильно, коли цикл вказує на будь-який вузол, окрім першого? Якщо початковий зв’язаний список такий (1-> 2-> 3-> 4-> 5-> 2 (з циклом від 5 до 2), то перевернутий список має вигляд 1-> 2 <-3 <-4 <-5? І якщо це буде зворотне, остаточний реконструйований список буде розкручений?
Зеніл

1
@Zenil: Тому я написав останній тестовий зразок, де останній вузол вказує на середину списку. Якщо реконструкція не спрацювала, тест не вдався б. Про ваш приклад: повернення 1-> 2-> 3-> 5-> 2 буде 1-> 2-> 5-> 4-> 3-> 2, оскільки цикл зупиняється лише один раз у кінці списку було досягнуто не тоді, коли був досягнутий кінець циклу (який ми не можемо легко виявити).
meriton

28

Черепаха та заєць

Погляньте на алгоритм rho Полларда . Це не зовсім та сама проблема, але, можливо, ви зрозумієте логіку від неї та застосуєте її для пов'язаних списків.

(якщо ви лінь, можете просто перевірити виявлення циклу - перевірити частину про черепаху та зайця.)

Це вимагає лише лінійного часу та 2 додаткових покажчиків.

На Java:

boolean hasLoop( Node first ) {
    if ( first == null ) return false;

    Node turtle = first;
    Node hare = first;

    while ( hare.next != null && hare.next.next != null ) {
         turtle = turtle.next;
         hare = hare.next.next;

         if ( turtle == hare ) return true;
    }

    return false;
}

(Більшість рішень не перевіряють nextі next.nextна нулі, і тому, що черепаха завжди позаду, вам не доведеться перевіряти її на нуль - заєць це вже робив.)


13

Користувач unicornaddict має приємний алгоритм, наведений вище, але, на жаль, він містить помилку для нешифрованих списків непарної довжини> = 3. Проблема в тому, що fastможе "застрягнути" безпосередньо перед кінцем списку,slow наздоганяє його та цикл (помилково) виявлений.

Ось виправлений алгоритм.

static boolean hasLoop(Node first) {

    if(first == null) // list does not exist..so no loop either.
        return false;

    Node slow, fast; // create two references.

    slow = fast = first; // make both refer to the start of the list.

    while(true) {
        slow = slow.next;          // 1 hop.
        if(fast.next == null)
            fast = null;
        else
            fast = fast.next.next; // 2 hops.

        if(fast == null) // if fast hits null..no loop.
            return false;

        if(slow == fast) // if the two ever meet...we must have a loop.
            return true;
    }
}

10

У цьому контексті повсюдно є навантаження на текстові матеріали. Я просто хотів опублікувати схематичне зображення, яке дійсно допомогло мені зрозуміти концепцію.

Коли швидко і повільно зустрічаються в точці p,

Відстань, пройдена швидким = a + b + c + b = a + 2b + c

Пройдена відстань повільно = a + b

Оскільки швидкий в 2 рази швидший, ніж повільний. Отже a + 2b + c = 2 (a + b) , тоді отримуємо a = c .

Отже, коли інший повільний покажчик знову біжить від голови до q , в той же час швидкий покажчик буде працювати від p до q , тому вони зустрічаються в точці q разом.

введіть тут опис зображення

public ListNode detectCycle(ListNode head) {
    if(head == null || head.next==null)
        return null;

    ListNode slow = head;
    ListNode fast = head;

    while (fast!=null && fast.next!=null){
        fast = fast.next.next;
        slow = slow.next;

        /*
        if the 2 pointers meet, then the 
        dist from the meeting pt to start of loop 
        equals
        dist from head to start of loop
        */
        if (fast == slow){ //loop found
            slow = head;
            while(slow != fast){
                slow = slow.next;
                fast = fast.next;
            }
            return slow;
        }            
    }
    return null;
}

2
Малюнок вартує більше тисяч слів. Дякую за акуратне та просте пояснення!
Calios

1
Найкраще пояснення в Інтернеті. Просто додамо, що це доводить, що швидкий і повільний покажчик збігається через лінійний час
VarunPandey

якщо aбільша довжина циклу, тоді швидкий зробить декілька циклів, а формула distance (fast) = a + b + b + cзміниться до a + (b+c) * k + bвведення додаткового параметра, kякий рахує кількість лопсів, зроблених швидким.
Бен

9

Алгоритм

public static boolean hasCycle (LinkedList<Node> list)
{
    HashSet<Node> visited = new HashSet<Node>();

    for (Node n : list)
    {
        visited.add(n);

        if (visited.contains(n.next))
        {
            return true;
        }
    }

    return false;
}

Складність

Time ~ O(n)
Space ~ O(n)

Як складність простору O (2n)?
Програміст345

@ user3543449 Ви праві, це має бути просто n, виправлено
Khaled.K

1
Це насправді час ~ O (n ^ 2), оскільки кожен містить перевірку наявності ArrayList займає O (n), і є O (n) з них. Використовуйте HashSet замість лінійного часу.
Дейв Л.

3
Це не тест на цикли, а на повторювані значення з використанням елементів equalsта hashCode. Це не одне й те саме. І це відношення nullна останньому елементі. І запитання нічого не сказало про збереження вузлів у LinkedList.
Лій

2
@Lii це псевдокод, це не код Java, тому я назвав його алгоритмом
Khaled.K

8

Наступний може бути не найкращим методом - це O (n ^ 2). Однак він повинен слугувати для виконання роботи (зрештою).

count_of_elements_so_far = 0;
for (each element in linked list)
{
    search for current element in first <count_of_elements_so_far>
    if found, then you have a loop
    else,count_of_elements_so_far++;
}

Як би ви знали, скільки елементів у списку потрібно зробити для ()?
Джетро Ларсон

@JethroLarson: Останній вузол у зв’язаному списку вказує на відому адресу (у багатьох реалізаціях це NULL). Закінчіть цикл for, коли буде досягнута відома адреса.
Спаркі

3
public boolean hasLoop(Node start){   
   TreeSet<Node> set = new TreeSet<Node>();
   Node lookingAt = start;

   while (lookingAt.peek() != null){
       lookingAt = lookingAt.next;

       if (set.contains(lookingAt){
           return false;
        } else {
        set.put(lookingAt);
        }

        return true;
}   
// Inside our Node class:        
public Node peek(){
   return this.next;
}

Вибачте мене за своє незнання (я все ще досить новачок у Java та програмуванні), але чому б вищезгадане не працювало?

Я думаю, це не вирішує питання постійного простору ... але воно принаймні потрапить туди в розумний час, правильно? Він займе лише простір зв'язаного списку плюс простір набору з п елементами (де n - кількість елементів у зв'язаному списку або кількість елементів, поки не досягне циклу). І на час, аналіз на найгірший випадок, я думаю, запропонував би O (nlog (n)). Погляди на SortedSet для містить () - це журнал (n) (перевірити javadoc, але я впевнений, що базовою структурою TreeSet є TreeMap, у свою чергу це червоно-чорне дерево), і в гіршому випадку (немає циклів, або цикл у самому кінці), доведеться робити n огляди.


2
Так, рішення з деяким набором працює добре, але вимагає місця, пропорційного розміру списку.
джуджума

3

Якщо нам дозволяють вбудовувати клас Node, я вирішував би проблему, як це я реалізував нижче. hasLoop()працює в O (n) час і займає лише простір counter. Це здається відповідним рішенням? Або є спосіб це зробити без вбудовування Node? (Очевидно, що в реальній реалізації було б більше методів, наприклад RemoveNode(Node n), тощо)

public class LinkedNodeList {
    Node first;
    Int count;

    LinkedNodeList(){
        first = null;
        count = 0;
    }

    LinkedNodeList(Node n){
        if (n.next != null){
            throw new error("must start with single node!");
        } else {
            first = n;
            count = 1;
        }
    }

    public void addNode(Node n){
        Node lookingAt = first;

        while(lookingAt.next != null){
            lookingAt = lookingAt.next;
        }

        lookingAt.next = n;
        count++;
    }

    public boolean hasLoop(){

        int counter = 0;
        Node lookingAt = first;

        while(lookingAt.next != null){
            counter++;
            if (count < counter){
                return false;
            } else {
               lookingAt = lookingAt.next;
            }
        }

        return true;

    }



    private class Node{
        Node next;
        ....
    }

}

1

Ви навіть можете це робити в постійний O (1) час (хоча це не буде дуже швидким чи ефективним): Існує обмежена кількість вузлів, на яких пам'ять комп'ютера може вмістити, скажімо, N записів. Якщо ви переходите більше N записів, то у вас є цикл.


Це не O (1), цей алгоритм не має значущої складності у часі у позначенні big-O. Позначення big-O лише повідомляє про продуктивність у ліміті, оскільки розмір вводу йде до нескінченності. Так що, якщо ваш алгоритм грунтується на припущенні , що не мають ніяких списків з більш ніж N елементи для деяких великих N, межа виконання як розмір списку наближається до нескінченності не визначена. Отже, складність не є "O (що-небудь)".
fgp

1
 // To detect whether a circular loop exists in a linked list
public boolean findCircularLoop() {
    Node slower, faster;
    slower = head;
    faster = head.next; // start faster one node ahead
    while (true) {

        // if the faster pointer encounters a NULL element
        if (faster == null || faster.next == null)
            return false;
        // if faster pointer ever equals slower or faster's next
        // pointer is ever equal to slower then it's a circular list
        else if (slower == faster || slower == faster.next)
            return true;
        else {
            // advance the pointers
            slower = slower.next;
            faster = faster.next.next;
        }
    }
}

1
boolean hasCycle(Node head) {

    boolean dec = false;
    Node first = head;
    Node sec = head;
    while(first != null && sec != null)
    {
        first = first.next;
        sec = sec.next.next;
        if(first == sec )
        {
            dec = true;
            break;
        }

    }
        return dec;
}

Використовуйте вищевказану функцію для виявлення циклу в пов'язаному списку в Java.


2
Майже те саме, що моя відповідь вище, але має питання. Він кине NullPointerException для списків із списками непарної довжини (без циклів). Наприклад, якщо head.next є null, то sec.next.next кине NPE.
Дейв Л.

1

Виявлення циклу у зв’язаному списку можна здійснити одним із найпростіших способів, що призводить до складності O (N) за допомогою хешмапу або O (NlogN) з використанням підходу на основі сортування.

Під час переходу списку, починаючи з голови, створіть відсортований список адрес. Коли ви вставляєте нову адресу, перевірте, чи адреса вже є у відсортованому списку, який займає складність O (logN).


Складність цього підходу становить O (N log N)
fgp

0

Я не бачу жодного способу змусити це зайняти фіксовану кількість часу або місця, і те й інше збільшиться з розміром списку.

Я б скористався IdentityHashMap (враховуючи, що ще немає IdentityHashSet) і зберігатиму кожен вузол у карті. Перед тим, як вузол буде збережено, ви б викликали містив на ньому містять Ключ. Якщо Вузол вже існує, у вас є цикл.

ItentityHashMap використовує == замість .equals, щоб ви перевіряли, де об’єкт знаходиться в пам'яті, а не в тому, що він має однаковий вміст.


3
Це, звичайно, неможливо зайняти фіксовану кількість часу, оскільки в самому кінці списку може бути цикл, тому весь список потрібно відвідати. Однак алгоритм швидкого / повільного демонструє рішення, використовуючи фіксовану кількість пам'яті.
Дейв Л.

Чи не йдеться про його асимптотичну поведінку, тобто це лінійне O (n), де n - довжина списку. Виправлено буде O (1)
Марк Робсон

0

Мені може бути страшно пізно і новим обробляти цю нитку. Але все ж..

Чому нахил адреси вузла та вказаного "наступного" вузла зберігається в таблиці

Якби ми могли так скласти таблицю

node present: (present node addr) (next node address)

node 1: addr1: 0x100 addr2: 0x200 ( no present node address till this point had 0x200)
node 2: addr2: 0x200 addr3: 0x300 ( no present node address till this point had 0x300)
node 3: addr3: 0x300 addr4: 0x400 ( no present node address till this point had 0x400)
node 4: addr4: 0x400 addr5: 0x500 ( no present node address till this point had 0x500)
node 5: addr5: 0x500 addr6: 0x600 ( no present node address till this point had 0x600)
node 6: addr6: 0x600 addr4: 0x400 ( ONE present node address till this point had 0x400)

Отже, формується цикл.


Ваше рішення не минає вимоги "постійної кількості місця".
Арно

0

Ось мій код для запуску.

Що я зробив, це відновлювати зв'язаний список за допомогою трьох тимчасових вузлів (складність простору O(1) ), які відслідковують посилання.

Цікавий факт його виконання - це допомогти виявити цикл у пов'язаному списку, оскільки, рухаючись вперед, ви не очікуєте повернення до початкової точки (кореневий вузол), і один з тимчасових вузлів повинен перейти до нуля, якщо ви не мати цикл, що означає, що він вказує на кореневий вузол.

Часова складність цього алгоритму є, O(n)а просторова складністьO(1) .

Ось клас класу для пов'язаного списку:

public class LinkedNode{
    public LinkedNode next;
}

Ось основний код із простим тестовим випадком із трьох вузлів, який останній вузол вказує на другий вузол:

    public static boolean checkLoopInLinkedList(LinkedNode root){

        if (root == null || root.next == null) return false;

        LinkedNode current1 = root, current2 = root.next, current3 = root.next.next;
        root.next = null;
        current2.next = current1;

        while(current3 != null){
            if(current3 == root) return true;

            current1 = current2;
            current2 = current3;
            current3 = current3.next;

            current2.next = current1;
        }
        return false;
    }

Ось простий тестовий випадок з трьох вузлів, що останній вузол вказує на другий вузол:

public class questions{
    public static void main(String [] args){

        LinkedNode n1 = new LinkedNode();
        LinkedNode n2 = new LinkedNode();
        LinkedNode n3 = new LinkedNode();
        n1.next = n2;
        n2.next = n3;
        n3.next = n2;

        System.out.print(checkLoopInLinkedList(n1));
    }
}

0

Цей код оптимізований і дасть результат швидше, ніж той, який обраний як найкраща відповідь. Цей код рятує від вступу в дуже тривалий процес переслідування вказівника прямого і зворотного вузлів, який відбудеться в наступному випадку, якщо ми будемо слідувати кращому метод 'відповідь'. Перегляньте наступне, і ви зрозумієте, що я намагаюся сказати. Потім подивіться на проблему за допомогою наведеного нижче методу та виміряйте «ні». кроків, зроблених для пошуку відповіді.

1-> 2-> 9-> 3 ^ -------- ^

Ось код:

boolean loop(node *head)
{
 node *back=head;
 node *front=head;

 while(front && front->next)
 {
  front=front->next->next;
  if(back==front)
  return true;
  else
  back=back->next;
 }
return false
}

Ви впевнені, що це дає правильний результат у всіх ситуаціях? Якщо запустити цей алгоритм у списку 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 3 -> ..., я вірю, що він поверне 4 як голова, тоді як ти хотів 3.
Sunreef

Питання полягає в тому, щоб дізнатися, чи існує цикл чи ні. У цьому випадку, так, це питання буде спрацьовувати нормально і отримає бажаний логічний результат для випадку. Якщо ви хочете точний вузол, з якого починається цикл, ми будемо потрібно додати дещо більше до коду. Але що стосується створення результату, це дасть швидший висновок.
Сартак Мехра

Ви неправильно не прочитали питання: Який найкращий спосіб написання, boolean hasLoop(Node first)який би повернув істину, якщо даний Вузол є першим у списку з циклом, а помилковим - інакше?
Sunreef

Ось сухий прогін для вашого списку. Перше значення означає задній вказівник, а друга частина означає вказівник вперед. (1,1) - (1,3) - (2,3) - (2,5) - (3,5) - (3,7) - (4,7) - (4,4).
Сартак Мехра

Насправді я зараз розумію, що існує два способи зрозуміти питання (або, принаймні, я бачу два різних тлумачення). Ваш алгоритм правильний, якщо ви просто шукаєте, чи є цикл. Але я подумав, що питання задає питання, звідки починається цикл.
Sunreef

0

Ось моє рішення в java

boolean detectLoop(Node head){
    Node fastRunner = head;
    Node slowRunner = head;
    while(fastRunner != null && slowRunner !=null && fastRunner.next != null){
        fastRunner = fastRunner.next.next;
        slowRunner = slowRunner.next;
        if(fastRunner == slowRunner){
            return true;
        }
    }
    return false;
}

0

Ви також можете використовувати алгоритм черепахи Флойда, як це запропоновано у відповідях вище.

Цей алгоритм може перевірити, чи є в окремо пов'язаному списку закритий цикл. Цього можна досягти, повторивши список з двома вказівниками, які рухатимуться з різною швидкістю. Таким чином, якщо є цикл, два покажчики зустрінуться в якийсь момент майбутнього.

Будь ласка, не соромтеся ознайомитись із моїм дописом у структурі даних пов'язаних списків, де я також включив фрагмент коду з реалізацією вищезазначеного алгоритму на мові Java.

З повагою,

Андреас (@xnorcode)


0

Ось рішення для виявлення циклу.

public boolean hasCycle(ListNode head) {
            ListNode slow =head;
            ListNode fast =head;

            while(fast!=null && fast.next!=null){
                slow = slow.next; // slow pointer only one hop
                fast = fast.next.next; // fast pointer two hops 

                if(slow == fast)    return true; // retrun true if fast meet slow pointer
            }

            return false; // return false if fast pointer stop at end 
        }

0

// пов'язаний список функції пошуку циклу

int findLoop(struct Node* head)
{
    struct Node* slow = head, *fast = head;
    while(slow && fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
            return 1;
    }
 return 0;
}

-1

Цей підхід має простір, але більш просту реалізацію:

Цикл можна ідентифікувати, зберігаючи вузли на карті. І перед тим, як поставити вузол; перевірте, чи вузол вже існує. Якщо на карті вже існує вузол, то це означає, що пов'язаний список має цикл.

public boolean loopDetector(Node<E> first) {  
       Node<E> t = first;  
       Map<Node<E>, Node<E>> map = new IdentityHashMap<Node<E>, Node<E>>();  
       while (t != null) {  
            if (map.containsKey(t)) {  
                 System.out.println(" duplicate Node is --" + t  
                           + " having value :" + t.data);  

                 return true;  
            } else {  
                 map.put(t, t);  
            }  
            t = t.next;  
       }  
       return false;  
  }  

Це не відповідає постійній кількості обмежень місця, наведеній у питанні!
дедек

погодьтеся, що у нього є місце накладних; це ще один підхід до вирішення цієї проблеми. Очевидний підхід - це алгоритм черепахи та впрягання.
rai.skumar

@downvoter, було б корисно, якби ви могли також пояснити причину.
rai.skumar

-2
public boolean isCircular() {

    if (head == null)
        return false;

    Node temp1 = head;
    Node temp2 = head;

    try {
        while (temp2.next != null) {

            temp2 = temp2.next.next.next;
            temp1 = temp1.next;

            if (temp1 == temp2 || temp1 == temp2.next) 
                return true;    

        }
    } catch (NullPointerException ex) {
        return false;

    }

    return false;

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.