CS162 - 5B (Bonus)
Proofread by Huỳnh Hà Phương Linh
TABLE OF CONTENTS
- Introduction - Circular Linked List (cont.)
- Problem statement
- Trivial solution
- Memory-friendly solution
- Tortoise and Hare solution
- SOLUTION
- References
Introduction - Circular Linked List (cont.)
This problem was introduced to class 22APCS1. I was lucky enough to attend the lecture and share with y’all 🥰.
The problem is about cycle detection, you can go to references section to see more.
Problem statement
You are given a linked list with the last node (pLast
) either pointing to nullptr
or a node inside the linked list. When pLast
points to nullptr
, there are no loops inside the linked list. Otherwise there is exactly one loop.
Giving you too much information of the list would be too easy so only the address of the first node of the list is given.
Your job is to find if there is any loop.
bool isLooped(Node* pFirst);
The problem seems easy at first, isn’t it? I’ll provide you with multiple solutions but first try to solve the problem yourself and see how good your solution(s) is/are.
Trivial solution
The simplest solution to this problem is to add an attribute to show if a node has been visited.
1
2
3
4
5
struct Node {
int data;
Node* pNext;
bool isVisited;
};
The code may look like this:
1
2
3
4
5
6
7
bool isLooped(Node* pFirst) {
while(pFirst != nullptr && pFirst->isVisited == false) {
pFirst->isVisited = true;
pFirst = pFirst->pNext;
}
return pFirst != nullptr;
}
After iterating through the list, if there’s a node pointing to nullptr
, the linked list has no loops and vice versa.
The code is simple and it works, but we have modified the structure of the node and increase the memory cost. Is there any solution that requires less memory?
Memory-friendly solution
Notice that as we iterate through the list, the distance between the first node and the iterating node grows before crossing the LL’s tail. Hence, if a loop exists, at some point the distance will stop growing. With that observation, we came up with the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bool isLooped(Node* pFirst) {
if(pFirst == nullptr) return false;
int savedDistance = 0;
Node* cur = pFirst->pNext;
while(cur != nullptr) {
int distance = 0;
for(Node* tmp=pFirst; tmp != cur; tmp = tmp->pNext)
distance++;
if(distance > savedDistance) {
savedDistance = distance;
} else {
return true;
}
cur = cur->pNext;
}
return false;
}
It is easy to see that the code will iterate approximately $n^2$ times ($n$ is the number of node in the list). But the code does not cost memory for every single node like the previous solution. Can we do better?
Tortoise and Hare solution
Assuming we have 2 iterators: tortoise
and hare
, both starting at the first node. The tortoise
and hare
will move 1 and 2 steps ahead respectively. If there’s a loop, thetortoise
and hare
will intersect (proof below). The code might look like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool isLooped(Node* pFirst) {
Node *tortoise = pFirst;
Node *hare = pFirst;
while ( tortoise != nullptr
&& hare != nullptr
&& hare->pNext != nullptr) {
tortoise = tortoise->pNext;
hare = hare->pNext->pNext;
if (tortoise == hare)
return true;
}
return false;
}
The code looks clean but is it alright? Let’s have a look!
If there are no loops, the function returns the expected answer - false, so we just have to focus on the case with a loop.
If there’s a loop, the tortoise
and hare
will both enter the loop after approximately $2n$ iterations. In the loop, they chase around in circle. Notice that every time the tortoise
and hare
move, the distance between them decrease by $1$ so the max number of iterations is $2n$. So the max number of iterations is $4n$ for those two to meet. (Actually it’s $\approx2n$).
There we have it, a memory-friendly and fast solution! This algorithm is known as Floyd’s Cycle Finding Algorithm.
SOLUTION
This solution is proposed by Lê Hữu Nghĩa. The number of iterations is almost similar to Floyd’s Cycle Finding Algorithm. You can calculate the number of iterations yourself as an exercise😉.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bool isLooped(Node* pFirst) {
if(pFirst == nullptr) return false;
if(pFirst->pNext == nullptr) return false;
int steps = 1;
Node* cur1 = pFirst
Node* cur2 = pFirst->pNext;
while(true) {
for(int i=1; i<=steps; i++) {
cur2 = cur2->pNext;
if(cur2 == nullptr) return false;
if(cur2 == cur1) return true;
}
for(int i=1; i<=steps; i++) {
cur1 = cur1->pNext;
if(cur1 == nullptr) return false;
if(cur1 == cur2) return true;
}
steps *= 2;
}
}