链接列表循环检测– Floyd的循环查找算法
在本教程中,我们将讨论一种非常流行的算法,该算法用于检测LinkedList是否具有循环。
称为弗洛伊德(Floyd)的循环查找算法。
LinkedList循环检测
当遍历整个LinkedList时没有NULL时,LinkedList中存在一个循环。
因此,为了检测LinkedList是否存在循环,我们可以遍历LinkedList,并将每个Node(如果已访问第一项)添加到已访问笔记的HashSet中。
到达HashSet中已经存在的节点后,我们就可以知道存在一个循环。
但是这种方法虽然更简单,但会占用另外的空间。
另一种方法是在LinkedList节点数据本身中为访问的节点设置标志。
但是同样,这种方法会占用一些另外的空间。
比我们之前使用的更多。
因此,检测回路的理想方法是使用Floyd的循环查找算法
Floyd的循环查找算法
弗洛伊德(Floyd)的循环查找算法使用两个指针以不同的速度移动。
如果存在一个周期,则两个指针将来都将指向相同的值。
弗洛伊德(Floyd)的循环查找算法类似于乌龟头发的故事。
让我们编写Java程序来创建我们自己的LinkedList类。
MyLinkedList.java
package com.theitroad.linkedlist.reverse; /** * A very simple linked list implementation * Not to use in application, created to show the linked list * operations, hence very minimal features * * @author hyman * */ public class MyLinkedList { public Node head; public static class Node { public Node next; public Object data; public Node(Object data) { this.data = data; next = null; } } }
下列类包含检测LinkedList中的循环的方法。
package com.theitroad.linkedlist.loopDetection; import com.theitroad.linkedlist.reverse.MyLinkedList; import com.theitroad.linkedlist.reverse.MyLinkedList.Node; public class DetectLinkedListLoop { public static void main(String[] args) { MyLinkedList myLinkedList = new MyLinkedList(); myLinkedList.head = new Node(1); myLinkedList.head.next = new Node(2); Node node = myLinkedList.head.next.next = new Node(3); myLinkedList.head.next.next.next = new Node(4); myLinkedList.head.next.next.next.next = new Node(5); myLinkedList.head.next.next.next.next.next = node; System.out.println("Has Loop? " + hasLoop(myLinkedList)); } private static boolean hasLoop(MyLinkedList myLinkedList) { Node head = myLinkedList.head; Node slow = head; Node fast = head; boolean loop = false; while (slow != null && fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; if (slow == fast) { loop = true; break; } } return loop; } }
我们以快慢指针两倍的速度移动了快指针。
如果存在循环,则快指针和慢指针有时会相遇。
其中我们可以结束while语句并返回布尔值标志。
上面的代码返回一个" true"。
检测循环的第一个元素
为了检测循环的起始节点,我们需要在上述算法中增加另一步。
一旦快和慢指针相等。
将其中之一设置回LinkedList头的开头。
以相等的速度移动两个指针。
在它们再次相遇的节点处,将是循环的起始节点。
private static Node startNode(MyLinkedList myLinkedList) { Node head = myLinkedList.head; Node slow = head; Node fast = head; boolean loop = false; while (slow != null && fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; if (slow == fast) { loop = true; break; } } if (loop) { slow = head; while (fast != slow) { fast = fast.next; slow = slow.next; } return fast; } else { return new Node(Integer.MIN_VALUE); } }
在main
中调用上述方法:
System.out.println("Start Node data: " + startNode(myLinkedList).data);
这将返回带有数据的节点3。
循环长度
找到循环的起点后,我们可以其中保留一个指针,并通过节点将另一个指针遍历,同时将长度增加1。
private static int lengthOfLoop(MyLinkedList myLinkedList) { Node head = myLinkedList.head; Node slow = head; Node fast = head; boolean loop = false; while (slow != null && fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; if (slow == fast) { loop = true; break; } } if (loop) { int length = 0; slow = head; while (fast != slow) { fast = fast.next; slow = slow.next; } do { fast = fast.next; length++; } while (fast != slow); return length; } return 0; }
在main
中调用上述方法:
System.out.println("Length of loop: " + lengthOfLoop(myLinkedList));
上例中的循环长度为3。
卸下环
为了删除循环,我们需要在上面的示例中添加另一个步骤:
一旦慢速指针和快速指针位于循环的起始节点,就将快速指针移动一个,直到其下一个指针等于慢速指针为止。
该快速指针的当前位置将是循环的最后一个节点。
我们需要将该节点的" next"设置为null。
private static Node removeLoop(MyLinkedList myLinkedList) { Node head = myLinkedList.head; Node slow = head; Node fast = head; boolean loop = false; while (slow != null && fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; if (slow == fast) { loop = true; break; } } if (loop) { slow = head; while (fast != slow) { fast = fast.next; slow = slow.next; } while (fast.next != slow) { fast = fast.next; } fast.next = null; return head; } return head; }
在main
中调用上述方法:
myLinkedList.head = removeLoop(myLinkedList); System.out.println("Has Loop? " + hasLoop(myLinkedList));
时间复杂度:O(n)辅助空间:O(1)