如何检查一个单向链表上是否有环?

2024-02-15 13:48

本文主要是介绍如何检查一个单向链表上是否有环?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1, 最简单的方法, 用一个指针遍历链表, 每遇到一个节点就把他的内存地址(java中可以用object.hashcode())做为key放在一个hashtable中. 这样当hashtable中出现重复key的时候说明此链表上有环. 这个方法的时间复杂度为O(n), 空间同样为O(n). 

2, 使用反转指针的方法, 每过一个节点就把该节点的指针反向:

  Boolean reverse(Node *head) {

   Node *curr = head;

   Node *next = head->next;

   curr->next = NULL;

   while(next!=NULL) {

    if(next == head) { /* go back to the head of the list, so there is a loop */

      next->next = curr;

      return TRUE;

    }

    Node *temp = curr;

    curr = next;

    next = next->next;

    curr->next = temp;

   }

   /* at the end of list, so there is no loop, let's reverse the list back */

   next = curr->next;

   curr ->next = NULL;

   while(next!=NULL) {

    Node *temp = curr;

    curr = next;

    next = next->next;

    curr->next = temp;

   }

   return FALSE;

  }

看上去这是一种奇怪的方法: 当有环的时候反转next指针会最终走到链表头部; 当没有环的时候反转next指针会破坏链表结构(使链表反向), 所以需要最后把链表再反向一次. 这种方法的空间复杂度是O(1), 实事上我们使用了3个额外指针;而时间复杂度是O(n), 我们最多2次遍历整个链表(当链表中没有环的时候).

这个方法的最大缺点是在多线程情况下不安全, 当多个线程都在读这个链表的时候, 检查环的线程会改变链表的状态, 虽然最后我们恢复了链表本身的结构, 但是不能保证其他线程能得到正确的结果.


3, 这是一般面试官所预期的答案: 快指针和慢指针

Boolean has_loop(Node *head) {

  Node *pf = head; /* fast pointer */

  Node *ps = head; /* slow pointer */

  while(true) {

    if(pf && pf->next)

      pf = pf->next->next;

    else

      return FALSE;

    ps = ps->next;

    if(ps == pf)

      return TRUE;

  }

}

需要说明的是, 当慢指针(ps)进入环之后, 最多会走n-1步就能和快指针(pf)相遇, 其中n是环的长度. 也就是说快指针在环能不会跳过慢指针, 这个性质可以简单的用归纳法来证明. (1)当ps在环中位置i, 而pf在环中位置i-1, 则在下一个iteration, ps会和pf在i+1相遇.
(2)当ps在环中位置i, 而pf在环中位置i-2, 则在下一个iteration, ps在i+1, pf在i, 于是在下一个iteration ps和pf会相遇在i+2位置

(3)和上面推理过程类似, 当ps在i, pf在i+1, 则他们会经过n-1个iteration在i+n-1的位置相遇. 于是慢指针的步数不会超过n-1.

扩展:

这个问题还有一些扩展, 例如, 如何找到环的开始节点? 如何解开这个环? 这些问题的本质就是如何找到有"回边"的那个节点.

我们可以利用方法3的一个变形来解决这个问题:

 

Boolean has_loop(Node *head) {

  Node *pf = head; /* fast pointer */

  Node *ps = head; /* slow pointer */

  while(true) {  /* step 1, is there a loop? */

    if(pf && pf->next)

      pf = pf->next->next;

    else

      return FALSE;

    ps = ps->next;

    if(ps == pf)

      break;

  }

  /* step 2, how long is the loop */

  int i = 0;

   do {

    ps = ps->next;

    pf = pf->next->next;

    i++;

  } while(ps!=pf)

 

  /* step 3, use 2 addtional pointers with distance i to break the loop */

  ps = head;

  pf = head;

  int j;

  for(j=0; j<i; j++) { pf = pf->next; }

  j = 0;

  while(ps!=pf) {

   ps = ps->next;

   pf = pf->next;

   j++;

  }

  printf("loop begins at position %d, node address is %x", j, ps);

  /*step 4, break the loop*/

  for(j=0; j<=i; j++) { ps = ps->next; } //step i-1 in the loop from loop beginning

  ps->next = NULL; // break the loop

 

  return TRUE;

}


转载地址:http://www.cnblogs.com/zqrferrari/archive/2010/07/12/1776115.html

这篇关于如何检查一个单向链表上是否有环?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/711557

相关文章

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

C#比较两个List集合内容是否相同的几种方法

《C#比较两个List集合内容是否相同的几种方法》本文详细介绍了在C#中比较两个List集合内容是否相同的方法,包括非自定义类和自定义类的元素比较,对于非自定义类,可以使用SequenceEqual、... 目录 一、非自定义类的元素比较1. 使用 SequenceEqual 方法(顺序和内容都相等)2.

查询Oracle数据库表是否被锁的实现方式

《查询Oracle数据库表是否被锁的实现方式》本文介绍了查询Oracle数据库表是否被锁的方法,包括查询锁表的会话、人员信息,根据object_id查询表名,以及根据会话ID查询和停止本地进程,同时,... 目录查询oracle数据库表是否被锁1、查询锁表的会话、人员等信息2、根据 object_id查询被

shell脚本快速检查192.168.1网段ip是否在用的方法

《shell脚本快速检查192.168.1网段ip是否在用的方法》该Shell脚本通过并发ping命令检查192.168.1网段中哪些IP地址正在使用,脚本定义了网络段、超时时间和并行扫描数量,并使用... 目录脚本:检查 192.168.1 网段 IP 是否在用脚本说明使用方法示例输出优化建议总结检查 1

如何测试计算机的内存是否存在问题? 判断电脑内存故障的多种方法

《如何测试计算机的内存是否存在问题?判断电脑内存故障的多种方法》内存是电脑中非常重要的组件之一,如果内存出现故障,可能会导致电脑出现各种问题,如蓝屏、死机、程序崩溃等,如何判断内存是否出现故障呢?下... 如果你的电脑是崩溃、冻结还是不稳定,那么它的内存可能有问题。要进行检查,你可以使用Windows 11

使用Python检查CPU型号并弹出警告信息

《使用Python检查CPU型号并弹出警告信息》本教程将指导你如何编写一个Python程序,该程序能够在启动时检查计算机的CPU型号,如果检测到CPU型号包含“I3”,则会弹出一个警告窗口,感兴趣的小... 目录教程目标方法一所需库步骤一:安装所需库步骤二:编写python程序步骤三:运行程序注意事项方法二

csu1329(双向链表)

题意:给n个盒子,编号为1到n,四个操作:1、将x盒子移到y的左边;2、将x盒子移到y的右边;3、交换x和y盒子的位置;4、将所有的盒子反过来放。 思路分析:用双向链表解决。每个操作的时间复杂度为O(1),用数组来模拟链表,下面的代码是参考刘老师的标程写的。 代码如下: #include<iostream>#include<algorithm>#include<stdio.h>#

深入手撕链表

链表 分类概念单链表增尾插头插插入 删尾删头删删除 查完整实现带头不带头 双向链表初始化增尾插头插插入 删查完整代码 数组 分类 #mermaid-svg-qKD178fTiiaYeKjl {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-