Floyd의 연결 리스트 사이클 탐지 알고리즘¶
시작점이 head로 표시되는 연결 리스트가 주어지며, 이 연결 리스트에는 사이클이 있을 수도 있고 없을 수도 있습니다. 예를 들면 다음과 같습니다.
여기서는 사이클의 시작점인 지점 C를 찾아야 합니다.
제안하는 알고리즘¶
이 알고리즘은 Floyd의 사이클 알고리즘 또는 토끼와 거북이(Tortoise And Hare) 알고리즘이라고 부릅니다. 사이클의 시작점을 알아내려면 먼저 사이클이 존재하는지부터 알아내야 합니다. 이 과정은 두 단계로 이루어집니다. 1. 사이클의 존재 여부를 알아냅니다. 2. 사이클의 시작점을 찾습니다.
1단계: 사이클의 존재 여부¶
- 두 포인터 $slow$와 $fast$를 준비합니다.
- 처음에는 두 포인터 모두 연결 리스트의 head를 가리킵니다.
- $slow$는 한 번에 한 칸씩 이동합니다.
- $fast$는 한 번에 두 칸씩 이동합니다. ($slow$ 포인터보다 두 배 빠른 속도입니다.)
- 둘 중 하나(또는 둘 다)가 null에 도달하기 전에 어느 시점에서든 두 포인터가 같은 노드를 가리키는지 확인합니다.
- 이동하는 도중 어느 시점에서든 두 포인터가 같은 노드를 가리키면, 연결 리스트에 실제로 사이클이 존재한다는 뜻입니다.
- null을 만나면 연결 리스트에 사이클이 없다는 뜻입니다.
이제 연결 리스트에 사이클이 있는지 알아냈으므로, 다음 단계에서는 사이클의 시작점, 즉 C를 찾아야 합니다.
2단계: 사이클의 시작점¶
- $slow$ 포인터를 연결 리스트의 head로 되돌립니다.
- 두 포인터를 모두 한 번에 한 칸씩 이동시킵니다.
- 두 포인터가 만나는 지점이 사이클의 시작점입니다.
// Presence of cycle
public boolean hasCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
if(slow==fast){
return true;
}
}
return false;
}
// Assuming there is a cycle present and slow and fast are point to their meeting point
slow = head;
while(slow!=fast){
slow = slow.next;
fast = fast.next;
}
return slow; // the starting point of the cycle.
이 알고리즘이 동작하는 이유¶
1단계: 사이클의 존재 여부¶
포인터 $fast$는 $slow$보다 두 배 빠른 속도로 이동하므로, 어느 시점에서든 $fast$는 $slow$보다 두 배 먼 거리를 이동했다고 할 수 있습니다. 또한 두 포인터가 이동한 거리의 차이가 $1$씩 증가한다는 것도 추론할 수 있습니다.
slow: 0 --> 1 --> 2 --> 3 --> 4 (distance covered)
fast: 0 --> 2 --> 4 --> 6 --> 8 (distance covered)
diff: 0 --> 1 --> 2 --> 3 --> 4 (difference between distance covered by both pointers)
2단계: 사이클의 시작점¶
두 포인터가 사이클 안에서 만나는 지점까지 각각 이동한 거리를 계산해 봅시다.
$slowDist = a + xL + b$ , $x\ge0$
$fastDist = a + yL + b$ , $y\ge0$
- $slowDist$는 느린 포인터가 이동한 총 거리입니다.
- $fastDist$는 빠른 포인터가 이동한 총 거리입니다.
- $a$는 두 포인터가 사이클에 들어가기 위해 이동해야 하는 걸음 수입니다.
- $b$는 C와 G 사이의 거리, 즉 사이클의 시작점과 두 포인터가 만나는 지점 사이의 거리입니다.
- $x$는 느린 포인터가 C를 시작점이자 끝점으로 하여 사이클 안을 돈 횟수입니다.
- $y$는 빠른 포인터가 C를 시작점이자 끝점으로 하여 사이클 안을 돈 횟수입니다.
$fastDist = 2 \cdot (slowDist)$
$a + yL + b = 2(a + xL + b)$
식을 정리하면 다음을 얻습니다:
$a=(y-2x)L-b$
여기서 $y-2x$는 정수입니다
이는 기본적으로 $a$ 걸음이 사이클에서 완전한 루프를 몇 번 돈 다음 $b$ 걸음 뒤로 가는 것과 같다는 뜻입니다. 빠른 포인터는 이미 사이클 입구보다 $b$걸음 앞에 있으므로, 빠른 포인터가 $a$걸음을 더 이동하면 사이클의 입구에 도착하게 됩니다. 그리고 느린 포인터는 연결 리스트의 시작점에서 출발하게 했으므로, $a$걸음 뒤에는 느린 포인터도 사이클 입구에 도착하게 됩니다. 따라서 두 포인터가 모두 $a$걸음을 이동하면 둘 다 사이클의 입구에서 만나게 됩니다.