zoukankan      html  css  js  c++  java
  • 有环链表

    问题:

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

    解答: 

    1,将所有的遍历过的节点用某个结构存储起来,然后每遍历一个节点,都在这个结构中查找是否遍历过,如果找到有重复,则说明该链表存在循环;如果直到遍历结束,则说明链表不存在循环。

    这个结构我们可以使用hash来做,hash中存储的值为节点的内存地址,(java中可以用object.hashcode()做为key放在一个hashtable中. 这样当hashtable中出现重复key的时候说明此链表上有环)这样查找的操作所需时间为O(1),遍历操作需要O(n),hash表的存储空间需要额外的O(n)。所以整个算法的时间复杂度为O(n),空间复杂度为O(n)。

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

    bool isLoopbyReverse(Node* head)
    {
        Node* cur=head;
        Node* next=head->next;
        cur->next=null;
        while(next!=null)
        {
            //如果回到头结点,那么链表有环
            if(next==head)
            {
                  next->next=cur;
                  return ture
            }
            //将指针翻转
            Node* tmp=cur;
            cur=next;
            next=next->next;
            cur->next=tmp;
        }
        //否则的话是没有环的,我们再把链表反转回去
        next=cur->next;
        cur->next=null;
        while(next!=null)
        {
            Node* tmp=cur;
            cur=next;
            next=next->next;
            cur->next=tmp;
        }
        return false;
    }

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

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

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

    bool hasLoop(Node* head)
    {
        Node* pf=head;//定义快指针,每次移动两个节点
        Node* ps=head;//定义慢指针,每次移动一个节点
        while(true)
        {
            if(pf && pf->next)
                pf=pf->next->next;
            else 
                return false;
            ps=ps->next;
            //快指针如果和慢指针相遇,则说明有环
            if(pf==ps)
                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.

    确定了一个链表有环,我们如何找到环的开始节点? 如何解开这个环? 这些问题的本质就是如何找到有"回边"的那个节点.

    两个指针在环内第一次相遇后,继续在环内移动,当它们再一次相遇时所经过的步数就是这个环的长度,知道了环的长度我们要找到环的开始节点就容易了,我们可以用两个指针,让它们开始时都指向头结点,然后让一个指针向前移动环长的节点,这个时候两个指针同时都以一次移动一个节点的速度向后移动,当它们相遇时,指向的那个节点就是环的开始节点

        //计算环的长度
        int i=0;
        do
        {
            ps=ps->next;
            pf=pf->next->next;
            i++
        }while(ps!=pf);
        //此时i保存的就是环的长度,用两个相距i的指针来找到环的开始节点
        ps=head;
        pf=head;
        int j;
        for(j=0;j<i;j++)
            pf=pf->next;
        j=0;//将j的值重新赋为0,用j来保存ps,pf相遇时移动的次数
        while(ps!=pf)
        {
            ps=ps->next;
            pf=pf->next;
            j++
        }
        cout<<"环的开始位置是:"<<j<<endl;
        //解开环,此时ps,pf都指向环的开始节点,所以移动其中一个指针到环的末尾
        for(j=0; j<=i; j++) 
        { 
            ps = ps->next; 
        } 
        ps->next = NULL;

     

     

     

     

  • 相关阅读:
    二进制包安装MySQL数据库
    Nginx 访问日志轮询切割
    安装Nginx服务
    生产环境常见的HTTP状态码列表
    SSH批量部署服务
    MYSQL数据库的优化
    inotify+rsync实现实时同步部署
    rsync同步架构
    Linux shell脚本编程(三)
    Linux shell脚本编程(二)
  • 原文地址:https://www.cnblogs.com/clownfish/p/2473137.html
Copyright © 2011-2022 走看看