链接列表循环检测– Floyd的循环查找算法

时间:2020-02-23 14:41:32  来源:igfitidea点击:

在本教程中,我们将讨论一种非常流行的算法,该算法用于检测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)