假设您在Java中有一个链表结构。 它由节点组成:

class Node {Node next;// some user data
}

每个节点都指向下一个节点,但最后一个节点除外,后者的下一个为空。 假设列表有可能包含一个循环-即最终Node(而不是null)具有对列表中位于其之前的节点之一的引用。

最好的写作方式是什么

boolean hasLoop(Node first)

如果给定的Node是带有循环的列表的第一个,则返回true否则返回false ? 您怎么写才能占用恒定的空间和合理的时间?

这是带有循环的列表的外观图:


#1楼

您甚至可以在恒定的O(1)时间内执行此操作(尽管它不是非常快或高效):您的计算机内存可以容纳的节点数量有限,例如N条记录。 如果遍历多于N条记录,则有一个循环。


#2楼

public boolean isCircular() {if (head == null)return false;Node temp1 = head;Node temp2 = head;try {while (temp2.next != null) {temp2 = temp2.next.next.next;temp1 = temp1.next;if (temp1 == temp2 || temp1 == temp2.next) return true;    }} catch (NullPointerException ex) {return false;}return false;}

#3楼

比弗洛伊德算法更好

理查德·布伦特(Richard Brent)描述了一种替代的周期检测算法 ,该算法与野兔和乌龟[弗洛伊德的周期]非常相似,不同之处在于,此处的慢速节点不移动,但后来“传送”到固定位置的快速节点的位置。间隔。

此处提供了描述: http : //www.siafoo.net/algorithm/11布伦特声称他的算法比弗洛伊德循环算法快24%至36%。 O(n)时间复杂度,O(1)空间复杂度。

