void SListIsCycle(SListNode* list)
{
assert(list);
SListNode *fast = list;
SListNode *slow = list;
SListNode *entry=list;
SListNode *meet = NULL;
DataType count = 0;
fast = fast->_next->_next;
slow = slow->_next;
while (fast->_next->_next)
{
if (fast == slow)
{
meet = fast;
printf("链表带环\n");
break;
}
else
{
fast = fast->_next->_next;
slow = slow->_next;
}
}
count++;
slow = slow->_next;
while (meet!= slow)
{
count=count+1;
slow = slow->_next;
}
printf("相遇点的数据为%d\n", meet->_data);
printf("环的长度为%d\n", count);
while (entry == meet)
{
entry = entry->_next;
meet = meet->_next;
}
printf("入口点位置的数据是%d\n", entry->_data);
}
常常遍历链表的时候会将结点的next为空的时候认为这个链表结束了,但是其实有一种十分常见的情况就是这个链表是带环的,其实也很好理解下面我在用图给大家画一下
链表的最后一个元素的next指向了他之前的某一个元素都能构成环。你的指针不断的next是不能走出链表的。
所以很多的程序在对链表操作之前都会判断一下这个链表是不是一个环,如果是环就不能再按照非循环单链表那么进行操作了。
首先,如果要证明你是一个环很自然的就会想到,如果你一直往后走一直走走不出这个链表的话就说明他是一个环,有的人想让他一直走一直走,如果好久都没有走出来那说明他就是一个环,可是这个时间长短并没有一个准确的概念,如果你的单链表很长呢?十万,一百万甚至一千万个结点的时候,你怎么是知道他是没有遍历完单链表还是已经在环里了呢?
这里还是用我们之前用的快慢指针问题,其实可以想一下, 一个指针走的快一个指针走的慢,快的会不停的在里边转圈,慢的也会在里边走,最后两个都在圈里走,一个快一个慢迟早会两个相等的,如果是单链表的话,快走的多,会一直在慢前边所以两者不可能遇到,如果两者遇到的话只能是出现了环。这样的话,慢指针一定是每次都走一个指针,那快指针是走多少合适?一次两个。一次三个,还是一次四个或者更多,其实简单的想,他走多少个都可以毕竟是个环迟早有一个时刻两个指针会相遇,但是实际上只能让你的快指针一次走两个,如果走三个或者更多的话就会出现bug会出现两个不相遇,但是两个一直都在环里边走。大家可以想一下为什么会出现这种情况。
其实这种情况很容易想到,假如现在你的慢指针进到了环里边,而你的快指针就在你的慢指针前一个,这时候你的慢指针往前走一个,快指针走一圈多一个,是不是此时快指针依然在慢指针的前一个,接下来慢指针在走一个,快指针依然走了环一圈多一个,这就会出现快慢指针一直不能相等的情况,有的人说你动了慢指针之后不是已经和快指针相等了吗?不要忘了你是在快慢指针都移动结束之后才进行帕努单快慢指针是不是相等。还有人说想到你的快指针怎么可能在环的时候只在慢指针前一个呢?这种极端情况是很容易控制出现的,因为在你慢指针进来之前快指针说不定已经在环里转了几圈了。所以说如果你快指针走的步数如果是你环长度加一个的情况下就会出现找不到的极端情况。所以你的快指针只能每次走两个,毕竟不可能存在只有一个元素的环。
while (fast->_next->_next)
{
if (fast == slow)
{
meet = fast;
printf("链表带环\n");
break;
}
else
{
fast = fast->_next->_next;
slow = slow->_next;
}
}
所以通过这段代码就能判断出你的链表是否带环。
第二个问题是判断你的环的长度,这个问题是三个问题中最简单最容易理解的一个问题,当你快慢指针相遇的时候,用一个指针meet记录下这个位置的数据,然后让你的慢指针开始走,定义一个计数器,slow慢指针每走一步计数器就加一,知道slow再次遇到meet的时候就是你的指针转了一圈的时候。直接输出你的计数器就是链表的长度了。
while (meet!= slow)
{
count=count+1;
slow = slow->_next;
}
printf("相遇点的数据为%d\n", meet->_data);
printf("环的长度为%d\n", count);
这几行代码就可以实现找到两个指针的相遇点,以及环的长度。
最后一个问题也是最难的一个问题,就是找到你链表环的入口点。
假设进入链表环之前走的路为L,假设单链表环的长度为C,入口点为红色箭头所指方向,meet为两个指针相遇的地方
首先要清楚一个东西,慢指针和快指针相遇的时候一定是慢指针进入指针环一圈之内的,因为快指针比走的速度是慢指针的两倍,慢指针进到指针环的时候,快指针最远的距离就是距离慢指针半圈远,这是最远的情况。即使是这种情况下慢指针走半圈的时候快指针也走了一圈,这时候两个指针也相遇了。
快指针会提前进入圈内,所以两个指针相遇的时候快指针走的路程为L+NC,N倍的链表环,慢指针走过的路程为2(L+C),经过化简就会发现L=NC-X。所以当两个指针相遇的时候,重新找一个指针从头开始走,另一个指针在环里相遇点开始走,当经过N-1圈之后,再走N-C,会发现两个指针都走到了链表的入口处。
红色部分就是C-X,绿色部分就是L,两个相等,所以两个相遇的时候就是链表环的入口点。
while (entry == meet)
{
entry = entry->_next;
meet = meet->_next;
}
printf("入口点位置的数据是%d\n", entry->_data);
通过这段代码就能很容易的找到链表环的入口点。
然后周围有的人在编写测试程序的时候也出现了一些问题,不知道怎么构造一个带环的链表
SList = BuySListNode(1);
SListPushBack(&SList, 2);
SListPushBack(&SList, 3);
SListPushBack(&SList, 4);
SListPushBack(&SList, 5);
SListPushBack(&SList, 2);
甚至周围有人想这样将链表变成一个环,其实将链表成环最简单的语句
cur = SList;
while (cur->_next)
{
cur = cur->_next;
}
cur->_next = SList->_next;
先找到这个链表最后一个元素,然后将最后一个元素的next指向头指针的next这样链表就变成了我文章开头画的那个样子,链表最后的一个元素next指向了第二个元素。这样链表就构成了一个环