一、问题概述
环形链表就是在链表中存在环的情况,如果出现了环形链表,在程序中可能造成死循环的情况,导致严重的损失,因此需要判断一个链表是否成了环。如果有条件,可以为链表的节点设置一个visit属性,记录每次访问的次数,访问过一次就加1,如果出现了节点的访问次数为2,说明一定存在环。但有时,我们并不需要为该操作去修改弥足珍贵的数据结构,下面我们采用三种方法讲解如何求解环形链表的问题
二、求解方法
我们先假设链表的数据结构如下:(来源于Leetcode141题)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
一、基于循环次数判断的暴力方法
如果一个链表不成环,那么它就是可以穷尽的,我们假定你的链表的节点的个数小于10^4个,如果比这个还多的话,也许你需要用到树型结构了,不然你的速度会比乌龟还慢。
因此我们可以一直循环,循环的时候,要么是因为到达了指针的尾部NULL,要么是因为循环次数太多超越了节点的个数,循环结束时判断循环次数就可以了。
C++代码如下:
class Solution {
public:
bool hasCycle(ListNode *head) {
if(head==NULL){
return 0;//没有环
}
struct ListNode * temp = head->next;
int count=0;
while(temp!=NULL&&count<100001){
temp=temp->next;
count++;
}
if(count>=100001){
return 1;
}
return 0;
}
};
二、借助Hash表
每次访问节点的时候都判断是否在hash表中,如果没有就加入,如果存在就说明有环,如果没有环,一个节点只可能被访问一次,这和我们企图修改链表的数据结构去维护访问次数是等价的,只是以一种不影响节点内容的方式去完成。
Java代码如下:
public class Solution{
public boolean hasCycle(ListNode head){
//利用hash表保存 看该数组是否被遍历过
Set<ListNode> buf = new HashSet<ListNode>();
while(head!=null){
if(!buf.add(head)){
return true;
}
head=head.next;
}
return false;
}
}
三、快慢指针
其实到这里,才是本问题最精彩的解法(前面都是在扯淡),这个方法十分精巧,是伟大的数学家Floyd发明的,即快慢指针的解法。
基于Floyd环的思想,定义两种类型的指针,slow慢指针前进步长为1,快指针前进步长为2(在第一次前进后要判断前进后是否到达了NULL)。这样,如果在循环的过程中,快指针追上了慢指针,那么说明成了环。其中为什么一定能追上的具体的数学证明需要沉下心来仔细Google。
C语言代码如下:
bool hasCycle(struct ListNode *head) {
if(head==NULL){
return 0;//不成环
}
//快慢指针
struct ListNode * slow = head;
struct ListNode * fast = head;
while(fast!=NULL){
//fast比slow快 只需要判断fast有没有越界就可以了
slow = slow->next;
fast = fast->next;
if(fast!=NULL){
fast=fast->next;//避免操纵空指针
}
else{
break;
}
//放在后面 处理初始状态
if(slow==fast){
return 1;//成环
}
}
return 0;
}
此外,为什么笔者要这么蛋疼写三种解法和三种不同的语言求解呢?其实本人就是从菜鸡算法一步步向大佬算法挖掘的,只有一步一步走,才能到达幸福的彼岸。同时,用多种语言也旨在说明,算法是一种思想,语言只是一种工具。
多谢大家指正,共勉!