public static boolean hasLoop(Node root){if(root == null) return false;Node slow = root, fast = root;int taken = 0, limit = 2;while (fast.next != null) {fast = fast.next;taken++;if(slow == fast) return true;if(taken == limit){taken = 0;limit <<= 1;    // equivalent to limit *= 2;slow = fast;    // teleporting the turtle (to the hare's position) }}return false;
}

#4楼

算法

public static boolean hasCycle (LinkedList<Node> list)
{HashSet<Node> visited = new HashSet<Node>();for (Node n : list){visited.add(n);if (visited.contains(n.next)){return true;}}return false;
}

复杂

Time ~ O(n)
Space ~ O(n)

#5楼

我可能来晚了又很新来处理这个线程。 但是还是

为什么不能将节点和指向的“下一个”节点的地址存储在表中

如果我们可以这样制表

node present: (present node addr) (next node address)node 1: addr1: 0x100 addr2: 0x200 ( no present node address till this point had 0x200)
node 2: addr2: 0x200 addr3: 0x300 ( no present node address till this point had 0x300)
node 3: addr3: 0x300 addr4: 0x400 ( no present node address till this point had 0x400)
node 4: addr4: 0x400 addr5: 0x500 ( no present node address till this point had 0x500)
node 5: addr5: 0x500 addr6: 0x600 ( no present node address till this point had 0x600)
node 6: addr6: 0x600 addr4: 0x400 ( ONE present node address till this point had 0x400)

因此,形成了一个循环。


#6楼

这种方法有空间开销,但是实现起来比较简单:

可以通过将节点存储在Map中来标识循环。 并且在放置节点之前; 检查节点是否已经存在。 如果节点已存在于映射中,则意味着链接列表具有循环。

public boolean loopDetector(Node<E> first) {  Node<E> t = first;  Map<Node<E>, Node<E>> map = new IdentityHashMap<Node<E>, Node<E>>();  while (t != null) {  if (map.containsKey(t)) {  System.out.println(" duplicate Node is --" + t  + " having value :" + t.data);  return true;  } else {  map.put(t, t);  }  t = t.next;  }  return false;  }

#7楼

乌龟和野兔

看一下Pollard的rho算法 。 这不是完全相同的问题,但是也许您会从中了解逻辑,并将其应用于链接列表。

(如果您很懒,可以只检查周期检测 -检查有关乌龟和野兔的部分。)

这仅需要线性时间和2个额外的指针。

在Java中:

boolean hasLoop( Node first ) {if ( first == null ) return false;Node turtle = first;Node hare = first;while ( hare.next != null && hare.next.next != null ) {turtle = turtle.next;hare = hare.next.next;if ( turtle == hare ) return true;}return false;
}

(大多数解决方案都不会同时检查nextnext.next是否为空。而且,由于乌龟始终在后面,因此您不必将其检查为空,因为兔子已经这样做了。)


#8楼

以下可能不是最佳方法-它是O(n ^ 2)。 但是,它应该有助于完成工作(最终)。

count_of_elements_so_far = 0;
for (each element in linked list)
{search for current element in first <count_of_elements_so_far>if found, then you have a loopelse,count_of_elements_so_far++;
}

#9楼

您可以利用弗洛伊德(Floyd)的循环查找算法 ,也称为草龟和野兔算法

想法是要有两个引用列表,并以不同的速度移动它们。 将一个节点向前移动1节点,将另一个节点向前移动2节点。

  • 如果链表有循环,它们肯定会碰面。
  • 其他两个引用(或它们的next )将变为null

实现该算法的Java函数:

boolean hasLoop(Node first) {if(first == null) // list does not exist..so no loop eitherreturn false;Node slow, fast; // create two references.slow = fast = first; // make both refer to the start of the listwhile(true) {slow = slow.next;          // 1 hopif(fast.next != null)fast = fast.next.next; // 2 hopselsereturn false;          // next node null => no loopif(slow == null || fast == null) // if either hits null..no loopreturn false;if(slow == fast) // if the two ever meet...we must have a loopreturn true;}
}

#10楼

我看不到有什么方法可以使它花费固定的时间或空间,两者都会随着列表的大小而增加。

我将使用一个IdentityHashMap(假设还没有IdentityHashSet)并将每个Node存储到地图中。 在存储节点之前,您将在其上调用containsKey。 如果该节点已经存在,则有一个循环。

ItentityHashMap使用==代替.equals,以便您检查对象在内存中的位置,而不是对象是否具有相同的内容。


#11楼

我暂时更改列表后,Turtle and Rabbit的替代解决方案不太好:

想法是遍历列表,并在执行时将其反转。 然后,当您第一次到达已被访问的节点时,其下一个指针将指向“向后”,从而导致迭代再次朝着first方向进行,并终止于此。

Node prev = null;
Node cur = first;
while (cur != null) {Node next = cur.next;cur.next = prev;prev = cur;cur = next;
}
boolean hasCycle = prev == first && first != null && first.next != null;// reconstruct the list
cur = prev;
prev = null;
while (cur != null) {Node next = cur.next;cur.next = prev;prev = cur;cur = next;
}return hasCycle;

测试代码:

static void assertSameOrder(Node[] nodes) {for (int i = 0; i < nodes.length - 1; i++) {assert nodes[i].next == nodes[i + 1];}
}public static void main(String[] args) {Node[] nodes = new Node[100];for (int i = 0; i < nodes.length; i++) {nodes[i] = new Node();}for (int i = 0; i < nodes.length - 1; i++) {nodes[i].next = nodes[i + 1];}Node first = nodes[0];Node max = nodes[nodes.length - 1];max.next = null;assert !hasCycle(first);assertSameOrder(nodes);max.next = first;assert hasCycle(first);assertSameOrder(nodes);max.next = max;assert hasCycle(first);assertSameOrder(nodes);max.next = nodes[50];assert hasCycle(first);assertSameOrder(nodes);
}

#12楼

public boolean hasLoop(Node start){   TreeSet<Node> set = new TreeSet<Node>();Node lookingAt = start;while (lookingAt.peek() != null){lookingAt = lookingAt.next;if (set.contains(lookingAt){return false;} else {set.put(lookingAt);}return true;
}
// Inside our Node class:
public Node peek(){return this.next;
}

原谅我的无知(我对Java和编程还是相当陌生的),但是为什么上面的方法不起作用?

我猜这不能解决恒定的空间问题...但是至少可以在合理的时间内到达那里,对吗? 它只会占用链接列表的空间加上具有n个元素的集合的空间(其中n是链接列表中的元素数,或者直到到达循环为止的元素数)。 对于时间,我认为最坏情况的分析建议使用O(nlog(n))。 对contains()的SortedSet查找为log(n)(请检查javadoc,但我很确定TreeSet的基础结构是TreeMap,而后者又是一棵红黑树),在最坏的情况下(没有循环,或在最后循环),则必须进行n次查找。


#13楼

如果允许我们嵌入类Node ,那么我将在下面实现它的同时解决该问题。 hasLoop()以O(n)时间运行,并且仅占用counter的空间。 这似乎是一个合适的解决方案吗? 还是有一种无需嵌入Node ? (显然,在实际的实现中,会有更多的方法,例如RemoveNode(Node n)等)

public class LinkedNodeList {Node first;Int count;LinkedNodeList(){first = null;count = 0;}LinkedNodeList(Node n){if (n.next != null){throw new error("must start with single node!");} else {first = n;count = 1;}}public void addNode(Node n){Node lookingAt = first;while(lookingAt.next != null){lookingAt = lookingAt.next;}lookingAt.next = n;count++;}public boolean hasLoop(){int counter = 0;Node lookingAt = first;while(lookingAt.next != null){counter++;if (count < counter){return false;} else {lookingAt = lookingAt.next;}}return true;}private class Node{Node next;....}}

#14楼

用户unicornaddict在上面有一个不错的算法,但是不幸的是,它包含一个错误循环,用于奇数长度> = 3的非循环列表。问题在于, fast可能会在列表末尾之前“卡住”,而slow赶上它,并且(错误地)检测到循环。

这是更正后的算法。

static boolean hasLoop(Node first) {if(first == null) // list does not exist..so no loop either.return false;Node slow, fast; // create two references.slow = fast = first; // make both refer to the start of the list.while(true) {slow = slow.next;          // 1 hop.if(fast.next == null)fast = null;elsefast = fast.next.next; // 2 hops.if(fast == null) // if fast hits null..no loop.return false;if(slow == fast) // if the two ever meet...we must have a loop.return true;}
}

#15楼

这是对快速/慢速解决方案的改进,可以正确处理奇数长度列表并提高清晰度。

boolean hasLoop(Node first) {Node slow = first;Node fast = first;while(fast != null && fast.next != null) {slow = slow.next;          // 1 hopfast = fast.next.next;     // 2 hops if(slow == fast)  // fast caught up to slow, so there is a loopreturn true;}return false;  // fast reached null, so the list terminates
}

#16楼

可以用最简单的方法之一来检测链表中的循环,这会导致使用哈希图导致O(N)复杂性,或者使用基于排序的方法导致O(NlogN)复杂性。

从头开始遍历列表时,请创建一个排序的地址列表。 当您插入新地址时,请检查该地址是否已存在于排序列表中,这会增加O(logN)的复杂度。


#17楼

这是我的可运行代码。

我所做的是通过使用三个跟踪链接的临时节点(空间复杂度O(1) )来检查链接列表。

这样做的有趣之处在于,它有助于检测链表中的循环,因为在前进过程中,您不希望返回起始点(根节点),并且其中一个临时节点应该变为空,除非您这样做。有一个周期,这意味着它指向根节点。

该算法的时间复杂度为O(n) ,空间复杂度为O(1)

这是链表的类节点:

public class LinkedNode{public LinkedNode next;
}

这是带有三个节点的简单测试用例的主要代码,最后一个节点指向第二个节点:

    public static boolean checkLoopInLinkedList(LinkedNode root){if (root == null || root.next == null) return false;LinkedNode current1 = root, current2 = root.next, current3 = root.next.next;root.next = null;current2.next = current1;while(current3 != null){if(current3 == root) return true;current1 = current2;current2 = current3;current3 = current3.next;current2.next = current1;}return false;}

这是三个节点的简单测试用例,最后一个节点指向第二个节点:

public class questions{public static void main(String [] args){LinkedNode n1 = new LinkedNode();LinkedNode n2 = new LinkedNode();LinkedNode n3 = new LinkedNode();n1.next = n2;n2.next = n3;n3.next = n2;System.out.print(checkLoopInLinkedList(n1));}
}

#18楼

 // To detect whether a circular loop exists in a linked list
public boolean findCircularLoop() {Node slower, faster;slower = head;faster = head.next; // start faster one node aheadwhile (true) {// if the faster pointer encounters a NULL elementif (faster == null || faster.next == null)return false;// if faster pointer ever equals slower or faster's next// pointer is ever equal to slower then it's a circular listelse if (slower == faster || slower == faster.next)return true;else {// advance the pointersslower = slower.next;faster = faster.next.next;}}
}

#19楼

该代码经过了优化,其结果生成速度比选择最佳答案的速度更快。此代码避免了追逐向前和向后节点指针的漫长过程,如果我们遵循“最佳选择”,则会在以下情况下发生回答''方法。仔细阅读下面的内容,您将了解我要说的内容。然后通过下面的给定方法查看问题并测量否。 找到答案的步骤。

1-> 2-> 9-> 3 ^ -------- ^

这是代码:

boolean loop(node *head)
{node *back=head;node *front=head;while(front && front->next){front=front->next->next;if(back==front)return true;elseback=back->next;}
return false
}

#20楼

boolean hasCycle(Node head) {boolean dec = false;Node first = head;Node sec = head;while(first != null && sec != null){first = first.next;sec = sec.next.next;if(first == sec ){dec = true;break;}}return dec;
}

使用上述功能可以检测Java中链表中的循环。


#21楼

这是我在Java中的解决方案

boolean detectLoop(Node head){Node fastRunner = head;Node slowRunner = head;while(fastRunner != null && slowRunner !=null && fastRunner.next != null){fastRunner = fastRunner.next.next;slowRunner = slowRunner.next;if(fastRunner == slowRunner){return true;}}return false;
}

#22楼

您也可以按照上述答案中的建议使用Floyd的陆龟算法。

该算法可以检查单链表是否具有闭合循环。 这可以通过用两个将以不同速度移动的指针迭代一个列表来实现。 这样,如果存在一个周期,则两个指针将在将来的某个时刻相遇。

请随时查看我在链接列表数据结构上的博客文章 ,其中还包括一个代码片段,其中包含上述Java语言算法的实现。

问候,

安德里亚斯(@xnorcode)


#23楼

这是检测周期的解决方案。

public boolean hasCycle(ListNode head) {ListNode slow =head;ListNode fast =head;while(fast!=null && fast.next!=null){slow = slow.next; // slow pointer only one hopfast = fast.next.next; // fast pointer two hops if(slow == fast)    return true; // retrun true if fast meet slow pointer}return false; // return false if fast pointer stop at end }

#24楼

在这种情况下,文本材料无处不在。 我只是想发布一个图表表示形式,这确实帮助我理解了这个概念。

当快慢点在p点相遇时,

快速行进的距离= a + b + c + b = a + 2b + c

慢速行驶的距离= a + b

因为快是慢的2倍。 所以a + 2b + c = 2(a + b) ,则得到a = c

因此,当另一个慢速指针再次从头运行到q时,快速指针将从p到q运行,因此它们在点q处相遇。

public ListNode detectCycle(ListNode head) {if(head == null || head.next==null)return null;ListNode slow = head;ListNode fast = head;while (fast!=null && fast.next!=null){fast = fast.next.next;slow = slow.next;/*if the 2 pointers meet, then the dist from the meeting pt to start of loop equalsdist from head to start of loop*/if (fast == slow){ //loop foundslow = head;while(slow != fast){slow = slow.next;fast = fast.next;}return slow;}            }return null;
}

#25楼

//链表查找循环功能

int findLoop(struct Node* head)
{struct Node* slow = head, *fast = head;while(slow && fast && fast->next){slow = slow->next;fast = fast->next->next;if(slow == fast)return 1;}return 0;
}

如何检测链表中的循环?相关推荐

  1. python代码检测链表中的环并删除环

    python代码检测链表中的环并删除环 在计算机科学中,链表是数据元素的线性集合,其顺序不是由它们在内存中的物理位置决定的.相反,每个元素指向下一个元素.它是一种数据结构,由一组节点组成,这些节点共同 ...

  2. 如何检测链表中是存在循环

    链表在面试中出现的频率很高,有的比较正常,考链表的常规操作,主要看基本功是否扎实,有些就比较难,难在思维的改变和是否能够想到对应的点.这里出现的是其中一个题目,我称之为有环链表问题.也就是从判断一个单 ...

  3. 写出一段代码将链表中的两个节点位置互换位置_干货||链表的技巧和算法总结...

    链表的操作总结   链表反转 这是一个简单的链表操作问题,在leetcode上面有52.7%的通过率,难度是简单.但是还是想在这里基于python做一下总结,顺便总结一下链表的各种操作. 首先先看一下 ...

  4. 数据结构初阶(4)(OJ练习【判断链表中是否有环、返回链表入口点、删除链表中的所有重复出现的元素】、双向链表LinkedList【注意事项、构造方法、常用方法、模拟实现、遍历方法、顺序表和链表的区别)

    接上次博客:数据结构初阶(3)(链表:链表的基本概念.链表的类型.单向不带头非循环链表的实现.链表的相关OJ练习.链表的优缺点 )_di-Dora的博客-CSDN博客 目录 OJ练习 双向链表--Li ...

  5. 判断单链表中的元素是否递增_检测单链表中是否有环(C语言)

    检测单链表中是否有环(C语言) 方法:双指针法思路 使用两个指针,两个初始时都指向链表的头结点,然后one指针一次加一,另一个two指针一次加二. 在链表有环时,two指针与one指针相等就说明有环. ...

  6. 【问题描述】在带头结点单链表中查找最大值,将其值与最后一个元素交换,输出交换后的单链表各元素。【输入形式】循环输入若干个整数,以字母结束输入,建立带头结点的单链表。【输出形式】输出最

    [问题描述] 在带头结点单链表中查找最大值,将其值与最后一个元素交换,输出交换后的单链表各元素. [输入形式] 循环输入若干个整数,以字母结束输入,建立带头结点的单链表. [输出形式] 输出最大值与最 ...

  7. lua检测表中是否有某个值_Lua基础知识总结(入职面试题)

    2019年8月刚入职新公司时,因为之前的项目都没有使用lua的经验,所以jojo老大出了一份题让我想尽办法找出答案,当时对于一个无经验的小菜鸟来说,属实费了不少功夫,如今分享出来,希望能对刚使用lua ...

  8. 单链表中倒数第K个结点

    单链表中倒数第K个结点 链表结点定义如下: typedef int ElemType;typedef struct Node {ElemType data; struct Node *next; }H ...

  9. LeetCode实战:删除链表中的节点

    背景 为什么你要加入一个技术团队? 如何加入 LSGO 软件技术团队? 我是如何组织"算法刻意练习活动"的? 为什么要求团队的学生们写技术Blog 题目英文 Write a fun ...

最新文章

  1. Springboot 2.返回cookies信息的get接口开发 和 带cookis去请求
  2. Anaconda(miniconda)安装及使用--转
  3. 利用Virtual Audio Cable实现系统声音录制
  4. OSI七层与TCP/IP四/五层网络架构
  5. 减治法解决八枚硬币问题/假币问题(JAVA)----二分,三分,不知轻重的情况
  6. JavaScript中引号的多重嵌套
  7. MFC 单文档应用程序 dialog 变量传递
  8. 内容 超链接_Word高效办公:自动创建带超链接的内容目录和图表目录
  9. MIPI-DSI 三种 Video Mode 理解
  10. UOJ 30 【CF Round #278】Tourists
  11. qlv转mp4失败 解决方法
  12. 什么软件测试苹果手机循环电池,如何检查iPhone电池的电池循环次数,看完你就明白了...
  13. NISP一级模拟题(一、二)
  14. 【图像视觉基础】浅谈CCD和CMOS相机的联系和区别
  15. android中listView下拉刷新
  16. 双目立体匹配算法SGBM
  17. 《财富自由之路》读书笔记
  18. 【它山之玉】博士三年发表论文回顾—科学网马臻
  19. Excel工作表单保护密码忘记如何找回
  20. 超声波脉冲发生器可调电源设计

热门文章

  1. 走过路过不要错过,面了六轮才拿到阿里Android研发岗的Offer,确定不来看看?
  2. 从URL输入到页面展现,过程中发生了什么?
  3. H5 App开发工具 WeX5
  4. python中标识符下划线用作开头_python python中那些双下划线开头的那些函数都是干啥用用的...
  5. Android之自定义控件显示点赞用户并通过用户名称进入该用户主页的功能
  6. (0025)iOS 开发之Xcode常用快捷键
  7. php excel parser pro v4.2,php中使用ExcelFileParser处理excel获得数据(可作批量导入到数据库使用)...
  8. arcgis python 新建图层组_分享Arcgis中62个常用技巧系列一(前20个技巧)
  9. 【转载】flash时间轴中变量的作用域
  10. 大北农集团被指控授意窃取商业秘密