0. 定义结点并创建链表
代码实现:
struct ListNode // 定义节点
{
int val;
struct ListNode * next;
ListNode(int x):val(x),next(NULL){}
};
ListNode* CreateLinkList(int n, int m) // 创建一条长度为n的链表,在第m个位置设置环的起始点
{
if(n<=0||m>n)
return NULL;
ListNode * pHead = NULL;
ListNode * p = NULL;
ListNode * pStart = NULL; // 保存环的起始结点
for(int i=0;i<n;i++)
{
ListNode * node = new ListNode(i+1);
if(i+1==m)
pStart = node;
if(i==0)
{
pHead = node;
p = pHead;
}
else
{
p->next = node;
p = p->next;
}
}
p->next = pStart;
return pHead;
}
1. 求解链表是否存在环?
错误思路:从头结点开始遍历链表,当再次遍历到头结点则可判断该链表存在环。这种解法没有充分理解题目的意思,链表中存在的环不一定是尾结点指向头结点(形如“O”),也可能是尾结点指向非头结点(形如“6”)。
正确思路:设置两个指针,一个快指针每次走两步,一个慢指针每次只走一步。当链表中存在环时,快指针一定会和慢指针相遇于环中的一结点;当链表中不存在环时,快指针先遍历完整个链表。
代码实现:
bool IsHaveCircle(ListNode* pHead) // 判断链表里是否有环
{
if(pHead==NULL)
return true; // 空链表默认无环
ListNode * slow = pHead;
ListNode * fast = pHead;
while(fast!=NULL)
{
fast = fast->next;
if(fast!=NULL)
{
fast = fast->next;
slow = slow->next;
}
if(slow==fast)
break;
}
if(fast==NULL)
return false;
else
return true;
}
2. 求解环中节点个数
正确思路:从问题1中我们得知,若链表中存在环,则快指针与慢指针必定相遇于环中任一结点,因此当相遇时我们开始计数直到下次相遇,慢指针所移动步数即为环中结点个数(设环中结点数为r,另设从第一次相遇开始到第二次相遇时慢指针移动S步,那么快指针移动步数为2S,且有2S=S+r,因此r=S)。
代码实现:
int CountCircle(ListNode* pHead) // 计算环中节点个数
{
if(pHead==NULL)
return 0;
ListNode * slow = pHead;
ListNode * fast = pHead;
while(fast!=NULL) // 先找到相遇点
{
fast = fast->next;
if(fast!=NULL)
{
fast = fast->next;
slow = slow->next;
}
if(fast==slow)
break;
}
if(fast==NULL)
return 0; // 无环返回0
int count = 0; //从相遇点开始计数,直至下一次相遇
do{
fast = fast->next->next;
slow = slow->next;
count++;
}while(fast!=slow);
return count;
}
3. 求解环的起始结点
正确思路:问题1提到当慢指针与快指针相遇时,必定相遇于环中某一结点,并且相遇时慢指针是第一次进入环(这是因为:若假设相遇前慢指针已经遍历过一遍环了,那么快指针必定可以遍历两遍环,必定与慢指针相遇;这与假设矛盾,因此第一次相遇时慢指针是第一次进入环)。现设整个链表长度为L,头结点距离环的起始结点的长度为l,而环起始结点距离相遇结点的长度为x,环的长度为r。
(1) 假设从开始到第一次相遇时慢指针移动S步,那么快指针移动步数为2S,则有2S=S+nr ---> S = nr;
(2) 根据慢指针是第一次入环,那么第一次相遇时,慢指针总共步数S为l+x, 而S=nr, 则有 l+x = nr ---> l+x = (n-1)r + r ---> l + x = (n-1)r + L - l ---> l = (n-1)r + L - I - x.
根据(2)中推出的公式l = (n-1)r + L - I - x,我们可以得出结论:若有一指针从表头开始遍历,另一指针从相遇点开始遍历,这两指针必将相遇于环的起始点,并且从相遇点开始遍历的指针在相遇时已经遍历过(n-1)次环。那么我们求解起始结点的步骤可以分为两步:第一步,找到相遇点;第二步,根据相遇点找到环的起始点。
代码实现:
ListNode* EntryNodeOfLoop(ListNode* pHead) // 找出环的起始点
{
if(pHead==NULL)
return NULL;
ListNode * slow = pHead;
ListNode * fast = pHead;
while(fast!=NULL) // 找出相遇点
{
fast = fast->next;
if(fast!=NULL)
{
fast = fast->next;
slow = slow->next;
}
if(fast==slow)
break;
}
if(fast==NULL)
return NULL;
slow = pHead; // 一个从开头遍历,一个从相遇点开始遍历
while(slow!=fast) // 相遇时的节点即为环的起始点
{
slow = slow->next;
fast = fast->next;
}
return fast;
}
4. 主程序调用
int main()
{
ListNode * pHead = NULL;
pHead = CreateLinkList(5,5);
bool result = IsHaveCircle(pHead);
if(result)
cout<<"Yes"<<endl;
else
cout<<"No"<<endl;
ListNode * pStart = NULL;
pStart = EntryNodeOfLoop(pHead);
if(pStart==NULL)
cout<<"Not exist the node"<<endl;
else
cout<<"The value of the node is "<<pStart->val<<endl;
cout<<CountCircle(pHead)<<endl;
return 0;
}