数据结构与算法之链表(四)单链表尾环问题全面分析

引言

单链表的尾环问题是一个比较经典的问题,它涉及的问题如下:
1>给一个单链表,判断其中是否有环的存在;
2>如果存在环,找出环的入口点;
3>如果存在环,求出环上节点的个数;
4>如果存在环,求出链表的长度;
5>如果存在环,求出环上距离任意一个节点最远的点(对面节点);
6>(扩展)如何判断两个无环链表是否相交
7>(扩展)如果相交,求出第一个相交的节点.
下面在分析带环链表的特征的基础上来一一解决这些问题.

尾环链表特征分析

1.下图是尾环链表的结构。我们采用快慢指针的方法,很明显的,这是一个典型的“跑道追及问题”,俩指针进入“跑道”,速度不一样,那么俩指针必然会在某一时刻相遇。


尾环链表(摘自网络).png

因此判断链表是否有环的关键就是判断俩指针是否能相遇:

    /**
     * 判断是否有回环
     *
     * @return
     */
    public boolean isLoop() {
       if (isEmpty()) {
            return false;
        }
        SNode<T> slow = headNode;
        SNode<T> fast = headNode;
        while (slow != null && fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {//俩哥们相遇,则返回true
                return true;
            }
        }
        //如果是单链表,快指针肯定会走到结尾跳出循环
        return false;
    }

2.下面分析入口点问题。如下图:

尾环链表环入口分析(摘自网络).png

说明:a为头到入口的距离,x为入口到相遇点的距离,r为环长度,链表长度为L,设定环的位置以入口为原点,顺时针为正方向。这里为了更加清晰的说理,这里特殊到一般分两种情况分析:
1>环较大,俩指针在环内一周之内相遇:当slow到达入口时,fast走了2a,在环内的位置为2a-a = a,此时fast在环上顺时针领先slow距离为a,相应的slow在环上顺时针"领先"fast的距离为r-a,因为每一步fast都比slow快一步,所以再经过r-a步,fast就可以追上slow了,此时相遇时,slow共走了a+(r-a)=r步,那么在环的位置为r-a,此时有相遇点到入口的顺时针距离正好为a,与头结点到入口的距离相等;
2>环较小,快指针已经在环里浪了n(n>=1)圈才与慢指针相遇:设头结点到相遇点的距离为s,那么fast走了2s,另外fast在环里转了n圈距离为(nr + s),所以有2s = s+nr得s = nr;又有a+x=s=nr,得a + x = (n-1)r + (L - a),推出a = (n-1)*r+(L-a-x);后者为环上相遇点到入口的顺时针距离.前者代表环转圈路程,这个公式表示的现实意义为:设定俩指针,一个指针p从head出发向入口运动,另外一个指针q从相遇点出发在环内顺时针运动,一定步数后一定会在入口相遇,此结论与情况1一致。
因此问题2的实现思路为:找到相遇点,然后分配指针分别从头结点和相遇点出发向前移动,如果相遇则相遇点为入口点.代码如下:

   /**
     * 查找环入口
     *
     * @return
     */
    public SNode<T> findLoopStartNode() {
      if (isEmpty()) {
            return null;
        }
        SNode<T> slow = headNode;
        SNode<T> fast = headNode;
        while (slow != null && fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {//俩哥们相遇,则返回true
                break;
            }
        }

        if (slow == null || fast == null || fast.next == null) return null;//没有环则返回空
        SNode<T> p = headNode;//从头节点出发
        SNode<T> q = slow;//从相遇点出发
        while (p != q) {
            p = p.next;
            q = q.next;
        }
        return p;//相遇点为环入口
    }

3.有了环入口,那么环大小就好说了,从相遇点开始遍历就行:

    /**
     * 获取环大小
     * @return
     */
    public int getLoopSize() {
          if (isEmpty()) {
            return 0;
        }
        SNode<T> slow = headNode;
        SNode<T> fast = headNode;
        while (slow != null && fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {//俩哥们相遇,则返回true
                break;
            }
        }

        if (slow == null || fast == null || fast.next == null) return 0;//没有环则返0
        int size = 1;//环size从1开始
        SNode<T> temp = slow.next;//从相遇点出发
        while (temp != slow) {
            size++;
            temp = temp.next;
        }
        return size;
    }

4.求环链表的长度:环链表的长度为环大小和入口到头节点距离的和:

   /**
     * 获取头节点到入口的距离
     *
     * @return
     */
    public int getLoopEntryDistance() {
        if (isEmpty()) {
            return 0;
        }
        SNode<T> slow = headNode;
        SNode<T> fast = headNode;
        while (slow != null && fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {//俩哥们相遇,则返回true
                break;
            }
        }

        if (slow == null || fast == null || fast.next == null) return size();//没有环则返回单链表长度
        SNode<T> p = headNode;//从头节点出发
        SNode<T> q = slow;//从相遇点出发
        int distance = 0;
        while (p != q) {
            distance++;
            p = p.next;
            q = q.next;
        }
        return distance;
    }

   /**
     * 获得带环链表大小
     * @return
     */
    public int getLoopLinkedListSize(){
        return getLoopSize() + getLoopEntryDistance();
    }

5.对面节点问题。环上距离节点最远的节点为对面节点,如图1和4、2和5、3和6为对面节点,且距离最大为环大小的一半。思路如下:判断节点是否在环上,如果在环上从当前节点前移环大小一半的步数.


对面节点问题.png
   /**
     * 获得对面节点
     *
     * @return
     */
    public SNode<T> getOppositeNode(SNode<T> node) {
        boolean isLoop = false;
        if (isEmpty() || node == null) {
            return null;
        }
        boolean isOnLoop = false;
        //判断节点是否在环上
        SNode<T> startNode = findLoopStartNode();
        if (startNode == null) {//没有环,返回空
            return null;
        }
        int loopSize = getLoopSize();
        for (int i = 0; i < loopSize; i++) {
            if (node == startNode) {
                isOnLoop = true;
                break;
            }
            startNode = startNode.next;
        }
        SNode<T> temp = node;
        if (isOnLoop) {
             //走环一半步数
            for (int i = 0; i < loopSize / 2; i++) {
                temp = temp.next;
            }
        }
        return temp;
    }

6.对于俩扩展问题,可以这样理解:加入链表A和B有交点,那么将其中一个链表首尾相连,那么另一个链表上必然会出现环,问题就转化成问题一,这里不在赘述。
总结:关于单链表,最核心的操作就是玩转节点的遍历、断开和连接操作,这四篇博客基本涵盖了单链表的大部分操作,为什么花费如此多的精力去学习它呢?因为它是基础,包含了节点操作的基本思想,熟悉了它,后面的双向链表、栈、队列就相对容易。当然,还是那句话:学习数据结构就是学习思想。一沙一世界,单链表也有很多东西可以发掘!关于单链表的学习暂时告一段落,如有纰漏欢迎指正!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351