如何判斷鏈表有環

                                                    如何判斷鏈表有環

前天晚上臨睡覺前看到了公衆號腳本之家推送的一篇文章,文章內容是一道算法題,並給出了思路解釋,但沒有具體源碼實現,這讓我覺得少了點什麼,於是,趁週末,我補齊了缺失的內容,好了,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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章