有一个单链表,其中可能有一个环,也就是某个节点的next指向的是链表中在它之前的节点,这样在链表的尾部形成一环。如下图所示
下面分析如何检测出来
首先想到的是是否可以将走过的路径记录下来,每次都需要检测这个是否遍历过,
当然这里考虑链表的长度比较短
首先模拟一个链表环,然后利用vector保存每个链表节点的地址,每次都进行搜索;
typedef struct node
{
int data;
struct node* next;
}LIST;
然后模拟一个简单链表环
LIST* creat()
{
int i=0;
LIST* h;
LIST* n;
LIST* nn;
LIST* r;
h=n=nn=r=NULL;
h=(LIST*)malloc(sizeof(LIST));
assert(h!=NULL);
h->data=1;
n=nn=h;
n=(LIST*)malloc(sizeof(LIST));
assert(n!=NULL);
n->data=2;
nn->next=n;
r=n;
nn=(LIST*)malloc(sizeof(LIST));
assert(nn!=NULL);
nn->data=3;
n->next=nn;
n=(LIST*)malloc(sizeof(LIST));
assert(n!=NULL);
n->data=4;
nn->next=n;
n->next=r;
return h;
}
全局区设置
vector <LIST*> tab;
用于保存遍历过的链表节点地址。
bool search(LIST* h)
{ int i=0;
for(i=0;i<tab.size();++i)
if(tab[i]==h)return true;
return false;
}
bool check(LIST* h)
{
assert(h!=NULL);
while(h!=NULL)
{
if(search(h))
return true;
else
tab.push_back(h);
h=h->next;
}
return false;
}
但是这种最直接方法,效率不高,再想想还有什么
谷歌大神问了下,提到了一种快慢指针,利用两个指针,一个一次遍历一个节点,另一个一次遍历两个节点,只要能相遇 说明是有环的。
那么问题来了进入环后,这两个指针一定能够相遇吗?
分析一下吧
快指针一定是先进入环中,我们假设当慢指针进入环的时候,建立一个类似坐标的标识,
快指针为位置0,慢指针假设位置是n,
每遍历一次快指针到达的位置是
2/6
比如遍历3次,快指针回到0点
(2×3)/6=0;
同理慢指针从位置n为基点,开始遍历,遍历一次为
n/6
比如遍历3次到达位置1
(n+3)/6=1
假设经过t次遍历两者相遇,
(2t)/6=(n+t)/6
t=n;
也就是经过n次遍历两者是可以相遇的,
而n是一个常量值,
所以快慢指针是一定可以相遇的。
下面看看代码如何实现这种方法
bool fspoint(LIST* h)
{
assert(h!=NULL);
LIST *f;
LIST *s;
f=s=h;
while(f!=NULL)
{
if(f==s && f!=h)
return true;
else
{
f=f->next;
f=f->next;
s=s->next;
}
}
return false;
}