Круговые связанные списки
Все фрагменты кода в этой статье доступны в моем репозитории: Github: Saaaaaad3
В этой статье мы обратимся к основам Java, чтобы узнать о структуре данных, поскольку Java, кажется, является одним из самых популярных языков, когда речь идет о изучении структур данных.
Предпосылки:
Односвязные списки: Link
Изучив односвязные и двусвязные списки, мы перейдем к другому типу связанных списков, хотя он используется не так часто, как два других, но о нем полезно знать. Циклические связанные списки — это именно то, что следует из названия, они являются циклическими в том смысле, что следующий конец указывает на узел списка, а не указывает на нуль.
Он не вводит новый указатель ни на что, это то же самое, что и односвязный список. Мы, конечно же, можем сделать двойной список циклическим связным списком.
Функция отображения, но с ограничениями!
В нециклическом связанном списке выполнение функции отображения происходит следующим образом:
· Проверьте, является ли заголовок нулевым или нет.
· Сделать временный головной узел.
· Итерировать до тех пор, пока не встретится нуль (следующий хвост).
Но, как объяснялось ранее, следующий указатель хвоста указывает на другой узел нашего списка. Это приведет к тому, что цикл функции отображения превратится в бесконечный или бесконечный цикл, так как наш временный указатель головы никогда не столкнется с нулевым значением.
Чтобы избежать такого сценария бесконечного цикла, мы можем добавить простое ограничение на то, сколько раз будет выполняться цикл while.
· Переменная count будет хранить количество итераций
· Добавить размер списка и max (принимается как параметр). Это гарантирует, что, ПО КРАЙНЕЙ МЕРЕ, полный список повторен.
Запускайте цикл while до тех пор, пока не встретится нуль (в случае, если список не круговой) и значение счетчика не станет меньше size+max.
public void DisplayWithLimit(int max){ if(head == null){ System.out.println("List is Empty"); return; } Node tempNode = head; int count = 0; while(tempNode != null && size + max > count){ if(tempNode == tail){ System.out.print("'"+tempNode.value+"'" + "->"); } else { System.out.print(tempNode.value + "->"); } tempNode = tempNode.next; count++; } System.out.print("End"); System.out.println(); }
Проверка цикла
Прежде чем мы сможем работать с циклическими связанными списками, важно знать, является ли список циклическим или нет.
Для этого мы можем использовать алгоритм поиска цикла Флойда или известный как алгоритм зайца-черепахи.
· В этом подходе у нас есть два указателя, скажем, FastPntr и SlowPntr
· FastPntr перемещается, пропуская 1 узел, а SlowPntr перемещается от узла к узлу.
· Если существует цикл, то обе точки со временем встретятся в общем узле.
· Если оба указателя находятся на одном и том же узле, мы можем заключить, что данный связанный список действительно является круговым связанным списком.
· Если каким-то образом FastPntr равен нулю, мы можем сказать, что данный связанный список не является круговым связанным списком.
public boolean CycleExists() { if (head == null) { System.out.println("List is Empty"); return false; } return PointersMeet(head) != null; } private Node PointersMeet(Node head) { Node fastPtnr = head; Node slowPtnr = head; do { if (fastPtnr.next == null || fastPtnr.next.next == null) { return null; } slowPtnr = slowPtnr.next; fastPtnr = fastPtnr.next.next; } while (slowPtnr != fastPtnr); return slowPtnr; }
Создать цикл в нециклическом связанном списке
Добавить цикл в некруговой связанный список довольно просто, учитывая условие, что вы поддерживаете хвост в коде. Мы можем просто указать следующий указатель хвоста на узел с индексом, указанным в параметре.
На всякий случай, если вы не поддерживаете хвост (хотя зачем вам это делать?), мы можем просто повторять, пока не встретим нуль. Таким образом, у нас будет последний узел, и мы укажем следующий из наших последних узлов на индекс узла.
public void makeCycleAt(int index){ if(head == null){ System.out.println("List is Empty"); return; } if(CycleExists()){ System.out.println("Cycle Already Exists"); return; } int count = 0; Node tempNode = head; while(count < index){ tempNode = tempNode.next; count++; } tail.next = tempNode; cycleHead = tempNode; }
Небольшой совет: точно так же, как мы поддерживаем указатели на начало и конец, мы также можем добавить CycleHead, который будет содержать узел, с которого начинается цикл. Это улучшит временную сложность функций, которым может потребоваться начало цикла, поскольку нам не нужно будет запускать другую функцию с циклом, чтобы определить начало цикла.
Чтобы разорвать цикл, просто сделайте так, чтобы следующий указатель хвоста указывал на ноль.
tail.next = null;
Эта часть была довольно короткой, так как большинство функций такие же, как и в односвязном списке. Тем не менее, с помощью cycleHead можно создать гораздо больше функций, которые мы видели в последнем разделе статьи.
Это были основы того, что такое круговой связанный список, чтобы помочь вам понять его суть.
Все фрагменты кода в этой статье доступны в моем репозитории: Github: Saaaaaad3