参考:《漫画算法-小灰的算法之旅》
目录
题目
有一个单向链表,链表中可能出现“环”,如下图所示。那么如何用程序来判断该链表是否为有环链表呢?
解决方法:
可以使用两盒指针来解决这个问题:
首先创建两个指针p1和p2,让它们同时指向这个链表的头节点。然后开始一个大循环,在循环体中,让指针p1每次向后移动一个节点,让指针p2每次向后移动2个节点,然后比较两个指针指向的节点是否相同。如果相同,则可以判断出链表有环,如果不同,则继续下一次循环。
第1步:p1和p2都指向节点5。
第2步:p1指向节点3,p2指向节点7。
第3步:p1指向节点7,p2指向节点6。
第4步:p1指向节点2,p2指向节点1。
第5步,p1指向节点6,p2也指向节点6,p1和p2所 指相同,说明链表有环。
此方法就类似于一个追及问题。在一个环形跑道上,两个运动员从同一地点起跑,一个运动员速度快,另一个运动员速度慢。当两 人跑了一段时间后,速度快的运动员必然会再次追上并超过速度慢的运动员,原因很简单,因为跑道是环形的。
假设链表的节点数量为n,则该算法的时间复杂度 为O(n)。除两个指针外,没有使用任何额外的存储空间,所以空间复杂度是O(1)。
代码
using namespace std;
#include<iostream>
#include<string>
//创建链表节点结构体
struct Node {
int data; //节点数据
Node* next; //指向下一个节点的指针
Node(int _data):data(_data),next(nullptr) {
}
//定义了一个构造函数,该构造函数带有一个整型参数 _data,表示该节点的数据。构造函数使用了成员初始化列表,
//将节点的数据成员 data 初始化为 _data,将节点的指针成员 next 初始化为 nullptr,表示该节点的下一个节点为空指针。
};
//判断是否有环
int isCycle(Node* head) {
Node* p1 = head;
Node* p2 = head;
while (p2 != nullptr && p2->next!= nullptr) {
p1 = p1->next; //p1向后移动一个节点
p2 = p2->next ->next; //p2向后移动两个节点
if (p1 == p2) {
return true;
}
}
return false;
}
int main()
{
Node* node1 = new Node(5);
Node* node2 = new Node(3);
Node* node3 = new Node(7);
Node* node4 = new Node(2);
Node* node5 = new Node(6);
Node* node6 = new Node(8);
Node* node7 = new Node(1);
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = node5;
node5->next = node6;
node6->next = node7;
node7->next = node4;
cout << isCycle(node1) << endl;
system("pause");
return 0;
}
问题扩展
如果链表有环,如何求出环的长度?
解:
当两个指针首次相遇,证明链表有环的时候,让两个指针从相遇点i继续循环前进,并统计前进的循环次数,直到两个指针第二次相遇。此时,统计出来的前进次数就是环长。因为指针p1每次走1步,指针p2每次走2步,两者的速度差是1步。当两个指针再次相遇时,p2比p1多走了整整1圈。因此,环长=每一次速度差×前进次数=前进次数。