java 判断链表有环_如何判断链表有环

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

题目描述:

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

6371b0841bb8a9d341615d76bf4079be.png

方法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方法:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 //单向链表

2 public classSingleLinkedList {3 private Node head;//标识头节点

4 private int size;//标识链表中节点个数

5

6 publicSingleLinkedList() {7 this.size = 0;8 this.head = null;9 }10

11 //node类

12 private classNode{13 private Object data;//每个节点的数据

14 private Node next;//指向下一个节点的链接

15

16 publicNode(Object data) {17 this.data =data;18 }19 }20

21 /**

22 * 将节点插入链表23 *@paramdata 带插入的值24 */

25 public voidadd(Object data) {26 Node temp =head;27 if (size == 0) {28 head = newNode(data);29 size++;30 return;31 }32 while (temp.next != null) {33 temp =temp.next;34 }35 temp.next = newNode(data);36 size++;37 }38

39 /**

40 * 从头开始遍历节点41 */

42 public voiddisplay() {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 *@paramkey4 *@return

5 */

6 public Object getNode(intkey) {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 returntemp.data;16 }17 temp =temp.next;18 count++;19 }20

21 }22 return null;23 }24

25

26 /**

27 * 从头开始,依次与给定索引位置的节点的值进行比较,如果相同,则返回true,否则false28 *@paramkey29 *@return

30 */

31 public boolean havaSameElement(intkey) {32 boolean flag = false;33 int count = 0;34 Node temp =head;35 while (temp != null && count

45 }46

47 /**

48 * 方式149 *@return

50 */

51 public booleanisAnnulate1() {52 boolean flag = false;53 for (int i = 1; i < size; i++) {54 if(havaSameElement(i)) {55 flag = true;56 break;57 }58 }59 returnflag;60 }

方法2:

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

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

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

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

方法2如下:

1 /**

2 * 方式23 *@return

4 */

5 public booleanisAnnulate2() {6 boolean flag = false;7 Set 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 returnflag;17

18 }

方法3:

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

f000aee3e50acfdfc9372a503c5d9005.png

我们假设两位跑步的同学速度始终不变,即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 publicNode 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 returntortoise;10 }11 }12 return null;13 }14

15 publicObject 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 returnstartNode.data;30

31 }

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

完整代码如下:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagejudgeLinkedListCircle;2

3 importjava.util.HashSet;4 importjava.util.Set;5

6

7 /**

8 * 单向链表9 *@authorCone10 *@since2019年7月27日11 *12 */

13 public classSingleLinkedList {14 private Node head;//标识头节点

15 private int size;//标识链表中节点个数

16

17 publicSingleLinkedList() {18 this.size = 0;19 this.head = null;20 }21

22 //node类

23 private classNode{24 private Object data;//每个节点的数据

25 private Node next;//指向下一个节点的链接

26

27 publicNode(Object data) {28 this.data =data;29 }30 }31

32 /**

33 * 将节点插入链表34 *@paramdata 带插入的值35 */

36 public voidadd(Object data) {37 Node temp =head;38 if (size == 0) {39 head = newNode(data);40 size++;41 return;42 }43 while (temp.next != null) {44 temp =temp.next;45 }46 temp.next = newNode(data);47 size++;48 }49

50 /**

51 * 从头开始遍历节点52 */

53 public voiddisplay() {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 *@paramkey72 *@return

73 */

74 public Object getNode(intkey) {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 returntemp.data;84 }85 temp =temp.next;86 count++;87 }88

89 }90 return null;91 }92

93

94 /**

95 * 从头开始,依次与给定索引位置的节点的值进行比较,如果相同,则返回true,否则false96 *@paramkey97 *@return

98 */

99 public boolean havaSameElement(intkey) {100 boolean flag = false;101 int count = 0;102 Node temp =head;103 while (temp != null && count

113 }114

115 /**

116 * 方式1117 *@return

118 */

119 public booleanisAnnulate1() {120 boolean flag = false;121 for (int i = 1; i < size; i++) {122 if(havaSameElement(i)) {123 flag = true;124 break;125 }126 }127 returnflag;128 }129

130

131 /**

132 * 方式2133 *@return

134 */

135 public booleanisAnnulate2() {136 boolean flag = false;137 Set 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 returnflag;147

148 }149

150 publicNode 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 returntortoise;159 }160 }161 return null;162 }163

164 /**

165 * 方式3166 *@return

167 */

168 publicObject 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 returnstartNode.data;183

184 }185

186 }

View Code

如有错误,欢迎指正。

代码已上传至github:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值