如何检查一个单向链表上是否有环?

1, 最简单的方法, 用一个指针遍历链表, 每遇到一个节点就把他的内存地址(java中可以用object.hashcode())做为key放在一个hashtable中. 这样当hashtable中出现重复key的时候说明此链表上有环. 这个方法的时间复杂度为O(n), 空间同样为O(n).

2, 使用反转指针的方法, 每过一个节点就把该节点的指针反向:

Boolean reverse(Node *head) {
    Node *curr = head;
    Node *next = head->next;
    curr->next = NULL;

    while(next!=NULL) {
        if(next == head) { /* go back to the head of the list, so there is a loop */
            next->next = curr;
            return TRUE;

        }

        Node *temp = curr;
        curr = next;
        next = next->next;
        curr->next = temp;
    }

    /* at the end of list, so there is no loop, let‘s reverse the list back */
    next = curr->next;
    curr ->next = NULL;
    while(next!=NULL) {
        Node *temp = curr;
        curr = next;
        next = next->next;
        curr->next = temp;
    }
    return FALSE;
}

看上去这是一种奇怪的方法: 当有环的时候反转next指针会最终走到链表头部; 当没有环的时候反转next指针会破坏链表结构(使链表反向), 所以需要最后把链表再反向一次. 这种方法的空间复杂度是O(1), 实事上我们使用了3个额外指针;而时间复杂度是O(n), 我们最多2次遍历整个链表(当链表中没有环的时候).

这个方法的最大缺点是在多线程情况下不安全, 当多个线程都在读这个链表的时候, 检查环的线程会改变链表的状态, 虽然最后我们恢复了链表本身的结构, 但是不能保证其他线程能得到正确的结果.

3, 这是一般面试官所预期的答案: 快指针和慢指针

Boolean has_loop(Node *head) {
    Node *pf = head; /* fast pointer */
    Node *ps = head; /* slow pointer */

    while(true) {
        if(pf && pf->next)
            pf = pf->next->next;
        else
            return FALSE;

        ps = ps->next;

        if(ps == pf)
            return TRUE;
    }
}

需要说明的是, 当慢指针(ps)进入环之后, 最多会走n-1步就能和快指针(pf)相遇, 其中n是环的长度. 也就是说快指针在环能不会跳过慢指针, 这个性质可以简单的用归纳法来证明.
(1)当ps在环中位置i, 而pf在环中位置i-1, 则在下一个iteration, ps会和pf在i+1相遇.
(2)当ps在环中位置i, 而pf在环中位置i-2, 则在下一个iteration, ps在i+1, pf在i, 于是在下一个iteration ps和pf会相遇在i+2位置
(3)和上面推理过程类似, 当ps在i, pf在i+1, 则他们会经过n-1个iteration在i+n-1的位置相遇. 于是慢指针的步数不会超过n-1.

扩展:

这个问题还有一些扩展, 例如, 如何找到环的开始节点? 如何解开这个环? 这些问题的本质就是如何找到有"回边"的那个节点.

我们可以利用方法3的一个变形来解决这个问题:

Boolean has_loop(Node *head) {
    Node *pf = head; /* fast pointer */
    Node *ps = head; /* slow pointer */

    while(true) {  /* step 1, is there a loop? */
        if(pf && pf->next)
            pf = pf->next->next;
        else
            return FALSE;

        ps = ps->next;

        if(ps == pf)
            break;
    }

    /* step 2, how long is the loop */
    int i = 0;
    do {
        ps = ps->next;
        pf = pf->next->next;
        i++;
    } while(ps!=pf)

    /* step 3, use 2 addtional pointers with distance i to break the loop */
    ps = head;
    pf = head;

    int j;
    for(j=0; j<i; j++) { pf = pf->next; }

    j = 0;
    while(ps!=pf) {
        ps = ps->next;
        pf = pf->next;
        j++;
    }

    printf("loop begins at position %d, node address is %x", j, ps);

    /*step 4, break the loop*/
    for(j=0; j<=i; j++) { ps = ps->next; } //step i-1 in the loop from loop beginning
    ps->next = NULL; // break the loop
    return TRUE;
}
时间: 2024-10-29 05:08:34

如何检查一个单向链表上是否有环?的相关文章

单向链表上是否有环

详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt115 有一个单链表,其中可能有一个环,也就是某个节点的next指向的是链表中在它之前的节点,这样在链表的尾部形成一环. 问题: 1.如何判断一个链表是不是这类链表?2.如果链表为存在环,如果找到环的入口点? 解答: 1.最简单的方法, 用一个指针遍历链表, 每遇到一个节点就把他的内存地址(java中可以用object.hashcode())做为key放在一个hashtabl

如何判断一个单向链表是否为回文链表(Palindrome Linked List)

