Как обнаружить петлю в связанном списке?


434

Скажем, у вас есть структура связанного списка в Java. Он состоит из узлов:

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

и каждый узел указывает на следующий узел, за исключением последнего узла, который имеет нулевое значение для следующего. Скажем, есть вероятность, что список может содержать цикл - то есть конечный узел, вместо нуля, имеет ссылку на один из узлов в списке, который предшествовал ему.

Какой лучший способ написания

boolean hasLoop(Node first)

который вернется, trueесли данный узел является первым из списка с циклом, и в falseпротивном случае? Как вы могли бы написать так, чтобы это занимало постоянное количество места и разумное количество времени?

Вот изображение того, как выглядит список с циклом:

альтернативный текст


50
Ничего себе .. Я хотел бы работать для этого работодателя finite amount of space and a reasonable amount of time?:)
codaddict

10
@SLaks - цикл не обязательно должен возвращаться к первому узлу. Это может вернуться на полпути.
Джуджума

110
Ответы ниже стоит прочитать, но такие вопросы на собеседовании ужасны. Вы либо знаете ответ (т.е. вы видели вариант алгоритма Флойда), либо нет, и он ничего не делает для проверки ваших рассуждений или способности к проектированию.
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
Вы должны проверять не только (медленно == быстро), но и: (медленно == быстро || slow.next == быстро), чтобы не допустить быстрого перехода через медленный
Олег Разгуляев

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

4
Проверка медленного == null является избыточной, если в списке нет только одного узла. Вы также можете избавиться от одного звонка на Node.next. Вот более простая и быстрая версия цикла: pastie.org/927591
Кей Сарраут

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на следующей итерации; он сохраняет только одну итерацию максимум за счет дополнительного теста для каждой итерации.
Джейсон С.

@ 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

Я сделал это очень давно, когда начал изучать алгоритмы. Отредактировал код. Спасибо :)
Ашок Биджой Дебнатх

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, потому что цикл останавливается только один раз в конце списка было достигнуто не тогда, когда был достигнут конец цикла (который мы не можем легко обнаружить).
Меритон

28

Черепаха и заяц

Взгляните на алгоритм ро Полларда . Это не совсем та же проблема, но, возможно, вы поймете логику из нее и примените ее для связанных списков.

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

Это требует только линейного времени и 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

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

Когда быстро и медленно встречаются в точке р,

Расстояние, пройденное быстро = 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он больше, чем длина цикла, тогда fast сделает несколько циклов, и формула distance (fast) = a + b + b + cизменится на a + (b+c) * k + bвведение дополнительного параметра, kкоторый подсчитывает количество циклов, выполненных fast.
Бен

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)?
Programmer345

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

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

@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 элементами (где n - количество элементов в связанном списке или количество элементов, пока он не достигнет цикла). И на время анализ наихудшего случая, я думаю, предложил бы O (nlog (n)). SortedSet ищет для содержащегося () лога (n) (проверьте javadoc, но я уверен, что базовой структурой TreeSet является TreeMap, чье дерево, в свою очередь, красно-черное), и в худшем случае (без циклов, или цикл в самом конце), он должен будет сделать n поиска.


2
Да, решение с каким-то множеством Set работает нормально, но требует пространства, пропорционального размеру списка.
jjujuma

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) с использованием hashmap или O (NlogN) с использованием подхода, основанного на сортировке.

Когда вы просматриваете список, начиная с заголовка, создайте отсортированный список адресов. Когда вы вставляете новый адрес, убедитесь, что этот адрес уже есть в отсортированном списке, что требует сложности O (logN).


Сложность этого apporach составляет O (N log N)
FGP

0

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

Я хотел бы использовать IdentityHashMap (учитывая, что IdentityHashSet еще не существует) и сохранить каждый узел на карте. Перед тем, как сохранить узел, вы должны вызвать для него containsKey. Если узел уже существует, у вас есть цикл.

ItentityHashMap использует == вместо .equals, чтобы вы проверяли, где находится объект в памяти, а не имеет ли он такое же содержимое.


3
Это, конечно, невозможно для этого занять фиксированное количество времени, так как может быть цикл в самом конце списка, поэтому необходимо просмотреть весь список. Однако алгоритм Fast / Slow демонстрирует решение с использованием фиксированного объема памяти.
Дейв Л.

Разве это не относится к его асимптотическому поведению, т. Е. Оно является линейным 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)который бы возвращал значение true, если данный узел является первым в списке с циклом, а в противном случае - false?
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.