"Floyd circle judgment algorithm" (also known as the tortoise and the hare race algorithm)



Algorithm Description


Is there a loop

Conclusion : If there is a ring in the linked list, set the fast and slow pointers to move from the head node of the linked list, the fast pointer fast moves 2 steps at a time, and the slow pointer slow moves one step at a time. As long as you keep going, the fast pointer will definitely meet the slow pointer in the ring. Conversely, if there is no ring, then the fast pointer will never meet the slow pointer, and the fast pointer will come to the end.

Calculate the length of the ring

Method : When the fast and slow pointers meet for the first time, keep the fast pointer still, and the slow pointer walks around again. When the slow pointer returns to the meeting point again, the slow pointer just walks the length of the ring. Just add a length variable, every time the slow pointer takes a step, length+1, when the slow pointer reaches the meeting point, the value of length is the length of the ring.

Start of calculation loop

Method : After the fast and slow pointers meet for the first time, let the slow pointer slow1 stay at the meeting point. Redefine a new slow pointer slow2, let it just point to the head node of the linked list (starting point of the linked list) . Let the two slow pointers slow1 and slow2 move together, one step at a time. When the two slow pointers just meet, the meeting point is just the starting point of the ring.



Algorithm proof


Prove that the meeting of the fast and slow pointers and the ring of the linked list are mutually necessary and sufficient conditions

First of all, the simplest case is that the speed difference between the fast pointer and the slow pointer is 1, that is, |v(fast)-v(slow)| = 1. We can understand that the relative speed of the fast and slow pointers is 1. If there is a ring, the fast pointer will enter the ring first, and the slow pointer will enter the ring last. When both the fast and slow pointers enter the ring, from the perspective of relative motion, it can be understood that the slow pointer does not move, and the fast pointer moves one bit at a time, then the distance between the fast pointer and the slow pointer decreases by 1 each time, then the fast pointer will catch up sooner or later A slow pointer that "doesn't move".

And for |v(fast)-v(slow)| > 1. Assuming that the fast and slow pointers do not move from the same starting point at the beginning, there may be a coincidence that the fast pointer will never meet the slow pointer in the ring. As shown in the figure below:
insert image description here
the fast pointer moves 4 steps each time, v(fast) = 4, while the slow pointer moves 1 step each time, v(slow) = 1. When the slow pointer enters the starting point 3 of the ring, the fast pointer moves 4 steps to 5, which is equivalent to 1 step slower than the slow pointer. After that, the fast pointer takes 4 steps each time, which is equivalent to taking one more step around the circle, and taking one step with the slow pointer is equivalent to the same speed. That way the fast and slow hands can never meet.

However, if the fast and slow hands start to move from the same starting point, suppose the speed difference between the fast and slow hands is greater than 1. It seems that there are complicated mathematical problems in it, and I did not give an example where the fast and slow pointers cannot meet ( if you know friends, you can point me in the comment area ).

In short, it is better to assume that the fast pointer moves 2 steps at a time, and the slow pointer moves 1 step at a time, which is better proved.

Prove that when two slow pointers meet for the second time, the node is the starting point of the ring

Look at the method of finding the starting point of the ring

Method : After the fast and slow pointers meet for the first time, let the slow pointer slow1 stay at the meeting point. Redefine a new slow pointer slow2, let it just point to the head node of the linked list (starting point of the linked list) . Let the two slow pointers slow1 and slow2 move together, one step at a time. When the two slow pointers just meet, the meeting point is just the starting point of the ring.
2 steps are required.
1. First prove that two slow pointers with the same speed must meet.

Assuming that when the fast pointer and the slow pointer meet for the first time, the slow pointer needs to take n steps, and the speed of the fast pointer is twice that of the slow pointer, then the fast pointer needs to take 2n steps.
It can be understood that the slow pointer slow2 moving from the head node is equivalent to the slow pointer at the beginning. When it takes n steps, it just reaches the meeting point where the fast and slow pointers meet for the first time.
The original slow pointer slow1 starts walking from the meeting point. You can understand that, compared with the slow pointer slow2, the slow pointer slow1 walks n steps first to the meeting point, and then they walk together. When it takes another n steps, it is equivalent to a total of 2n steps, which is the number of steps taken by the fast pointer. Then it will definitely go to the meeting point of the fast and slow pointers again, just like the fast pointer.
Therefore, after slow1 and slow2 take n steps, the two slow pointers slow1 and slow2 will definitely meet at the meeting point.

2. Prove again that the node where two slow pointers with the same speed meet for the first time is the starting point of the ring.
And since the two slow pointers slow1 and slow2 have the same speed, once they meet, they will always coincide. Then backwards, they must have met from the starting point of the ring, and then coincided all the time until they reached the meeting point of the fast and slow pointers.
The starting point of the ring must be the node where the two slow pointers meet for the first time, otherwise the two slow pointers with the same speed will never meet.

Thus it is proved that it is only necessary to return the node where the two slow pointers meet for the first time, which is the starting point of the ring.



Code


1. Determine whether the linked list has a ring

bool hasCycle(struct ListNode *head) {
    
    
    struct ListNode* fast = head;
    struct ListNode* slow = head;
    // 如果快指针走到末尾,结束循环
    while (fast && fast->next)
    {
    
    
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow)			// 如果快慢指针相遇,返回true
            return true;
    }
    return false;					// 如果跳出循环,说明fast到末尾,返回false
}

2. Find the starting point of the ring of the linked list

struct ListNode *detectCycle(struct ListNode *head) {
    
    
	// 如果链表是空表或者只有一个结点(且该节点的next不指向自身),直接返回false
     if (head == NULL || head->next == NULL)
        return false;
    // 快慢指针初始都指向头结点
    struct ListNode* fast = head;
    struct ListNode* slow = head;
    do
    {
    
    
    	// 快指针每次移动2步,慢指针每次移动一步
        fast = fast->next->next;
        slow = slow->next;
        // 如果fast能走到末尾,说明没环,返回false
        if (fast == NULL || fast->next == NULL)
            return false;
    }
    while (fast != slow);				// 如果fast == slow,说明走到快慢指针相遇点
    struct ListNode* newSlow = head;	// 让新的慢指针指向链表头结点
    // 假如链表头结点也是环的起点,两个慢指针一开始就相遇,程序不需要进入循环,直接返回该结点
    // 假如链表头结点不是环的起点,两个慢指针首次相遇的结点,就是环的起点
    while (slow != newSlow)
    {
    
    
        slow = slow->next;
        newSlow = newSlow->next;
    }
    return slow;			// 首次相遇的结点位置,就是环的起点
}

Guess you like

Origin blog.csdn.net/qq_983030560/article/details/128212954