题目:给定一个单向链表,判断它是不是回文链表(即从前往后读和从后往前读是一样的).原题见下图,还要求了O(n)的时间复杂度O(1)的空间复杂度. 我的思考: 1,一看到这个题目,大脑马上想到的解决方案就是数组.遍历链表,用数组把数据存下来,然后再进行一次遍历,同时用数组反向地与之比较,这样就可以判断是否回文.这个方法时间复杂度是O(n),达到了要求,然而空间复杂度显然不满足要求.所以,开数组这一类的方法显然不是最佳的. 2,既然要满足O(1)的空间复杂度,我就想到了用一个变量来存储这些数据,恰好

【编程题目】输入一个单向链表,输出该链表中倒数第 k 个结点

第 13 题(链表):题目:输入一个单向链表,输出该链表中倒数第 k 个结点.链表的倒数第 0 个结点为链表的尾指针.链表结点定义如下: struct ListNode {int m_nKey;ListNode* m_pNext;}; 我的思路:先翻转链表,再从翻转后的链表的头向尾数k-1个,返回,再次翻转链表. 代码如下:注意这个思路非常差.差的原因是:如果只是用最原始的方法,先遍历一遍计数,再遍历一遍找倒数第k个,需要遍历两遍.但我的思路,翻转两次链表就要遍历两遍.还要在走k-1步找倒数第k

13输入一个单向链表,输出该链表中倒数第k个结点。链表的倒数第0个结点为链表的尾指针。

转载请注明出处:http://www.cnblogs.com/wuzetiandaren/p/4250795.html 声明:现大部分文章为寻找问题时在网上相互转载,此博是为自己做个记录记录,方便自己也方便有类似问题的朋友,本文的思想也许有所借鉴,但源码均为本人实现,如有侵权,请发邮件表明文章和原出处地址,我一定在文章中注明.谢谢. 题目:输入一个单向链表,输出该链表中倒数第k个结点.链表的倒数第0个结点为链表的尾指针. 题目分析: 1.链表的倒数第0个结点为链表的尾指针,设为r,则r指向最后一

输入一个单向链表,输出该链表中倒数第 k 个结点

class ListNode { public: ListNode() { pNext = NULL; nValue = 0; } ListNode* pNext; int nValue; }; ListNode* CreaList() { int nValue; ListNode* Head = NULL; ListNode* ListIndex = NULL; while(cin >> nValue) { if (Head == NULL) { Head = new ListNode();

C++异常机制的实现方式和开销分析 (大图,编译器会为每个函数增加EHDL结构,组成一个单向链表,非常著名的“内存访问违例”出错对话框就是该机制的一种体现)

白杨 http://baiy.cn 在我几年前开始写<C++编码规范与指导>一文时,就已经规划着要加入这样一篇讨论 C++ 异常机制的文章了.没想到时隔几年以后才有机会把这个尾巴补完 :-). 还是那句开场白:“在恰当的场合使用恰当的特性” 对每个称职的 C++ 程序员来说都是一个基本标准.想要做到这点,就必须要了解语言中每个特性的实现方式及其时空开销.异常处理由于涉及大量底层内容,向来是 C++ 各种高级机制中较难理解和透彻掌握的部分.本文将在尽量少引入底层细节的前提下,讨论 C++ 中这一

Python3中定义一个单向链表

链表是由节点构成的,一个指针代表一个方向,如果一个构成链表的节点都只包含一个指针,那么这个链表就是单向链表. 单向链表中的节点不光有代表方向的指针变量,也有值变量.所以我们定义链表,就是要定义链表中的节点,对链表的操作最后也就是对节点的操作. 这些包含数据的节点们在一种指定的结构下连接起来,成为了一种数据结构——单向链表.以上是我对单向链表的理解. 以下是我用python3对单向链表这种数据结构的一种实现: ''' Python3版单向链表-单向链表简称单链表 单链表中所包含的基本操作: 初始化

判断单向链表中是否有环和查找环的入口

快慢指针 算法描述 定义两个指针slow, fast.slow指针一次走1个结点,fast指针一次走2个结点.如果链表中有环,那么慢指针一定会再某一个时刻追上快指针(slow == fast).如果没有环,则快指针会第一个走到NULL. 实现 结点定义如下: class Node { public Node next; public Object data; public static int sequence = 0; } 算法: /** * 快慢指针 * @param head * @ret

推断单向链表中是否有环和查找环的入口

快慢指针 算法描写叙述 定义两个指针slow, fast. slow指针一次走1个结点,fast指针一次走2个结点.假设链表中有环,那么慢指针一定会再某一个时刻追上快指针(slow == fast).假设没有环,则快指针会第一个走到NULL. 实现 结点定义例如以下: class Node { public Node next; public Object data; public static int sequence = 0; } 算法: /** * 快慢指针 * @param head *