Java 循环链表逻辑

Java 循环链表逻辑,java,algorithm,linked-list,Java,Algorithm,Linked List,为了检测循环链表,我们使用了两个指针技术,慢和快 我的问题是,如果列表是循环列表,我如何知道指针必须在某个点相交。这是一个数字1到12的循环列表,然后循环回到1 大手移动得很快,小手移动得很慢,都朝着同一方向移动,并且从同一点开始(top=12) 因为列表(边)是圆形的,大手最终会接住小手。速度的快慢取决于速度差,但它会赶上。如果它赶上了,名单必须是循环的 若它并没有赶上,但到达了列表的末尾,则列表不是循环的 即使列表没有回到起始点,例如,如果12圈回到9,快手也会继续圈,直到小手进入圆圈,然后

为了检测循环链表,我们使用了两个指针技术,慢和快


我的问题是,如果列表是循环列表,我如何知道指针必须在某个点相交。这是一个数字1到12的循环列表,然后循环回到1

大手移动得很快,小手移动得很慢,都朝着同一方向移动,并且从同一点开始(top=12)

因为列表(边)是圆形的,大手最终会接住小手。速度的快慢取决于速度差,但它会赶上。如果它赶上了,名单必须是循环的

若它并没有赶上,但到达了列表的末尾,则列表不是循环的

即使列表没有回到起始点,例如,如果12圈回到9,快手也会继续圈,直到小手进入圆圈,然后快手最终会追上小手


好的,最后一部分,手表的图像不是很好,但我希望你能理解这一点。

证据并不像看上去那么明显

实际上,只要稍作改动,快速指针就会更快,例如使用
fast=fast.next.next.next
,算法就不再保证有效

重要的是两个指针的相对速度

在标准情况下,相对速度为2-1=1,这意味着在每一步,快速指针都会与慢速指针接近一个单位。通过这种方式,可以保证速度快的一个会迎头赶上,而不会跳过另一个

否则,例如,如果相对速度为3-1=2,则它们可能永远不会相交。如果我们从指针之间的奇数距离开始,循环长度为偶数,则会发生这种情况。在这种情况下,距离将始终保持奇数(因此永远不会为零)


为了明确指针如果不注意速度差,就不能交叉,考虑一个速度为3的快速指针和一个速度为1的慢指针,在一个循环中运行4个节点,标记为0, 1, 2,3,形成一个循环,如0—1>2>3—>0。 假设最初,慢速指针位于节点0,快速指针位于节点1。(请注意,这不是一个很强的假设,并且可能不会通过不同的初始化策略得到缓解——无论采用何种初始化方法,在这种情况下,图形中可能会有一些额外的节点,而不是循环的一部分,这使得指针在到达循环后可以处于任意位置)


执行
k
步骤后,慢速指针将位于节点
k mod 4
。快速节点将位于节点
(1+3k)mod 4
。假设存在一个
k
,快速指针和慢速指针位于同一位置,则表示
(1+3k)mod 4=k mod 4
,即
(1+2k)mod 4=0
。但左手边是个奇数,所以不能是零。因此,假设指针可以指向同一个节点导致了矛盾。

正如andreas提到的,看看手表,但如果这仍然没有意义,make可能会有所帮助

此外,您还可以稍微简化代码:

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head == null){
            return false;
        }
          ListNode slow = head;
          ListNode fast = head.next;

          while((slow != null) && (fast != null) && (slow.next != null) && (fast.next != null)){
            if(slow == fast){
                return true;
            }
            slow = slow.next;
            fast = fast.next.next;

          }

          return false;
    }
}
  • 从复制的代码

我还认为您应该使用头来初始化快速指针,而不是头。接下来

下面是C中循环链表的完整实现:

public boolean isCyclic(Node first) {
    if(first == null) {
      return false;
    }
    Node fast = first;
    Node slow = first;
    while(fast.getNext() != null && fast.getNext().getNext() != null) {
      slow = slow.getNext();
      fast = fast.getNext().getNext();
      if(slow == fast) {
        return true;
      }
    }
    return false;
}

