带环链表
这里的带环单链表可不是环形单链表,这个环可能是我们不想要的,所以需要检测。
我们就不假设有一个打结状的环了,那样跑到哪里去也不清楚,这里的“带环链表”,环必然是在末端。
这是经典问题,方法众多,但方法效率差别很大,本文试举三例,并对较优算法加以编程实现。
算法一:暴力查重
双指针即可,前一个指针在遍历单链表,后一个指针遍历前指针之前的所有结点是否有与前指针相同的结点。
如果能跑到结尾,自然是无环的,否则就跑下去,总会发现的。
这算法很暴力,不好。
算法二:HashSet查重
不需要双指针,但需要多费一些空间,建一个HashSet,里面装每一个搜索过的结点,搜到的结点如果Set里有,就是说明了有环,否则就把新搜到的结点加入HashSet里面。搜到尽头就是没环。
算法三:双指针
前一个指针一次跳两个结点,后一个结点一次跳一个结点,如果有环,则前后必能相遇。如果前者跑到尽头就说明没环。
算法核心代码(Java语言描述):
private boolean isCircular() {
Node<T> prev = first;
Node<T> rear = first;
while (prev.next != null) {
prev = prev.next;
if (prev.next == null) {
return false;
}
prev = prev.next;
rear = rear.next;
if (prev == rear) {
return true;
}
}
return false;
}
完整代码(Java语言描述)
public class Main {
private static class Node<T> {
T element;
Node<T> next;
Node(T element) {
this.element = element;
}
}
private static class LinkedList<T> {
Node<T> first;
private boolean isCircular() {
Node<T> prev = first;
Node<T> rear = first;
while (prev.next != null) {
prev = prev.next;
if (prev.next == null) {
return false;
}
prev = prev.next;
rear = rear.next;
if (prev == rear) {
return true;
}
}
return false;
}
}
public static void main(String[] args) {
Node<Integer> node1 = new Node<>(1);
Node<Integer> node2 = new Node<>(2);
Node<Integer> node3 = new Node<>(3);
Node<Integer> node4 = new Node<>(4);
Node<Integer> node5 = new Node<>(5);
Node<Integer> node6 = new Node<>(6);
Node<Integer> node7 = new Node<>(7);
Node<Integer> node8 = new Node<>(8);
Node<Integer> node9 = new Node<>(9);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = node6;
node6.next = node7;
node7.next = node8;
node8.next = node9;
node9.next = node4;
LinkedList<Integer> list = new LinkedList<>();
list.first = node1;
System.out.println("链表是否有环:" + list.isCircular());
}
}