zoukankan      html  css  js  c++  java
  • 如何判断链表有环

     前天晚上临睡觉前看到了公众号脚本之家推送的一篇文章,文章内容是一道算法题,并给出了思路解释,但没有具体源码实现,这让我觉得少了点什么,于是,趁周末,我补齐了缺失的内容,好了,no code, no bb,我们开始吧。

    题目描述:

    有一个单向链表,链表中有可能出现“环”,就像下图这样。那么,如何用程序来判断该链表是否为有环链表呢?(图片来自公众号)

    方法1:

    从头开始遍历整个单链表,每遍历一个新节点,就把它与之前遍历过的节点进行比较,如果值相同,那么就认为这两个节点是一个节点,则证明链表有环,停止遍历,否则继续遍历下一个节点,重复刚才的操作,直到遍历结束。结合上图来说,流程是这样的:

    ① 得到 "3"节点,把它与第一个节点 “5”比较,值不相等,继续遍历下一个节点 “7”。(从第二个节点开始遍历)

    ② 得到 “7”节点,把它依次与 “5”、“3”比较,值不相等,继续遍历下一个节点 “2”

    ③ 重复以上操作,直到遍历完节点 “1”

    ④ 得到节点 “2”,把它依次与 “5”、“3”、“7”、“2”、“6”、“8”、“1”进行比较,当比较到节点 “2”时,值相等,遍历结束,证明该链表有环。

    假设链表的节点数量为n,则该解法的时间复杂度为O(n2),由于没有创建额外的存储空间,所以空间复杂度为O(1)

    链表的实现比较简单,我只写了一个add方法,一个display方法:

     1 //单向链表
     2 public class SingleLinkedList {
     3     private Node head;//标识头节点
     4     private int size;//标识链表中节点个数
     5     
     6     public SingleLinkedList() {
     7         this.size = 0;
     8         this.head = null;
     9     }
    10     
    11     //node类
    12     private class Node{
    13         private Object data;//每个节点的数据
    14         private Node next;//指向下一个节点的链接
    15         
    16         public Node(Object data) {
    17             this.data = data;
    18         }
    19     }
    20     
    21     /**
    22      * 将节点插入链表
    23      * @param data 带插入的值
    24      */
    25     public void add(Object data) {
    26         Node temp = head;
    27         if (size == 0) {
    28             head = new Node(data);
    29             size++;
    30             return;
    31         }
    32         while (temp.next != null) {
    33             temp = temp.next;
    34         }
    35         temp.next = new Node(data);
    36         size++;
    37     }
    38     
    39     /**
    40      * 从头开始遍历节点
    41      */
    42     public void display() {
    43         if (size > 0) {
    44             Node node = head;
    45             if (size == 1) {
    46                 System.out.println("[" + node.data + "]");
    47                 return;
    48             }
    49             while (node != null) {
    50                 System.out.println(node.data);
    51                 node = node.next;
    52             }
    53         } else {
    54             System.out.println("[]");
    55         }
    56     }
    57 }
    View Code

    方法1如下:

     1     /**
     2      * 根据索引得到链表的某个节点的值
     3      * @param key
     4      * @return
     5      */
     6     public Object getNode(int key) {
     7         
     8         if (key < 0 || key > size - 1) {
     9             throw new ArrayIndexOutOfBoundsException("越界!");
    10         } else {
    11             Node temp = head;
    12             int count = 0;
    13             while (temp != null) {
    14                 if (count == key) {
    15                     return temp.data;
    16                 }
    17                 temp = temp.next;
    18                 count++;
    19             }
    20             
    21         }
    22         return null;
    23     }
    24     
    25     
    26     /**
    27      * 从头开始,依次与给定索引位置的节点的值进行比较,如果相同,则返回true,否则false
    28      * @param key
    29      * @return
    30      */
    31     public boolean havaSameElement(int key) {
    32         boolean flag = false;
    33         int count = 0;
    34         Node temp = head;
    35         while (temp != null && count < key) {
    36             if (temp.data == getNode(key)) {
    37                 flag = true;
    38                 return flag;
    39             }
    40             count++;
    41             temp = temp.next;
    42         }
    43         return flag;
    44         
    45     }
    46     
    47     /**
    48      * 方式1
    49      * @return
    50      */
    51     public boolean isAnnulate1() {
    52         boolean flag = false;
    53         for (int i = 1; i < size; i++) {
    54             if (havaSameElement(i)) {
    55                 flag = true;
    56                 break;
    57             }
    58         }
    59         return flag;
    60     }

    方法2:


    这种方法用到了HashSet中add方法去重的特点,思路是这样的:

    ① new一个HashSet,用来存储之前遍历过的节点值

    ②从头节点head开始,依次遍历链表中的节点,并把它add到集合中

    ③ 如果在集合中已经有一个相同的值,那么会返回false,这样便证明链表有环,退出遍历

    方法2如下:

     1     /**
     2      * 方式2
     3      * @return
     4      */
     5     public boolean isAnnulate2() {
     6         boolean flag = false;
     7         Set<Object> set = new HashSet<>();
     8         Node temp = head;
     9         while (temp != null) {
    10             if (!set.add(temp.data)) {
    11                 flag = true;
    12                 break;
    13             }
    14             temp = temp.next;
    15         }
    16         return flag;
    17 
    18     }

    方法3:

    定义两个指针tortoise与rabbit,让它们一开始均指向head头节点,之后,tortoise每次向后移动一个节点,rabbit每次向后移动2个节点,只要这个链表是有环的,它们必定会在某一次移动完后相遇,什么?你问我为什么?我们来思考这样一个问题,两个人在运动场跑步,他们的起始位置都是一样的,当开跑后,只有在两种情况下,他们的位置会重合,第一就是他们的速度始终一致,第二就是跑得快的那个人套圈,如下图所示:

    我们假设两位跑步的同学速度始终不变,即tortoise以V的速度进行移动,rabbit以2V的速度进行移动,在经过了相同的时间T后,他们相遇了,此时tortoise移动的距离为VT,而rabbit移动的距离为2VT,他们移动的距离差VT,即为这个链表中 “环”的周长,如上图所示,节点A表示为环入口,节点B表示他们第一次相遇,我们将head头节点至节点A的距离记为a,将节点A至他们第一次相遇的节点B的距离记为b,通过我们刚才的分析,不难得出,tortoise移动的距离VT = a + b,等量代换,他们移动的距离差也为 a+ b,所以链表中环的周长为 a + b,现在已知节点A至节点B的距离为b,那么节点B至节点A的距离便为a,至此,发现什么了?head头节点到节点A的距离同样为a,我们建立一个指针 start 指向头节点,它与B节点处的tortoise同时以一个节点的速度进行移动,一段时间后,它们将在环入口相遇。我们不光能证明一个链表是否有环,还能找到环的入口。

    方法3如下:

     1     public Node getIntersect() {
     2         Node temp = head;
     3         Node tortoise = temp;
     4         Node rabbit = temp;
     5         while (rabbit != null && rabbit.next != null) {
     6             tortoise = tortoise.next;
     7             rabbit = rabbit.next.next;
     8             if (tortoise == rabbit) {
     9                 return tortoise;
    10             }
    11         }
    12         return null;
    13     }
    14     
    15     public Object isAnnulate3() {
    16         Node temp = head;
    17         if (temp == null) {
    18             return null;
    19         }
    20         Node intersect = getIntersect();
    21         if (intersect == null) {
    22             return null;
    23         }
    24         Node startNode = head;
    25         while (startNode != intersect) {
    26             startNode = startNode.next;
    27             intersect = intersect.next;
    28         }
    29         return startNode.data;
    30         
    31     }

    我要说明的是,方法3中的代码只是 “伪代码”,它并不能真的证明链表有环,并返回环的入口节点值。至于为什么,我的理解是,因为单链表的结构特点,它并不会真的存在 “环”,我们这里说的环只是把它抽象出来,实际上,单链表中一个节点只保留有对它后面那个节点的引用,并没有对它前面节点的引用,所以,并不存在真正的 “环”,不过,这种思路还是值得学习的。假设链表的节点数量为n,则该算法的时间复杂度为O(n),除指针外,没有占用任何额外的存储空间,所以空间复杂度为O(1)。

    完整代码如下:

      1 package judgeLinkedListCircle;
      2 
      3 import java.util.HashSet;
      4 import java.util.Set;
      5 
      6 
      7 /**
      8  * 单向链表
      9  * @author Cone
     10  * @since 2019年7月27日
     11  *
     12  */
     13 public class SingleLinkedList {
     14     private Node head;//标识头节点
     15     private int size;//标识链表中节点个数
     16     
     17     public SingleLinkedList() {
     18         this.size = 0;
     19         this.head = null;
     20     }
     21     
     22     //node类
     23     private class Node{
     24         private Object data;//每个节点的数据
     25         private Node next;//指向下一个节点的链接
     26         
     27         public Node(Object data) {
     28             this.data = data;
     29         }
     30     }
     31     
     32     /**
     33      * 将节点插入链表
     34      * @param data 带插入的值
     35      */
     36     public void add(Object data) {
     37         Node temp = head;
     38         if (size == 0) {
     39             head = new Node(data);
     40             size++;
     41             return;
     42         }
     43         while (temp.next != null) {
     44             temp = temp.next;
     45         }
     46         temp.next = new Node(data);
     47         size++;
     48     }
     49     
     50     /**
     51      * 从头开始遍历节点
     52      */
     53     public void display() {
     54         if (size > 0) {
     55             Node node = head;
     56             if (size == 1) {
     57                 System.out.println("[" + node.data + "]");
     58                 return;
     59             }
     60             while (node != null) {
     61                 System.out.println(node.data);
     62                 node = node.next;
     63             }
     64         } else {
     65             System.out.println("[]");
     66         }
     67     }
     68     
     69     /**
     70      * 根据索引得到链表的某个节点的值
     71      * @param key
     72      * @return
     73      */
     74     public Object getNode(int key) {
     75         
     76         if (key < 0 || key > size - 1) {
     77             throw new ArrayIndexOutOfBoundsException("越界!");
     78         } else {
     79             Node temp = head;
     80             int count = 0;
     81             while (temp != null) {
     82                 if (count == key) {
     83                     return temp.data;
     84                 }
     85                 temp = temp.next;
     86                 count++;
     87             }
     88             
     89         }
     90         return null;
     91     }
     92     
     93     
     94     /**
     95      * 从头开始,依次与给定索引位置的节点的值进行比较,如果相同,则返回true,否则false
     96      * @param key
     97      * @return
     98      */
     99     public boolean havaSameElement(int key) {
    100         boolean flag = false;
    101         int count = 0;
    102         Node temp = head;
    103         while (temp != null && count < key) {
    104             if (temp.data == getNode(key)) {
    105                 flag = true;
    106                 return flag;
    107             }
    108             count++;
    109             temp = temp.next;
    110         }
    111         return flag;
    112         
    113     }
    114     
    115     /**
    116      * 方式1
    117      * @return
    118      */
    119     public boolean isAnnulate1() {
    120         boolean flag = false;
    121         for (int i = 1; i < size; i++) {
    122             if (havaSameElement(i)) {
    123                 flag = true;
    124                 break;
    125             }
    126         }
    127         return flag;
    128     }
    129     
    130     
    131     /**
    132      * 方式2
    133      * @return
    134      */
    135     public boolean isAnnulate2() {
    136         boolean flag = false;
    137         Set<Object> set = new HashSet<>();
    138         Node temp = head;
    139         while (temp != null) {
    140             if (!set.add(temp.data)) {
    141                 flag = true;
    142                 break;
    143             }
    144             temp = temp.next;
    145         }
    146         return flag;
    147 
    148     }
    149     
    150     public Node getIntersect() {
    151         Node temp = head;
    152         Node tortoise = temp;
    153         Node rabbit = temp;
    154         while (rabbit != null && rabbit.next != null) {
    155             tortoise = tortoise.next;
    156             rabbit = rabbit.next.next;
    157             if (tortoise == rabbit) {
    158                 return tortoise;
    159             }
    160         }
    161         return null;
    162     }
    163     
    164     /**
    165      * 方式3
    166      * @return
    167      */
    168     public Object isAnnulate3() {
    169         Node temp = head;
    170         if (temp == null) {
    171             return null;
    172         }
    173         Node intersect = getIntersect();
    174         if (intersect == null) {
    175             return null;
    176         }
    177         Node startNode = head;
    178         while (startNode != intersect) {
    179             startNode = startNode.next;
    180             intersect = intersect.next;
    181         }
    182         return startNode.data;
    183         
    184     }
    185 
    186 }
    View Code

    如有错误,欢迎指正。

    代码已上传至github:

    https://github.com/Thinker-Mars/ByteDance

  • 相关阅读:
    pow()函数结果强制转化为int造成误差的分析
    warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
    博客园鼠标点击特效代码
    codeblocks更改颜色主题
    codeblocks1712设置中文
    SQl
    项目中nodejs包高效升级插件npm-check-updates
    正则表达式的整理(将金钱数变成带有千分位)
    从一个数组中过滤出另外一个数组中相关字段相等的数据
    IONIC3 打包安卓apk详细过程(大量图文)
  • 原文地址:https://www.cnblogs.com/cone/p/11257063.html
Copyright © 2011-2022 走看看