顺便说一句,循环链表中不需要两个指针。它主要用于实现队列和Fibonacci堆。当到达开始的第一个节点时,就停止遍历。@PorkkoM问题不是关于使用循环链的链表实现,但是关于检测非循环列表的损坏链,考虑到复杂性,为什么不同时拥有一个链表和一个集合呢?调整链表中项目的顺序,并跟踪索引结构中的重复(循环)。那么为什么不使用已经存在的。已实现并测试,如果要添加任何自定义行为(如在add()上引发异常而不是返回false),则可以从中继承?@andrei macarie双指针方法的一个优点是它不需要额外的内存(除了存储两个指针:-)。此外,它还可以通过快速指针与慢速指针相交(检测到循环)或快速指针到达列表末尾(无循环)在O(n)中找到答案。@qwertyman我明白了,所以这个问题是关于-我更关心内存,而不太关心性能,复杂性不超过O(n)。因此,该算法将用于有限数量的元素,无论如何都会消耗较低的内存,但对于物联网时代,在便宜的设备上,它可能是值得的。现在我明白了:)这适用于所有速度的指针吗?也就是说,慢动作一次2个,快动作一次6个?还是慢动作一次1,快动作一次17?如果它的通告是?@user7487099,他们必须赶上来。正如我所说,快手赶上慢手的速度取决于速度差,但它会赶上。它们可以在同一个圆圈内以不同的速度移动,而不是偶尔相遇。@Andreas我怀疑,正如另一个答案所提到的,如果两个指针使用了正确(错误)的跳跃大小,它们应该可以同步,这样它们就永远不会真正命中。@Sahuagin是真的。我的答案更多的是一种模拟的视觉解释,解释为什么他们总是会相遇。在模拟世界中,快手无法“跳过”慢手。在数字世界中,步长增量确实很重要,但与问题代码的1-vs-2步长无关。
#include <stdio.h>
#include <stdlib.h>
struct node {
int data;
struct node* next;
};
void insert(struct node** head,int ele){
struct node* temp = (struct node*)malloc(sizeof(struct node));
struct node* ptr;
temp->data = ele;
temp->next = temp;
if(*head == NULL){
    *head = temp;
}else{
    ptr = *head;
    while(ptr->next != *head){
        ptr = ptr->next;
    }
    ptr->next = temp;
    temp->next = *head;
}
}
void deleteLastEle(struct node** head){
struct node* current = *head;
struct node* pre = *head;
while(current->next != *head){
    pre = current;
    current = current->next;
}
pre->next = current->next;
free(current);
}
void deleteAtPos(struct node** head,int pos){
struct node* current = *head;
struct node* pre = *head;
for(int i=0;i<pos-1;i++){
    pre = current;
    current = current->next;
}
pre->next = current->next;
free(current);
}
void deleteFirst(struct node** head){
struct node* current = *head;
struct node* temp = *head;
while(current->next != *head){
    current = current->next;
}
current->next = (*head)->next;
*head = (*head)->next;
free(temp);
}
printCLL(struct node** head){
struct node* ptr = *head;
do{
    printf("%d ",ptr->data);
    ptr = ptr->next;
}while(ptr != *head);
printf("\n");
}
// main() function.
int main(void) {
struct node* head = NULL;
for(int i=0;i<5;i++){
    //function to insert elements in linked list
    insert(&head,i);
}
printf("inseted linkedlist: \n");
//print the content of linked list.
printCLL(&head);

//function to delete last element
deleteLastEle(&head);
printf("after deleting last element: \n");
printCLL(&head);

//function to delete element at pos 3.
deleteAtPos(&head,3);
printf("after deleting at pos 3: \n");
printCLL(&head);

//function to delete first element of linkedlust
deleteFirst(&head);
printf("after deleting first element: \n");
printCLL(&head);
return 0;
}
inseted linkedlist: 
0 1 2 3 4 
after deleting last element: 
0 1 2 3 
after deleting at pos 3: 
0 1 3 
after deleting first element: 
1 3