链表(篇6)Floyd破圈法以及其改进方法检查删除链表中的环

来源:互联网 发布:英语词典哪个好 知乎 编辑:程序博客网 时间:2024/06/03 20:43

检查给定的链表是否包含环,如果环存在,则删除环并返回true。如果列表不包含环,则返回false。下图显示了一个带有环的链表。必须将以下列表更改为1-> 2-> 3-> 4-> 5-> NULL。

这里写图片描述


思路:
要删除循环,我们需要做的是获取指向循环的最后一个节点的指针。例如,上图中值为5的节点。一旦我们有指向最后一个节点的指针,我们可以使这个节点的下一个为NULL。可以使用Floyd循环检测算法来检测和删除循环。在Floyd算法中,慢速和快速指针在循环节点处相遇。可以利用这个循环节点来去除循环。
Floyd的算法
如果有限状态机、迭代函数或者链表上存在环,那么在某个环上以不同速度前进的2个指针必定会在某个时刻相遇。同时显然地,如果从同一个起点(即使这个起点不在某个环上)同时开始以不同速度前进的2个指针最终相遇,那么可以判定存在一个环,且可以求出2者相遇处所在的环的起点与长度。


解法1
在Floyd算法中采用两个不同速度的ptr1,ptr2,它们相遇时肯定处于环中,如上图中的2,3,4,5 中的某一位置。使用一个新节点p从1开始,ptr2围着环2-3-4-5转一圈,如果ptr2碰到了p则肯定是p在2的位置ptr2恰好在5的位置。


代码:


// 检测并删除链表中的环class LinkedList {    static Node head;    static class Node {        int data;        Node next;        Node(int d) {            data = d;            next = null;        }    }    // Floyd方法检测是否有环    //其中fast每次向前移动2个节点,slow每次向前移动一个节点,如果有环则    //fast和slow一定会在环中相遇    int detectAndRemoveLoop(Node node) {        Node slow = node, fast = node;        while (slow != null && fast != null && fast.next != null) {            slow = slow.next;            fast = fast.next.next;            // If slow and fast meet at same point then loop is present            if (slow == fast) {                removeLoop(slow, node);                return 1;            }        }        return 0;    }    //删除环    void removeLoop(Node loop, Node curr) {        Node ptr1 = null, ptr2 = null;        /* ptr1从头开始检测是否是环的开始节点 */        ptr1 = curr;        while (1 == 1) {            /*             ptr2围绕着环转一圈如果碰到ptr1则可知ptr1是环的开始节点            */            ptr2 = loop;            while (ptr2.next != loop && ptr2.next != ptr1) {                ptr2 = ptr2.next;            }            if (ptr2.next == ptr1) {                break;            }            /* 如果没碰到ptr1,ptr1向后移动一位*/            ptr1 = ptr1.next;        }        /*ptr2.next与ptr1相遇时可知ptr2是环的最后一个节点,使ptr2指向null来破环 */        ptr2.next = null;    }    void printList(Node node) {        while (node != null) {            System.out.print(node.data + " ");            node = node.next;        }    }    // Driver program to test above functions    public static void main(String[] args) {        LinkedList list = new LinkedList();        list.head = new Node(50);        list.head.next = new Node(20);        list.head.next.next = new Node(15);        list.head.next.next.next = new Node(4);        list.head.next.next.next.next = new Node(10);        // Creating a loop for testing         head.next.next.next.next.next = head.next.next;        list.detectAndRemoveLoop(head);        System.out.println("Linked List after removing loop : ");        list.printList(head);    }}

解法2(优化解法1)

此方法也依赖于Floyd的周期检测算法。
1)使用Floyd的循环检测算法检测循环并获取指向循环节点的指针。
2)计算循环中的节点数。让计数为k。
3)将一个指针固定到头部,将另一个指针固定到头部的第k个节点。
4)以相同的速度移动两个指针,它们将在循环开始节点处相遇。
5)获取指向循环的最后一个节点的指针,并将其下一个作为NULL。


代码


class LinkedList {    static Node head;    static class Node {        int data;        Node next;        Node(int d) {            data = d;            next = null;        }    }    //检测链表中是否有环    int detectAndRemoveLoop(Node node) {        Node slow = node, fast = node;        while (slow != null && fast != null && fast.next != null) {            slow = slow.next;            fast = fast.next.next;            // 如果fast和slow相遇则可知有环调用removeLoop删除环            if (slow == fast) {                removeLoop(slow, node);                return 1;            }        }        return 0;    }    // 删除环    void removeLoop(Node loop, Node head) {        Node ptr1 = loop;        Node ptr2 = loop;        //计算环中的节点个数        int k = 1, i;        while (ptr1.next != ptr2) {            ptr1 = ptr1.next;            k++;        }        // 使ptr1指向头结点,        ptr1 = head;        // ptr2指向第k个节点。        ptr2 = head;        for (i = 0; i < k; i++) {            ptr2 = ptr2.next;        }        /*  同时移动ptr1,ptr2可知当ptr1与ptr2相遇时正好是环的最后一个节点与环的开始节点相遇 */        while (ptr2 != ptr1) {            ptr1 = ptr1.next;            ptr2 = ptr2.next;        }        ptr2 = ptr2.next;        while (ptr2.next != ptr1) {            ptr2 = ptr2.next;        }        /* 使环的最后一个节点指向null*/        ptr2.next = null;    }}

解法3(优化解法2)
我们不需要计算循环中的节点数。在检测到循环之后,如果我们从头开始slow并且以相同的速度移动slow和fast。那么slow和fast最终会在环的开始出相遇。
下面是数学推导:
这里写图片描述

fast指针移动的距离是慢指针的两倍:
(m+n*x+k)=2*(m+n*y+k)
由上可得到:
m+k=(x-2y)*n;
也就是说m+k是n的倍数。
因此,如果我们开始以相同的速度移动两个指针,使得一个指针(慢指针)从链表的头节点开始并且其他指针(快指针)从会合点开始。当慢指针到达链表的开头(已经做了m步)。快速指针也会移动m步,因为他们现在移动相同的速度。由于m + k是n的倍数,并且从k开始快速,它们将在开始时满足。

class LinkedList {    static Node head;    static class Node {        int data;        Node next;        Node(int d) {            data = d;            next = null;        }    }    void detectAndRemoveLoop(Node node) {        Node slow = node;        Node fast = node.next;        // 使用slow和fast来找环        while (fast != null && fast.next != null) {            if (slow == fast) {                break;            }            slow = slow.next;            fast = fast.next.next;        }        /* 如果环存在 */        if (slow == fast) {            slow = node;            while (slow != fast.next) {                slow = slow.next;                fast = fast.next;            }            fast.next = null; /* 删除环*/        }    } }
0 0