环形链表是一种特殊的链表,其中链表的末尾节点指向头节点,形成一个环。在环形链表中,我们需要找到入环节点(环的起点)和出环节点(环的终点)。
一、环形链表的初始化
首先,我们需要初始化一个环形链表。以下是一个简单的环形链表结构体示例:
// 环形链表节点结构体
typedef struct stData {
float data;
struct stData *pNext;
} stData;
初始化环形链表的过程如下:
- 为环形链表分配一个节点的内存,这也是整个环形链表的地址。
- 使用两个临时指针,一个用于连接各个节点(指定各节点的
pNext
指针指向下一个节点),另一个用于不断分配下一个节点的内存。
void init_list(stData **pList, int len) {
// 分配第一个节点的内存
*pList = (stData*)malloc(sizeof(stData));
(*pList)->data = 0;
(*pList)->pNext = NULL;
stData *pTmp1 = *pList;
for (int i = 0; i < len - 1; i++) {
// 分配下一个节点的内存
stData *pTmp2 = (stData*)malloc(sizeof(stData));
pTmp2->data = 0;
pTmp2->pNext = NULL;
// 连接节点
pTmp1->pNext = pTmp2;
pTmp1 = pTmp2;
}
// 将最后一个节点的 next 指向头节点,构成环形链表
pTmp1->pNext = *pList;
}
二、查询环形链表中有效数据的长度
在向环形链表写入数据时,我们需要知道有效数据的长度,以便在合适的位置写入新的数据。以下是查询有效数据长度的函数:
int check_list_isfull(stData *pHead, stData *pTail) {
int cnt = 0;
stData *pTmp = pHead;
while (pTmp != pTail && pTmp != NULL) {
cnt++;
pTmp = pTmp->pNext;
}
return cnt;
}
三、找到入环节点
一旦我们找到相遇点(即环中的某个节点),我们可以从链表的头节点和相遇点同时出发,直到它们再次相等。此时,相等的节点就是入环节点。
stData *detectCycle(stData *head) {
stData *fast = head;
stData *slow = head;
while (fast && fast->pNext) {
slow = slow->pNext;
fast = fast->pNext->pNext;
if (fast == slow) {
// 从头节点和相遇点同时走,直到相等,即为入环点
while (head != slow) {
head = head->pNext;
slow = slow->pNext;
}
return head; // 入环节点
}
}
return NULL; // 无环
}
算法原理
当我们需要在一个链表中找到带环的入环节点时,快慢指针算法是一种常见且高效的解决方案。下面详细解释一下这个算法的原理:
-
初始化快慢指针:
- 我们定义两个指针:
fast
和slow
,初始时都指向链表的头部。 fast
每次移动两步,而slow
每次移动一步。
- 我们定义两个指针:
-
判断是否有环:
- 如果链表中不存在环,那么
fast
指针最终会遇到null
,此时我们结束算法。 - 如果链表中存在环,那么
fast
和slow
指针一定会再次相遇。
- 如果链表中不存在环,那么
-
找到入环节点:
- 当
fast
和slow
指针相遇时,我们额外创建一个指针ptr
,并将其指向链表的头部。 - 然后,我们同时移动
ptr
和slow
,每次都走一步,直到它们再次相遇。 - 此时,相遇点就是链表的入环节点。
- 当
-
为什么这个算法有效:
- 假设链表中存在环,我们可以将环的长度记为
c
,从链表头部到入环点的距离记为a
,从入环点到相遇点的距离记为b
。 - 当
slow
和fast
指针相遇时,slow
走过的距离为a + b
,而fast
走过的距离为 a + n·c + b,其中n
表示fast
绕环的圈数。 - 由于
fast
指针的速度是slow
的两倍,我们可以得到以下关系:a + n·c + b = 2(a + b)
。 - 进一步简化,我们可以得到
a = n·c - b
。 - 这意味着,如果我们从头部再创建一个指针,让它和
slow
同步移动,当它们相遇时,相遇点就是入环节点。
- 假设链表中存在环,我们可以将环的长度记为
-
时间复杂度和空间复杂度:
- 时间复杂度:O(N),其中 N 是链表节点的数量。
- 空间复杂度:O(1),因为我们只使用了
slow
、fast
和ptr
三个指针。